""" 실시간 KPI 갱신 — prediction_kpi_realtime 테이블 업데이트. 매 분석 사이클마다 오늘 날짜 기준 카운트를 계산하여 6개 KPI 갱신. """ import logging from datetime import datetime, timedelta, timezone from config import qualified_table from db.kcgdb import get_conn logger = logging.getLogger(__name__) KPI_TABLE = qualified_table('prediction_kpi_realtime') EVENTS_TABLE = qualified_table('prediction_events') ENF_TABLE = qualified_table('enforcement_records') VAR_TABLE = qualified_table('vessel_analysis_results') # 한국 표준시 (운영 기준) _KST = timezone(timedelta(hours=9)) def run_kpi_writer() -> dict: """ 오늘(KST) 날짜 기준으로 6개 KPI를 재계산하여 갱신. Returns: { kpi_key: value } 딕셔너리 """ # KST 기준 "오늘" 시작 시각 (해당 시각은 UTC로도 비교 가능하므로 DB 필드가 TIMESTAMPTZ면 안전) now_kst = datetime.now(_KST) today_start = now_kst.replace(hour=0, minute=0, second=0, microsecond=0) now = datetime.now(timezone.utc) results = {} with get_conn() as conn: cur = conn.cursor() # 1. 실시간 탐지 (오늘 분석 결과 수) cur.execute( f"SELECT COUNT(DISTINCT mmsi) FROM {VAR_TABLE} WHERE analyzed_at >= %s", (today_start,) ) realtime = cur.fetchone()[0] or 0 results['realtime_detection'] = realtime # 2. EEZ 침범 (오늘 EEZ 관련 이벤트) cur.execute( f"SELECT COUNT(*) FROM {EVENTS_TABLE} WHERE category = 'EEZ_INTRUSION' AND occurred_at >= %s", (today_start,) ) eez = cur.fetchone()[0] or 0 results['eez_violation'] = eez # 3. 다크베셀 (현재 dark 상태인 선박) cur.execute( f"""SELECT COUNT(DISTINCT mmsi) FROM {VAR_TABLE} WHERE is_dark = true AND analyzed_at >= %s""", (today_start,) ) dark = cur.fetchone()[0] or 0 results['dark_vessel'] = dark # 4. 환적 의심 (오늘) cur.execute( f"""SELECT COUNT(*) FROM {EVENTS_TABLE} WHERE category = 'ILLEGAL_TRANSSHIP' AND occurred_at >= %s""", (today_start,) ) transship = cur.fetchone()[0] or 0 results['illegal_transship'] = transship # 5. 추적 중 (IN_PROGRESS 상태 이벤트) cur.execute( f"SELECT COUNT(*) FROM {EVENTS_TABLE} WHERE status = 'IN_PROGRESS'" ) tracking = cur.fetchone()[0] or 0 results['tracking_active'] = tracking # 6. 나포/검문 (오늘 단속) cur.execute( f"SELECT COUNT(*) FROM {ENF_TABLE} WHERE enforced_at >= %s", (today_start,) ) captured = cur.fetchone()[0] or 0 results['captured_inspected'] = captured # KPI 테이블 업데이트 (이전 값과 비교하여 trend 계산) for key, value in results.items(): cur.execute( f"SELECT value FROM {KPI_TABLE} WHERE kpi_key = %s", (key,) ) row = cur.fetchone() prev = row[0] if row else 0 if value > prev: trend, delta = 'up', ((value - prev) / max(prev, 1)) * 100 elif value < prev: trend, delta = 'down', ((value - prev) / max(prev, 1)) * 100 else: trend, delta = 'flat', 0.0 cur.execute( f"""UPDATE {KPI_TABLE} SET value = %s, trend = %s, delta_pct = %s, updated_at = %s WHERE kpi_key = %s""", (value, trend, round(delta, 2), now, key) ) conn.commit() logger.info(f'kpi_writer: {results}') return results