From a9d8fd9c3c9d8620194f62006f8717aa7760ed8c Mon Sep 17 00:00:00 2001 From: OG T Date: Wed, 1 Apr 2026 18:48:04 +0800 Subject: [PATCH] =?UTF-8?q?feat(telegram):=20ADR-050=20P2=20-=20detail/his?= =?UTF-8?q?tory=20info=20actions=20=E5=AF=A6=E4=BD=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - _send_incident_detail: 取得事件詳情 + AI 信心條形圖,傳送新訊息保留原始簽核卡片 - _send_incident_history: 頻率統計 (1h/24h/7d/30d + 自動修復次數) - reanalyze: 保留為開發中 placeholder Co-Authored-By: Claude Sonnet 4.6 --- apps/api/src/services/telegram_gateway.py | 128 +++++++++++++++++++++- 1 file changed, 124 insertions(+), 4 deletions(-) diff --git a/apps/api/src/services/telegram_gateway.py b/apps/api/src/services/telegram_gateway.py index ffce0a96..8e0164fb 100644 --- a/apps/api/src/services/telegram_gateway.py +++ b/apps/api/src/services/telegram_gateway.py @@ -1808,10 +1808,23 @@ class TelegramGateway: if parsed.get("is_info_action"): if not self._security.is_whitelisted(user_id): raise UserNotWhitelistedError(f"User {user_id} not in whitelist") - await self._answer_callback( - callback_query_id, action, - text={"detail": "📋 功能開發中", "reanalyze": "🔄 功能開發中", "history": "📊 功能開發中"}.get(action, "⏳ 功能開發中"), - ) + + incident_id = parsed.get("incident_id", approval_id) + + if action == "detail": + # ADR-050 P2: 取得事件詳情,傳送新訊息 (保留原始簽核卡片+按鈕) + # 2026-04-01 Claude Code (ADR-050 P2) + await self._answer_callback(callback_query_id, action, text="📋 詳情傳送中...") + await self._send_incident_detail(incident_id) + elif action == "history": + # ADR-050 P2: 取得頻率統計 + # 2026-04-01 Claude Code (ADR-050 P2) + await self._answer_callback(callback_query_id, action, text="📊 歷史統計傳送中...") + await self._send_incident_history(incident_id) + else: + # reanalyze: 開發中 + await self._answer_callback(callback_query_id, action, text="🔄 功能開發中") + return { "action": action, "approval_id": approval_id, @@ -2225,6 +2238,113 @@ class TelegramGateway: "disable_web_page_preview": True, }) + async def _send_incident_detail(self, incident_id: str) -> None: + """ + ADR-050 P2: 傳送事件詳情訊息 (不修改原始簽核卡片) + + 2026-04-01 Claude Code (ADR-050 P2): detail button handler + """ + # 延遲 import 避免循環依賴 (與 approval_service 同一模式) + from src.repositories.incident_repository import get_incident_repository + + try: + repo = get_incident_repository() + incident = await repo.get_by_id(incident_id) + + if not incident: + await self.send_notification(f"⚠️ 找不到事件 {html.escape(incident_id)}") + return + + dc = incident.decision_chain + confidence_bar = "█" * int((dc.confidence if dc else 0) * 10) + "░" * (10 - int((dc.confidence if dc else 0) * 10)) + + lines = [ + f"📋 事件詳情", + f"", + f"🔖 ID: {html.escape(incident.incident_id)}", + f"📊 狀態: {incident.status.value}", + f"⚡ 嚴重度: {incident.severity.value}", + ] + + if incident.affected_services: + lines.append(f"🎯 受影響服務: {', '.join(html.escape(s) for s in incident.affected_services[:3])}") + + if dc: + lines += [ + f"", + f"🤖 AI 分析 ({html.escape(dc.model_used)})", + f"💡 {html.escape(dc.hypothesis[:200])}{'...' if len(dc.hypothesis) > 200 else ''}", + f"📈 信心: [{confidence_bar}] {dc.confidence:.0%}", + ] + if dc.probable_root_causes: + lines.append(f"🔍 根因: {html.escape(dc.probable_root_causes[0][:100])}") + + lines += [ + f"", + f"🕐 建立: {incident.created_at.strftime('%m/%d %H:%M')}", + ] + + if incident.frequency_stats: + fs = incident.frequency_stats + lines.append(f"📉 頻率: 1h={fs.count_1h} 24h={fs.count_24h} 7d={fs.count_7d}") + + await self.send_notification("\n".join(lines)) + + except Exception as e: + logger.warning("send_incident_detail_failed", incident_id=incident_id, error=str(e)) + await self.send_notification(f"⚠️ 無法取得事件詳情: {html.escape(str(e)[:100])}") + + async def _send_incident_history(self, incident_id: str) -> None: + """ + ADR-050 P2: 傳送事件頻率統計訊息 + + 2026-04-01 Claude Code (ADR-050 P2): history button handler + """ + from src.repositories.incident_repository import get_incident_repository + + try: + repo = get_incident_repository() + incident = await repo.get_by_id(incident_id) + + if not incident: + await self.send_notification(f"⚠️ 找不到事件 {html.escape(incident_id)}") + return + + fs = incident.frequency_stats + if not fs: + await self.send_notification( + f"📊 事件歷史\n\n🔖 {html.escape(incident.incident_id)}\n\n無頻率統計資料" + ) + return + + lines = [ + f"📊 事件歷史統計", + f"", + f"🔖 {html.escape(incident.incident_id)}", + f"🔑 告警鍵: {html.escape(fs.anomaly_key[:60])}", + f"", + f"⏱ 發生頻率", + f" 1小時: {fs.count_1h} 次", + f" 24小時: {fs.count_24h} 次", + f" 7天: {fs.count_7d} 次", + f" 30天: {fs.count_30d} 次", + ] + + if fs.auto_repair_count > 0: + lines += [ + f"", + f"🔧 自動修復", + f" 執行次數: {fs.auto_repair_count}", + ] + if fs.last_repair_action: + lines.append(f" 最後動作: {html.escape(fs.last_repair_action[:80])}") + + await self.send_notification("\n".join(lines)) + + 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])}") + async def send_notification( self, text: str,