kcg-ai-monitoring/prediction/output/kpi_writer.py
htlee 474e672683 feat: S3 prediction 신규 출력 모듈 5종 + scheduler 통합
분석 사이클 완료 후 자동 실행되는 출력 파이프라인:
- event_generator: 분석결과 → 이벤트 자동 생성 (7개 룰, 카테고리별 dedup)
- violation_classifier: 위반 유형 라벨링 (EEZ/DARK/MMSI/TRANSSHIP/GEAR/RISK)
- kpi_writer: 실시간 KPI 6개 갱신 (오늘 기준 카운트)
- stats_aggregator: hourly/daily/monthly 사전 집계 (UPSERT)
- alert_dispatcher: CRITICAL/HIGH 이벤트 자동 알림 생성

scheduler.py에 출력 모듈 통합 (분석 8단계 완료 후 실행, non-fatal)
DB 연동 테스트 통과 (alerts 8건 생성, KPI tracking_active=2)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 13:00:50 +09:00

110 lines
3.5 KiB
Python

"""
실시간 KPI 갱신 — prediction_kpi_realtime 테이블 업데이트.
매 분석 사이클마다 오늘 날짜 기준 카운트를 계산하여 6개 KPI 갱신.
"""
import logging
from datetime import date, datetime, 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')
def run_kpi_writer() -> dict:
"""
오늘 날짜 기준으로 6개 KPI를 재계산하여 갱신.
Returns:
{ kpi_key: value } 딕셔너리
"""
today = date.today()
today_start = datetime(today.year, today.month, today.day, tzinfo=timezone.utc)
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