123 lines
5.4 KiB
Python
123 lines
5.4 KiB
Python
#!/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())
|