feat: 운전판정 고도화 — realtime stall 수정 + 교차검증 + 단위/레인지
- ExperionRealtimeService를 단일 SuperviseAsync supervisor로 재설계: 비블로킹 부팅, PublishingStopped/KeepAliveStopped 워치독으로 silent stall 감지, 30초 주기 무한 재연결, flush 루프 단일화 - RealtimeServiceStatus에 LastDataAgeSeconds/Stalled 추가, History는 Stalled 시 스냅샷 skip - v_plant_running_state에 진공펌프(vp-) 포함 + 교차검증 4객체 (pump_corroboration_manual, v_pump_signal_map, v_plant_running_state_corroborated, v_plant_running_state_agg) + v_instrument_range 뷰 (boot DDL) - MetadataLoaderService에 euhi/eulo/units 메타속성 추가 - generate_status_report에 agg 조회 연동 + sample/focus 버그 수정 - plant_context.md에 펌프 prefix(p-/vp-) + 교차검증 뷰 사용법 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -1708,6 +1708,25 @@ async def generate_status_report(area: str | None = None, hours: int = 24) -> st
|
||||
for ev in events:
|
||||
by_type[ev["event_type"]] = by_type.get(ev["event_type"], 0) + 1
|
||||
|
||||
# 2.5) 펌프 운전 교차검증 (유량/진공 기반) — v_plant_running_state_agg
|
||||
pump_corr: list[dict] = []
|
||||
try:
|
||||
corr_raw = await _execute_sql_internal(
|
||||
"SELECT area_code, status, total_pumps, confirmed_running, suspicious_running, "
|
||||
"stale_running, indeterminate_running, tripped_pumps, "
|
||||
"confirmed_tags, suspicious_tags, stale_tags "
|
||||
"FROM v_plant_running_state_agg ORDER BY area_code"
|
||||
)
|
||||
corr_parsed = json.loads(corr_raw)
|
||||
if corr_parsed.get("success"):
|
||||
pump_corr = corr_parsed.get("data", [])
|
||||
if area:
|
||||
_f = [r for r in pump_corr if (r.get("area_code") or "").upper() == area.upper()]
|
||||
if _f:
|
||||
pump_corr = _f
|
||||
except Exception:
|
||||
pump_corr = []
|
||||
|
||||
# 3) LLM 보고서
|
||||
alarm_lines = [
|
||||
f"- [{a['event_type']}] {a['tag_name']} since {_kst_str(a['since'])} "
|
||||
@@ -1718,9 +1737,16 @@ async def generate_status_report(area: str | None = None, hours: int = 24) -> st
|
||||
f"- [{ev['event_type']}] {ev['tag_name']} @ {_kst_str(ev['event_time'])} "
|
||||
f"({ev.get('prev_value')}→{ev.get('curr_value')})"
|
||||
f" (직전상태유지={ev.get('prev_state_duration_s', '?')}s)"
|
||||
for ev in sample
|
||||
for ev in events[:40]
|
||||
]
|
||||
focus_line = f"\n특히 다음 관점을 우선해 설명하세요: {focus}\n" if focus else ""
|
||||
corr_lines = [
|
||||
f"- {r.get('area_code')}: {r.get('status')} "
|
||||
f"(확인 {r.get('confirmed_running', 0)}, 의심 {r.get('suspicious_running', 0)}, "
|
||||
f"정체 {r.get('stale_running', 0)}, 트립 {r.get('tripped_pumps', 0)})"
|
||||
+ (f" 의심펌프={r.get('suspicious_tags')}" if r.get('suspicious_running') else "")
|
||||
+ (f" 정체펌프={r.get('stale_tags')}" if r.get('stale_running') else "")
|
||||
for r in pump_corr
|
||||
] or ["- 펌프 교차검증 데이터 없음"]
|
||||
|
||||
system = (
|
||||
"당신은 IIoT/공장 운전 분석 전문가입니다. 디지털 포인트의 상태 변경 이벤트 로그를 보고 "
|
||||
@@ -1731,13 +1757,17 @@ async def generate_status_report(area: str | None = None, hours: int = 24) -> st
|
||||
"4) 다음 점검 권고 (있다면)\n"
|
||||
"구체적인 태그명과 시각을 포함하되 추측은 자제합니다.\n\n"
|
||||
"참고 - 모든 시각은 KST(UTC+9, Asia/Seoul)입니다. "
|
||||
"`직전상태유지`는 이 이벤트 직전에 태그가 머물렀던 시간(초)입니다."
|
||||
"`직전상태유지`는 이 이벤트 직전에 태그가 머물렀던 시간(초)입니다.\n"
|
||||
"펌프 운전 교차검증: 펌프 RUN 상태를 유량(kg/hr)·진공압(torr)으로 확인한 결과입니다. "
|
||||
"'확인'=실질 운전, '의심'=RUN인데 유량/진공 없음(deadhead·센서이상 가능), "
|
||||
"'정체'=실시간 수집 지연으로 판정 보류. 의심/정체가 있으면 보고서에 우선 명시하세요."
|
||||
)
|
||||
user_msg = (
|
||||
f"대상 area: {area or '전체'}\n"
|
||||
f"분석 윈도우: 최근 {hours}시간\n"
|
||||
f"이벤트 통계 (type별): {by_type}\n"
|
||||
f"활성 알람 {len(alarms)}건:\n" + "\n".join(alarm_lines) + "\n\n"
|
||||
f"펌프 운전 교차검증 (area별):\n" + "\n".join(corr_lines) + "\n\n"
|
||||
f"최근 이벤트 표본 (최대 40건):\n" + "\n".join(recent_lines)
|
||||
)
|
||||
|
||||
@@ -1766,6 +1796,7 @@ async def generate_status_report(area: str | None = None, hours: int = 24) -> st
|
||||
"active_alarms_count": len(alarms),
|
||||
"recent_events_count": len(events),
|
||||
"by_type": by_type,
|
||||
"pump_corroboration": pump_corr,
|
||||
"window_hours": hours,
|
||||
"area": area,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user