from __future__ import annotations from collections import Counter from typing import Any from sqlalchemy import func, select from sqlalchemy.ext.asyncio import AsyncSession from app.core.enums import BGPStatus from app.models.alert import Alert, AlertSeverity, AlertStatus from app.models.bgp_anomaly import BGPAnomaly from app.models.bgp_incident import BGPIncident from app.schemas.ai import SituationalAnalysisRequest from app.ai_tasks.prompts import get_effective_prompt from app.services.bgp_ai_brief_store import get_latest_bgp_brief_record SITUATIONAL_ALERT_BRIEF_PROMPT_KEY = "alerts.situational.brief" def _format_pairs(pairs: list[tuple[str, int]], empty_text: str = "无") -> str: if not pairs: return empty_text return ",".join(f"{key} {value}" for key, value in pairs if key) async def build_situational_alert_brief_request( db: AsyncSession, ) -> tuple[SituationalAnalysisRequest, list[str], dict[str, Any]]: total_alerts_result = await db.execute(select(func.count(Alert.id))) active_alerts_result = await db.execute( select(func.count(Alert.id)).where(Alert.status == AlertStatus.ACTIVE) ) alert_severity_result = await db.execute( select(Alert.severity, func.count(Alert.id)) .where(Alert.status == AlertStatus.ACTIVE) .group_by(Alert.severity) ) alert_source_result = await db.execute( select(Alert.datasource_name, func.count(Alert.id)) .where(Alert.status == AlertStatus.ACTIVE) .group_by(Alert.datasource_name) .order_by(func.count(Alert.id).desc()) .limit(6) ) recent_alerts_result = await db.execute( select(Alert) .order_by(Alert.created_at.desc(), Alert.id.desc()) .limit(6) ) total_incidents_result = await db.execute(select(func.count(BGPIncident.id))) active_incidents_result = await db.execute( select(func.count(BGPIncident.id)).where(BGPIncident.status == BGPStatus.ACTIVE.value) ) bgp_severity_result = await db.execute( select(BGPIncident.severity, func.count(BGPIncident.id)) .where(BGPIncident.status == BGPStatus.ACTIVE.value) .group_by(BGPIncident.severity) ) bgp_region_counter: Counter[str] = Counter() recent_incidents_result = await db.execute( select(BGPIncident) .order_by(BGPIncident.created_at.desc(), BGPIncident.id.desc()) .limit(5) ) total_anomalies_result = await db.execute(select(func.count(BGPAnomaly.id))) active_anomalies_result = await db.execute( select(func.count(BGPAnomaly.id)).where(BGPAnomaly.status == BGPStatus.ACTIVE.value) ) anomaly_type_result = await db.execute( select(BGPAnomaly.anomaly_type, func.count(BGPAnomaly.id)) .where(BGPAnomaly.status == BGPStatus.ACTIVE.value) .group_by(BGPAnomaly.anomaly_type) .order_by(func.count(BGPAnomaly.id).desc()) .limit(6) ) recent_incidents = recent_incidents_result.scalars().all() for incident in recent_incidents: for region in incident.affected_regions or []: if not isinstance(region, dict): continue label = ", ".join(part for part in [region.get("city"), region.get("country")] if part) or "未知区域" bgp_region_counter[label] += 1 latest_bgp_brief = get_latest_bgp_brief_record() active_alert_severities = [ (item[0].value if isinstance(item[0], AlertSeverity) else str(item[0]), item[1]) for item in alert_severity_result.fetchall() if item[0] ] active_bgp_severities = [ (str(item[0]), item[1]) for item in bgp_severity_result.fetchall() if item[0] ] active_anomaly_types = [(str(item[0]), item[1]) for item in anomaly_type_result.fetchall() if item[0]] active_alert_sources = [ (str(item[0] or "未命名数据源"), item[1]) for item in alert_source_result.fetchall() ] total_alerts = total_alerts_result.scalar() or 0 active_alerts = active_alerts_result.scalar() or 0 total_incidents = total_incidents_result.scalar() or 0 active_incidents = active_incidents_result.scalar() or 0 total_anomalies = total_anomalies_result.scalar() or 0 active_anomalies = active_anomalies_result.scalar() or 0 facts = [ ( f"系统告警侧:总告警 {total_alerts} 条,active {active_alerts} 条;" f"活跃告警严重度分布为 {_format_pairs(active_alert_severities)}。" ), ( f"BGP态势侧:累计 incidents {total_incidents} 条,active incidents {active_incidents} 条;" f"活跃 incidents 严重度分布为 {_format_pairs(active_bgp_severities)}。" ), ( f"BGP异常侧:累计 anomalies {total_anomalies} 条,active anomalies {active_anomalies} 条;" f"活跃 anomaly 类型分布为 {_format_pairs(active_anomaly_types)}。" ), ] if active_alert_sources: facts.append(f"当前系统告警主要集中在:{_format_pairs(active_alert_sources)}。") if bgp_region_counter: facts.append(f"BGP近期高风险区域线索:{_format_pairs(bgp_region_counter.most_common(5))}。") recent_alerts = recent_alerts_result.scalars().all() if recent_alerts: facts.append( "最近系统告警摘录:" + ";".join( [ f"{alert.datasource_name or '未命名数据源'} / {alert.severity.value if alert.severity else '-'} / {alert.status.value if alert.status else '-'} / {alert.message or '-'}" for alert in recent_alerts ] ) ) if recent_incidents: facts.append( "最近BGP事件摘录:" + ";".join( [ f"{incident.incident_type} / {incident.severity} / {incident.status} / {incident.summary}" for incident in recent_incidents ] ) ) if latest_bgp_brief: facts.append( f"最近一份 BGP AI 简报生成于 {latest_bgp_brief.generated_at},模型 {latest_bgp_brief.model},可作为当前态势的补充说明。" ) context = { "source": "situational-alerts", "active_system_alerts": active_alerts, "active_system_alert_severities": dict(active_alert_severities), "top_system_alert_sources": dict(active_alert_sources), "active_bgp_incidents": active_incidents, "active_bgp_incident_severities": dict(active_bgp_severities), "active_bgp_anomalies": active_anomalies, "active_bgp_anomaly_types": dict(active_anomaly_types), "bgp_hot_regions": dict(bgp_region_counter.most_common(5)), "latest_bgp_brief_id": latest_bgp_brief.id if latest_bgp_brief else None, "latest_bgp_brief_generated_at": latest_bgp_brief.generated_at if latest_bgp_brief else None, } prompt = await get_effective_prompt(db, SITUATIONAL_ALERT_BRIEF_PROMPT_KEY) request = SituationalAnalysisRequest( title="态势告警 AI 简报", objective=prompt.prompt, system_prompt=prompt.system_prompt or None, observations=facts, constraints=[ "明确区分事实、推断与建议。", "优先指出仍在 active 状态的系统告警与 BGP 风险是否存在联动。", "不要把单一数据源的局部异常夸大成全局态势。", "如果证据不足,请明确写出仍缺哪些模块或区域信息。", ], context=context, ) return request, facts, context