guardia-esn/scripts/rag_ingest_esn.py

123 lines
5.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""
GUARDiA ESN — 중앙 guardia-rag 색인 훅 (대표 도메인/운영 지식 → esn 테넌트 컬렉션).
알람 원인분석(GraphRAG 의존성 근거)·POS 분류(스키마 grounding)를 위해, ESN 의 대표
도메인 메타데이터(테이블/관계/운영 룰)만 중앙 RAG 의 테넌트별 컬렉션(esn__<TENANT>)에 적재한다.
실제 데이터 행·자격증명·PII(passwordHash·ssh_*)는 색인하지 않는다(보안 불변).
사용:
python scripts/rag_ingest_esn.py # 4개 테넌트 전부
python scripts/rag_ingest_esn.py LGINNOTEK # 특정 테넌트만
RAG_URL=http://127.0.0.1:8020 python scripts/rag_ingest_esn.py
설계 메모:
- 외부 API 0. 중앙 guardia-rag(온프레미스 루프백)만 호출.
- 테넌트 격리: 컬렉션 esn__<TENANT> 로 분리. 한 테넌트 색인이 다른 테넌트에 섞이지 않음.
- 멱등: doc_id 안정 → 재실행 시 upsert(중앙 처리). 실패해도 ESN 동작 영향 없음(보조 스크립트).
"""
import json
import os
import sys
import urllib.request
import urllib.error
RAG_URL = os.environ.get("RAG_URL", "http://127.0.0.1:8020").rstrip("/")
SOLUTION = "esn"
TENANTS = ["LGINNOTEK", "LGIT", "EMART", "ZIOINFO"]
# 대표 도메인/운영 지식 문서 — 알람 의존성 추적·POS 분류 grounding 용.
# (자격증명·PII 제외. 관계·룰·물리 테이블 정의 중심. 테넌트 공통 지식 → 각 테넌트 컬렉션에 적재.)
COMMON_DOCS = [
{
"doc_id": "esn_dep_graph",
"source": "ESN-DEPENDENCY",
"text": (
"의존성 관계: esn_hcore_device(게이트웨이/허브) → esn_store(매장) → ESL 단말 → "
"esn_tag_binding(ESL↔상품 바인딩). 허브 1대 장애 시 하위 매장의 다수 ESL 단말에서 "
"연쇄(cascading) 알람이 발생한다. 동일 hcore 하위 단말의 동시 알람은 단일 근본원인(허브)으로 "
"수렴하여 1건으로 그룹핑한다."
),
},
{
"doc_id": "esn_alarm_schema",
"source": "ESN-SCHEMA",
"text": (
"테이블 esn_alarm: 장치 알람. 컬럼 - id, tenant_code, store_code, device_id, "
"alarm_type(통신단절/배터리/표시오류 등), severity(CRITICAL/HIGH/MEDIUM/LOW), message, "
"occurred_at. 우선순위 산정: 영향 단말 수 × 테넌트 중요도 × SLA 잔여시간 × 중복도 가중합 → P1~P4."
),
},
{
"doc_id": "esn_priority_rule",
"source": "ESN-RULE",
"text": (
"우선순위 규칙(폴백): severity CRITICAL→P1, HIGH→P2, MEDIUM→P3, LOW→P4. "
"단, 영향 단말 수가 많거나 핵심 매장이면 한 단계 상향. 같은 groupKey(동일 근본원인) 파생 "
"알람은 1건으로 집계한다."
),
},
{
"doc_id": "esn_poscvt_schema",
"source": "ESN-SCHEMA",
"text": (
"테이블 esn_pos_cvt: POS→ESL 가격 변환. 컬럼 - id, tenant_code, product_code, price(정가), "
"sale_price(판매가), status. 분류 룰 — ABNORMAL 판정: 판매가>정가, 음수 가격, 누락, "
"비현실적 값(예: 0 또는 과대). 그 외 NORMAL. confidence 임계 미만은 수동 검토(needsReview)."
),
},
{
"doc_id": "esn_hcore_schema",
"source": "ESN-SCHEMA",
"text": (
"테이블 esn_hcore_device: 게이트웨이/허브. 컬럼 - id, tenant_code, store_code, status"
"(ONLINE/OFFLINE), child_device_count(하위 단말 수), last_seen_at(마지막 통신). "
"OFFLINE + 하위 단말 다수 알람 = 허브 장애 시그니처."
),
},
]
def ingest_tenant(tenant: str) -> int:
collection = f"{SOLUTION}__{tenant}"
# 문서에 테넌트 메타 부여(컬렉션 격리)
docs = [{**d, "tenant_code": tenant} for d in COMMON_DOCS]
payload = {"solution": SOLUTION, "collection": collection, "tenant_code": tenant, "documents": docs}
data = json.dumps(payload).encode("utf-8")
req = urllib.request.Request(
f"{RAG_URL}/rag/ingest",
data=data,
headers={"Content-Type": "application/json", "X-Solution-Key": SOLUTION},
method="POST",
)
try:
with urllib.request.urlopen(req, timeout=120) as resp:
body = resp.read().decode("utf-8")
print(f"[OK] ingested {len(docs)} docs into '{collection}'")
print(f" response: {body[:200]}")
return 0
except urllib.error.HTTPError as e:
print(f"[WARN] ingest HTTP {e.code} ({collection}) — 중앙 RAG 응답 이상(보조 스크립트). ESN 동작 영향 없음.")
return 1
except urllib.error.URLError as e:
print(f"[WARN] 중앙 guardia-rag 미가용({RAG_URL}) — 색인 보류. 라이브 가동 후 재실행. (요약: {str(e.reason)[:80]})")
return 1
except Exception:
print(f"[WARN] 색인 중 예기치 못한 오류({collection}) — 보류(ESN 동작 영향 없음).")
return 1
def main() -> int:
targets = [t.upper() for t in sys.argv[1:]] or TENANTS
rc = 0
for t in targets:
if t not in TENANTS:
print(f"[SKIP] 알 수 없는 테넌트: {t} (허용: {', '.join(TENANTS)})")
continue
rc |= ingest_tenant(t)
return rc
if __name__ == "__main__":
sys.exit(main())