#!/usr/bin/env python3 """ GUARDiA ESN — 중앙 guardia-rag 색인 훅 (대표 도메인/운영 지식 → esn 테넌트 컬렉션). 알람 원인분석(GraphRAG 의존성 근거)·POS 분류(스키마 grounding)를 위해, ESN 의 대표 도메인 메타데이터(테이블/관계/운영 룰)만 중앙 RAG 의 테넌트별 컬렉션(esn__)에 적재한다. 실제 데이터 행·자격증명·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__ 로 분리. 한 테넌트 색인이 다른 테넌트에 섞이지 않음. - 멱등: 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())