분석 사이클 완료 후 자동 실행되는 출력 파이프라인: - 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>
65 lines
1.8 KiB
Python
65 lines
1.8 KiB
Python
"""
|
|
경보 발송 — CRITICAL/HIGH 이벤트에 대해 prediction_alerts INSERT.
|
|
|
|
현재는 DASHBOARD 채널만 기록 (실제 SMS/EMAIL은 향후 연동).
|
|
"""
|
|
import logging
|
|
from datetime import datetime, timezone
|
|
|
|
from psycopg2.extras import execute_values
|
|
|
|
from config import qualified_table
|
|
from db.kcgdb import get_conn
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
ALERTS_TABLE = qualified_table('prediction_alerts')
|
|
EVENTS_TABLE = qualified_table('prediction_events')
|
|
|
|
# CRITICAL/HIGH 이벤트만 알림 대상
|
|
ALERT_LEVELS = ('CRITICAL', 'HIGH')
|
|
|
|
|
|
def run_alert_dispatcher() -> dict:
|
|
"""
|
|
아직 알림이 없는 CRITICAL/HIGH 이벤트에 대해 알림 생성.
|
|
|
|
Returns:
|
|
{ 'dispatched': int }
|
|
"""
|
|
now = datetime.now(timezone.utc)
|
|
dispatched = 0
|
|
|
|
with get_conn() as conn:
|
|
cur = conn.cursor()
|
|
|
|
# 알림이 아직 없는 CRITICAL/HIGH 이벤트 조회
|
|
cur.execute(
|
|
f"""SELECT e.id, e.ai_confidence
|
|
FROM {EVENTS_TABLE} e
|
|
LEFT JOIN {ALERTS_TABLE} a ON a.event_id = e.id
|
|
WHERE e.level IN %s AND a.id IS NULL
|
|
ORDER BY e.occurred_at DESC
|
|
LIMIT 100""",
|
|
(ALERT_LEVELS,)
|
|
)
|
|
rows = cur.fetchall()
|
|
|
|
if rows:
|
|
alerts = [
|
|
(event_id, 'DASHBOARD', None, now, 'SENT', confidence)
|
|
for event_id, confidence in rows
|
|
]
|
|
execute_values(
|
|
cur,
|
|
f"""INSERT INTO {ALERTS_TABLE}
|
|
(event_id, channel, recipient, sent_at, delivery_status, ai_confidence)
|
|
VALUES %s""",
|
|
alerts,
|
|
)
|
|
conn.commit()
|
|
dispatched = len(alerts)
|
|
|
|
logger.info(f'alert_dispatcher: dispatched={dispatched}')
|
|
return {'dispatched': dispatched}
|