fix(telegram): 歷史按鈕改從 AnomalyCounter(Redis) 讀頻率,修復永遠顯示「無頻率統計資料」
Some checks failed
CD Pipeline / build-and-deploy (push) Failing after 1m45s

根本原因: frequency_stats 從未持久化到 DB,get_by_id() 回傳永遠是 None
修復: 用 AnomalyCounter.derive_key_from_incident() 推導 anomaly_key,
直接從 Redis 查即時頻率與處置分佈統計

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
OG T
2026-04-10 00:56:23 +08:00
parent e2c6ca598e
commit e59f8181b3

View File

@@ -2655,9 +2655,10 @@ class TelegramGateway:
"""
ADR-050 P2: 傳送事件頻率統計訊息
2026-04-01 Claude Code (ADR-050 P2): history button handler
2026-04-10 ogt: 修復 — frequency_stats 不存 DB改從 AnomalyCounter (Redis) 即時查詢
"""
from src.repositories.incident_repository import get_incident_repository
from src.services.anomaly_counter import get_anomaly_counter, AnomalyCounter
try:
repo = get_incident_repository()
@@ -2667,47 +2668,60 @@ class TelegramGateway:
await self.send_notification(f"⚠️ 找不到事件 <code>{html.escape(incident_id)}</code>")
return
fs = incident.frequency_stats
if not fs:
# frequency_stats 不持久化,從 AnomalyCounter (Redis) 即時查詢
anomaly_key = AnomalyCounter.derive_key_from_incident(incident)
if not anomaly_key:
await self.send_notification(
f"📊 <b>事件歷史</b>\n\n🔖 <code>{html.escape(incident.incident_id)}</code>\n\n無頻率統計資料"
f"📊 <b>事件歷史</b>\n\n🔖 <code>{html.escape(incident_id)}</code>\n\n⚠️ 無法推導告警鍵signals 為空)"
)
return
counter = get_anomaly_counter()
freq = await counter.record_anomaly({
"alert_name": incident.signals[0].alert_name if incident.signals else "",
"service": incident.affected_services[0] if incident.affected_services else "",
"namespace": (incident.signals[0].labels or {}).get("namespace", "") if incident.signals else "",
"error_type": (incident.signals[0].labels or {}).get("reason", (incident.signals[0].labels or {}).get("error_type", "")) if incident.signals else "",
})
disposition = await counter.get_disposition_stats(anomaly_key)
lines = [
f"📊 <b>事件歷史統計</b>",
f"",
f"🔖 <code>{html.escape(incident.incident_id)}</code>",
f"🔑 告警鍵: <code>{html.escape(fs.anomaly_key[:60])}</code>",
f"🔖 <code>{html.escape(incident_id)}</code>",
f"🔑 告警鍵: <code>{html.escape(anomaly_key)}</code>",
f"",
f"⏱ <b>發生頻率</b>",
f" 1小時: {fs.count_1h}",
f" 24小時: {fs.count_24h}",
f" 7天: {fs.count_7d}",
f" 30天: {fs.count_30d}",
f" 1小時: {freq.count_1h}",
f" 24小時: {freq.count_24h}",
f" 7天: {freq.count_7d}",
f" 30天: {freq.count_30d}",
]
if fs.auto_repair_count > 0:
if freq.auto_repair_count > 0:
lines += [
f"",
f"🔧 <b>自動修復</b>",
f" 執行次數: {fs.auto_repair_count}",
f" 執行次數: {freq.auto_repair_count}",
]
if fs.last_repair_action:
lines.append(f" 最後動作: {html.escape(fs.last_repair_action[:80])}")
if freq.last_repair_action:
lines.append(f" 最後動作: {html.escape(freq.last_repair_action)}")
# 2026-04-07 Claude Code: Sprint 4 D2 — 處置分佈明細
total_res = fs.total_resolution_count
# 處置分佈
auto_r = disposition.get("auto_repair_count", 0)
cold_s = disposition.get("cold_start_trust_count", 0)
human_a = disposition.get("human_approved_count", 0)
manual_r = disposition.get("manual_resolved_count", 0)
total_res = auto_r + cold_s + human_a + manual_r
if total_res > 0:
auto_total = fs.auto_repair_count + fs.cold_start_trust_count
auto_rate = int(auto_total / total_res * 100) if total_res > 0 else 0
auto_rate = int((auto_r + cold_s) / total_res * 100)
lines += [
f"",
f"📋 <b>處置分佈</b> (共 {total_res} 次)",
f" 🤖 自動修復: {fs.auto_repair_count}",
f" ❄️ 冷啟動信任: {fs.cold_start_trust_count}",
f" 👤 人工審核: {fs.human_approved_count}",
f" 🔧 手動處理: {fs.manual_resolved_count}",
f" 🤖 自動修復: {auto_r}",
f" ❄️ 冷啟動信任: {cold_s}",
f" 👤 人工審核: {human_a}",
f" 🔧 手動處理: {manual_r}",
f" 📈 自動化率: <b>{auto_rate}%</b>",
]
@@ -2715,7 +2729,7 @@ class TelegramGateway:
except Exception as e:
logger.warning("send_incident_history_failed", incident_id=incident_id, error=str(e))
await self.send_notification(f"⚠️ 無法取得歷史統計: {html.escape(str(e)[:100])}")
await self.send_notification(f"⚠️ 無法取得歷史統計: {html.escape(str(e))}")
async def _send_reanalyze_result(self, incident_id: str) -> None:
"""