from __future__ import annotations from collections import Counter from sqlalchemy import func, select from sqlalchemy.ext.asyncio import AsyncSession from app.api.v1.bgp import BGP_SOURCES from app.models.bgp_anomaly import BGPAnomaly from app.models.bgp_incident import BGPIncident from app.models.bgp_observation import BGPObservation from app.schemas.ai import SituationalAnalysisRequest from app.services.bgp_collectors import build_bgp_collector_coverage def _format_counter(counter: dict[str, int], empty_text: str = "无") -> str: if not counter: return empty_text return ",".join(f"{key} {value}" for key, value in counter.items()) def _severity_rank(value: str | None) -> int: order = { "critical": 0, "high": 1, "medium": 2, "low": 3, "info": 4, } return order.get((value or "").lower(), 99) async def build_bgp_brief_request( db: AsyncSession, *, incident_limit: int = 5, anomaly_limit: int = 6, collector_limit: int = 5, ) -> SituationalAnalysisRequest: incidents_result = await db.execute( select(BGPIncident) .order_by(BGPIncident.created_at.desc(), BGPIncident.id.desc()) .limit(max(incident_limit, 1)) ) anomalies_result = await db.execute( select(BGPAnomaly) .order_by(BGPAnomaly.created_at.desc(), BGPAnomaly.id.desc()) .limit(max(anomaly_limit, 1)) ) observations_result = await db.execute( select(BGPObservation).where(BGPObservation.source.in_(BGP_SOURCES)) ) incident_count_result = await db.execute(select(func.count(BGPIncident.id))) anomaly_count_result = await db.execute(select(func.count(BGPAnomaly.id))) incidents = incidents_result.scalars().all() anomalies = anomalies_result.scalars().all() observations = observations_result.scalars().all() collectors = await build_bgp_collector_coverage(db, source_filter=BGP_SOURCES) total_incidents = incident_count_result.scalar() or 0 total_anomalies = anomaly_count_result.scalar() or 0 total_observations = len(observations) active_collectors = [item for item in collectors if item["observation_count"] > 0] incident_status_counts = Counter((item.status or "unknown") for item in incidents) incident_severity_counts = Counter((item.severity or "unknown") for item in incidents) incident_type_counts = Counter((item.incident_type or "unknown") for item in incidents) anomaly_type_counts = Counter((item.anomaly_type or "unknown") for item in anomalies) event_type_counts = Counter((item.event_type or "unknown") for item in observations) top_collectors = sorted( active_collectors, key=lambda item: ( -int(item["recent_24h_observation_count"]), -int(item["observation_count"]), str(item["collector"]), ), )[: max(collector_limit, 1)] observations_lines: list[str] = [ f"当前共有 {total_incidents} 起 BGP incidents、{total_anomalies} 条 anomalies、{total_observations} 条原始观测事件。", f"活跃观测站 {len(active_collectors)} 个;近 24 小时事件数合计 {sum(int(item['recent_24h_observation_count']) for item in active_collectors)}。", f"最近 incidents 严重度分布:{_format_counter(dict(sorted(incident_severity_counts.items(), key=lambda item: _severity_rank(item[0]))))}。", f"最近 incidents 状态分布:{_format_counter(dict(incident_status_counts))}。", f"最近 incidents 类型分布:{_format_counter(dict(incident_type_counts.most_common(5)))}。", f"最近 anomalies 类型分布:{_format_counter(dict(anomaly_type_counts.most_common(6)))}。", f"观测事件类型分布:{_format_counter(dict(event_type_counts.most_common(6)))}。", ] if incidents: observations_lines.append( "最近 incident 摘要:" + ";".join( [ f"{item.incident_type} / {item.severity} / {item.status}" f" / 前缀 {', '.join(item.affected_prefixes[:2]) if item.affected_prefixes else '-'}" f" / 观测站 {len(item.affected_collectors or [])} 个" for item in incidents ] ) ) if anomalies: observations_lines.append( "最近 anomaly 摘要:" + ";".join( [ f"{item.anomaly_type} / {item.severity}" f" / 前缀 {item.prefix or '-'}" f" / ASN {item.new_origin_asn or item.origin_asn or '-'}" for item in anomalies ] ) ) if top_collectors: observations_lines.append( "重点观测站:" + ";".join( [ f"{item['collector']} ({', '.join([part for part in [item.get('city'), item.get('country')] if part]) or '未知位置'})" f" / 近24h {item['recent_24h_observation_count']} 条" f" / 前缀 {item['prefix_count']} 个" for item in top_collectors ] ) ) return SituationalAnalysisRequest( title="BGP 态势 AI 简报", objective="基于当前 BGP incidents、anomalies、原始观测事件与观测站覆盖情况,生成一份面向操作员的简明态势简报,突出当前风险、证据和优先动作。", observations=observations_lines, constraints=[ "明确区分事实、推断与建议。", "优先指出需要立即关注的高严重度 incident 或异常模式。", "结论应服务值班排障,不要写成泛泛的模型演示文案。", "如果证据不足,要明确指出缺失数据。", ], context={ "source": "bgp-overview", "incident_total": total_incidents, "anomaly_total": total_anomalies, "observation_total": total_observations, "active_collectors": len(active_collectors), "top_incident_types": dict(incident_type_counts.most_common(5)), "top_anomaly_types": dict(anomaly_type_counts.most_common(6)), "top_event_types": dict(event_type_counts.most_common(6)), }, )