"""Deterministic high-risk Intelligence Center notification fan-out.""" from __future__ import annotations from decimal import Decimal, InvalidOperation from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.intelligence.models import IntelligenceSnapshot from app.notifications.models import NotificationOutbox from app.notifications.service import NotificationEnvelope, NotificationService _CRITICAL_SCORE = Decimal("60") _MIN_CONFIDENCE = Decimal("70") _DOMAIN_LABELS = { "people_org": ("人员与组织", "People & organization", "人材と組織"), "lifecycle": ("入转离生命周期", "Lifecycle", "入退社ライフサイクル"), "assets": ("资产健康", "Asset health", "資産健全性"), "software_api": ("软件、席位与 API", "Software, seats & API", "ソフトウェア・席・API"), "cloud_network": ("云与网络", "Cloud & network", "クラウドとネットワーク"), "cost_efficiency": ("成本与效率", "Cost & efficiency", "コストと効率"), "security_compliance": ("安全与合规", "Security & compliance", "セキュリティとコンプライアンス"), "platform_ops": ("平台运行", "Platform operations", "プラットフォーム運用"), } def critical_health_dimensions(dimensions: list[dict[str, object]]) -> list[str]: """Return only high-risk dimensions with enough deterministic evidence.""" result: list[str] = [] for dimension in dimensions: key = str(dimension.get("key") or "") if key not in _DOMAIN_LABELS: continue try: score = Decimal(str(dimension.get("score"))) confidence = Decimal(str(dimension.get("confidence"))) except (InvalidOperation, ValueError): continue if score < _CRITICAL_SCORE and confidence >= _MIN_CONFIDENCE: result.append(key) return result def _labels(keys: list[str], locale_index: int) -> str: return "、".join(_DOMAIN_LABELS[key][locale_index] for key in keys) async def notify_critical_health_snapshot( db: AsyncSession, snapshot: IntelligenceSnapshot, ) -> int: """Queue one deduplicated admin digest for a new critical snapshot. The predicate is based solely on the versioned score rows. No model narrative, recommendation priority, or model-generated severity can make this notification fire. A unique immutable snapshot is the alert event; repeated daily builds with unchanged facts do not produce another digest. """ keys = critical_health_dimensions(snapshot.dimensions) if not keys: return 0 # Serialise fan-out on the immutable snapshot. Two scheduler workers may # both reach a newly-created snapshot after a lease recovery; locking the # parent before the existing-row query makes the per-recipient dedupe a # real transaction boundary instead of a best-effort read-then-write. await db.execute( select(IntelligenceSnapshot.id) .where(IntelligenceSnapshot.id == snapshot.id) .with_for_update(), ) from app.scheduled_jobs.failure_notifier import list_admin_recipients recipients = await list_admin_recipients(db) if not recipients: return 0 existing = set((await db.execute( select(NotificationOutbox.recipient_person_id).where( NotificationOutbox.related_resource_type == "intelligence.critical_health", NotificationOutbox.related_resource_id == snapshot.id, NotificationOutbox.status != "dead", ) )).scalars().all()) context = { "critical_count": len(keys), "critical_domains_zh": _labels(keys, 0), "critical_domains_en": _labels(keys, 1), "critical_domains_ja": _labels(keys, 2), } service = NotificationService(db) queued = 0 for recipient in recipients: if recipient.id in existing: continue await service.enqueue(NotificationEnvelope( recipient=recipient, title=f"[AI 大脑] {context['critical_count']} 个高风险领域需要复核", body_text="(see template)", channels=["feishu", "email"], template_key="intelligence.critical_health", url="/admin/intelligence", related_resource_type="intelligence.critical_health", related_resource_id=snapshot.id, context=context, )) queued += 1 if queued: await db.commit() return queued __all__ = ["critical_health_dimensions", "notify_critical_health_snapshot"]