104 lines
4.3 KiB
Python
104 lines
4.3 KiB
Python
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.models.alert import Alert, AlertSeverity, AlertStatus
|
||
from app.schemas.ai import AlertBriefRequest, SituationalAnalysisRequest
|
||
|
||
|
||
def _format_counter(counter: Counter[str], empty_text: str = "无") -> str:
|
||
if not counter:
|
||
return empty_text
|
||
return ",".join(f"{key} {value}" for key, value in counter.items())
|
||
|
||
|
||
async def build_alert_brief_request(
|
||
db: AsyncSession,
|
||
*,
|
||
alert_limit: int = 8,
|
||
) -> tuple[SituationalAnalysisRequest, list[str], dict[str, Any]]:
|
||
recent_alerts_result = await db.execute(
|
||
select(Alert)
|
||
.order_by(Alert.created_at.desc(), Alert.id.desc())
|
||
.limit(max(alert_limit, 1))
|
||
)
|
||
total_result = await db.execute(select(func.count(Alert.id)))
|
||
active_result = await db.execute(select(func.count(Alert.id)).where(Alert.status == AlertStatus.ACTIVE))
|
||
acknowledged_result = await db.execute(
|
||
select(func.count(Alert.id)).where(Alert.status == AlertStatus.ACKNOWLEDGED)
|
||
)
|
||
resolved_result = await db.execute(select(func.count(Alert.id)).where(Alert.status == AlertStatus.RESOLVED))
|
||
|
||
recent_alerts = recent_alerts_result.scalars().all()
|
||
total_alerts = total_result.scalar() or 0
|
||
active_alerts = active_result.scalar() or 0
|
||
acknowledged_alerts = acknowledged_result.scalar() or 0
|
||
resolved_alerts = resolved_result.scalar() or 0
|
||
|
||
severity_counts = Counter((item.severity.value if item.severity else "unknown") for item in recent_alerts)
|
||
status_counts = Counter((item.status.value if item.status else "unknown") for item in recent_alerts)
|
||
datasource_counts = Counter((item.datasource_name or "未命名数据源") for item in recent_alerts)
|
||
active_datasource_counts = Counter(
|
||
(item.datasource_name or "未命名数据源")
|
||
for item in recent_alerts
|
||
if item.status == AlertStatus.ACTIVE
|
||
)
|
||
|
||
facts = [
|
||
f"告警总量 {total_alerts} 条,其中 active {active_alerts} 条、acknowledged {acknowledged_alerts} 条、resolved {resolved_alerts} 条。",
|
||
f"最近告警严重度分布:{_format_counter(severity_counts)}。",
|
||
f"最近告警状态分布:{_format_counter(status_counts)}。",
|
||
f"最近告警数据源分布:{_format_counter(Counter(dict(datasource_counts.most_common(6))))}。",
|
||
]
|
||
|
||
if active_datasource_counts:
|
||
facts.append(
|
||
"当前待处理告警主要集中在:"
|
||
+ _format_counter(Counter(dict(active_datasource_counts.most_common(5))))
|
||
+ "。"
|
||
)
|
||
|
||
if recent_alerts:
|
||
facts.append(
|
||
"最近告警摘录:"
|
||
+ ";".join(
|
||
[
|
||
f"{item.datasource_name or '未命名数据源'} / {item.severity.value if item.severity else '-'} / {item.status.value if item.status else '-'} / {item.message or '-'}"
|
||
for item in recent_alerts[:6]
|
||
]
|
||
)
|
||
)
|
||
|
||
context = {
|
||
"source": "alerts",
|
||
"total_alerts": total_alerts,
|
||
"active_alerts": active_alerts,
|
||
"acknowledged_alerts": acknowledged_alerts,
|
||
"resolved_alerts": resolved_alerts,
|
||
"severity_distribution": dict(severity_counts),
|
||
"status_distribution": dict(status_counts),
|
||
"top_datasources": dict(datasource_counts.most_common(6)),
|
||
"top_active_datasources": dict(active_datasource_counts.most_common(5)),
|
||
}
|
||
|
||
return (
|
||
SituationalAnalysisRequest(
|
||
title="告警态势 AI 简报",
|
||
objective="基于当前告警总量、严重度、状态、数据源分布与最近告警摘录,生成一份面向值班人员的简明告警态势简报,突出待处理风险、告警集中点和优先动作。",
|
||
observations=facts,
|
||
constraints=[
|
||
"明确区分事实、推断与建议。",
|
||
"优先指出仍处于 active 状态且高严重度的告警簇。",
|
||
"不要把 acknowledged 或 resolved 告警误判成当前仍在扩大。",
|
||
"如果证据不足,请明确指出缺失的上下文。",
|
||
],
|
||
context=context,
|
||
),
|
||
facts,
|
||
context,
|
||
)
|