feat: notification cooldown dedup for events and problems (notif-31)

Store last notify time by dedup_key/fingerprint, gate dispatch before
channels; config SAC_NOTIFY_*_COOLDOWN_SEC, migration 008.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
PTah
2026-05-29 16:24:01 +10:00
parent 1a70980920
commit 4167687dec
12 changed files with 309 additions and 14 deletions
+25 -9
View File
@@ -1,18 +1,19 @@
"""Dispatch ingest notifications per global policy (severity → channels)."""
import logging
from sqlalchemy.orm import Session
from app.models import Event, Problem
from app.services import email_notify, telegram_notify, webhook_notify
from app.services.notification_cooldown import should_notify_event, should_notify_problem
from app.services.notification_policy import get_effective_notification_policy
from app.services.notification_severity import severity_meets_minimum
from app.services.notification_settings import CHANNEL_EMAIL, CHANNEL_TELEGRAM, CHANNEL_WEBHOOK
logger = logging.getLogger(__name__)
def notify_event(event: Event, *, db: Session | None = None) -> None:
policy = get_effective_notification_policy(db)
if not severity_meets_minimum(event.severity, policy.min_severity):
return
def _dispatch_event_channels(event: Event, *, db: Session | None, policy) -> None:
if policy.use_telegram:
telegram_notify.notify_event(event, db=db, apply_policy_gate=False)
if policy.use_webhook:
@@ -21,13 +22,28 @@ def notify_event(event: Event, *, db: Session | None = None) -> None:
email_notify.notify_event(event, db=db, apply_policy_gate=False)
def notify_problem(problem: Problem, event: Event | None = None, *, db: Session | None = None) -> None:
policy = get_effective_notification_policy(db)
if not severity_meets_minimum(problem.severity, policy.min_severity):
return
def _dispatch_problem_channels(problem: Problem, event: Event | None, *, db: Session | None, policy) -> None:
if policy.use_telegram:
telegram_notify.notify_problem(problem, event, db=db, apply_policy_gate=False)
if policy.use_webhook:
webhook_notify.notify_problem(problem, event, db=db, apply_policy_gate=False)
if policy.use_email:
email_notify.notify_problem(problem, event, db=db, apply_policy_gate=False)
def notify_event(event: Event, *, db: Session | None = None) -> None:
policy = get_effective_notification_policy(db)
if not severity_meets_minimum(event.severity, policy.min_severity):
return
if not should_notify_event(event, db):
return
_dispatch_event_channels(event, db=db, policy=policy)
def notify_problem(problem: Problem, event: Event | None = None, *, db: Session | None = None) -> None:
policy = get_effective_notification_policy(db)
if not severity_meets_minimum(problem.severity, policy.min_severity):
return
if not should_notify_problem(problem, db):
return
_dispatch_problem_channels(problem, event, db=db, policy=policy)