From de3935d1d41a07f6f3f101ae6e41316c2cd3eb2d Mon Sep 17 00:00:00 2001 From: OG T Date: Tue, 7 Apr 2026 13:02:20 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20Sprint=204=20Phase=20E+F=20=E2=80=94=20?= =?UTF-8?q?=E5=89=8D=E7=AB=AF=E8=99=95=E7=BD=AE=E7=B5=B1=E8=A8=88=20+=20?= =?UTF-8?q?=E9=80=B1=E5=A0=B1=E8=99=95=E7=BD=AE=E5=88=86=E4=BD=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase E: 前端頁面 - E1: /reports 完整處置統計儀表板 (已在 Sprint F 完成) - E2: 首頁 Metrics Strip — 從 disposition API 取得真實自動化率 優先使用 /stats/disposition auto_rate,fallback 到 incidents 推算 - E3: /auto-repair 處置概況卡片 (已在 Sprint F 完成) - E4: /neural-command stats tab 處置分佈 (已在 Sprint F 完成) - E5: i18n 翻譯 zh-TW + en (已在 Sprint F 完成) Phase F: 週報 + 文件 - F1: WeeklyReportMessage 新增 disposition 5 欄位 週報格式加「📋 處置分佈」區塊 (自動/冷啟動/人工/手動 + 自動化率) weekly_report_service 整合 get_all_disposition_stats() - message 字數上限從 900 提升到 1200 (適應處置區塊) Co-Authored-By: Claude Haiku 4.5 --- apps/api/src/services/telegram_gateway.py | 24 +++++++++++++++++-- .../api/src/services/weekly_report_service.py | 19 +++++++++++++++ apps/web/src/app/[locale]/page.tsx | 19 ++++++++++++++- 3 files changed, 59 insertions(+), 3 deletions(-) diff --git a/apps/api/src/services/telegram_gateway.py b/apps/api/src/services/telegram_gateway.py index 5b73575a..95c5cfeb 100644 --- a/apps/api/src/services/telegram_gateway.py +++ b/apps/api/src/services/telegram_gateway.py @@ -978,6 +978,12 @@ class WeeklyReportMessage: # 成本 ai_cost_week: float = 0.0 ai_tokens_week: int = 0 + # 2026-04-07 Claude Code: Sprint 4 F1 — 處置分佈 + disposition_auto: int = 0 + disposition_human: int = 0 + disposition_manual: int = 0 + disposition_cold_start: int = 0 + disposition_total: int = 0 def format(self) -> str: """格式化為 Telegram HTML""" @@ -1015,10 +1021,24 @@ class WeeklyReportMessage: f"━━━━━━━━━━━━━━━━━━━\n" f"💰 AI 成本\n" f"├ 費用: ${self.ai_cost_week:.2f}\n" - f"└ Tokens: {self.ai_tokens_week:,}" + f"└ Tokens: {self.ai_tokens_week:,}\n" ) - return message[:900] + # Sprint 4 F1: 處置分佈(有資料才加) + if self.disposition_total > 0: + auto_total = self.disposition_auto + self.disposition_cold_start + auto_rate = int(auto_total / self.disposition_total * 100) if self.disposition_total > 0 else 0 + message += ( + f"━━━━━━━━━━━━━━━━━━━\n" + f"📋 處置分佈\n" + f"├ 🤖 自動修復: {self.disposition_auto}\n" + f"├ ❄️ 冷啟動信任: {self.disposition_cold_start}\n" + f"├ 👤 人工審核: {self.disposition_human}\n" + f"├ 🔧 手動處理: {self.disposition_manual}\n" + f"└ 自動化率: {auto_rate}%" + ) + + return message[:1200] @dataclass diff --git a/apps/api/src/services/weekly_report_service.py b/apps/api/src/services/weekly_report_service.py index 57eefd78..d9d59ccb 100644 --- a/apps/api/src/services/weekly_report_service.py +++ b/apps/api/src/services/weekly_report_service.py @@ -178,6 +178,20 @@ class WeeklyReportService: pod_restarts = k3s_status.pod_restart_48h if k3s_status else 0 hpa_events = 0 # 需要從 Prometheus 取得 HPA 事件 + # 2026-04-07 Claude Code: Sprint 4 F1 — 取得處置分佈 + disp_auto = disp_human = disp_manual = disp_cold = disp_total = 0 + try: + from src.services.anomaly_counter import get_anomaly_counter + counter = get_anomaly_counter() + disp_summary, _ = await counter.get_all_disposition_stats() + disp_auto = disp_summary.get("auto_repair", 0) + disp_human = disp_summary.get("human_approved", 0) + disp_manual = disp_summary.get("manual_resolved", 0) + disp_cold = disp_summary.get("cold_start_trust", 0) + disp_total = disp_summary.get("total", 0) + except Exception as _disp_e: + logger.warning("weekly_report_disposition_failed", error=str(_disp_e)) + # 組裝週報 report = WeeklyReportMessage( week_range=week_range, @@ -197,6 +211,11 @@ class WeeklyReportService: deploy_count=deploys, ai_cost_week=0.0, # 需要從 AI 成本追蹤取得 ai_tokens_week=0, # 需要從 AI 成本追蹤取得 + disposition_auto=disp_auto, + disposition_human=disp_human, + disposition_manual=disp_manual, + disposition_cold_start=disp_cold, + disposition_total=disp_total, ) logger.info( diff --git a/apps/web/src/app/[locale]/page.tsx b/apps/web/src/app/[locale]/page.tsx index f0a8dc86..20b0dcb4 100644 --- a/apps/web/src/app/[locale]/page.tsx +++ b/apps/web/src/app/[locale]/page.tsx @@ -365,8 +365,22 @@ export default function Home({ params }: { params: { locale: string } }) { // P0 count const p0Count = incidents?.filter(i => i.severity === 'P0').length ?? 0 - // 自動處置率 + // 2026-04-07 Claude Code: Sprint 4 E2 — 從 disposition API 取得真實自動化率 + const [dispositionRate, setDispositionRate] = useState<{ auto_rate: number; total: number } | null>(null) + useEffect(() => { + fetch(`${API_BASE}/api/v1/stats/disposition`) + .then(r => r.json()) + .then(d => { + if (d?.summary) setDispositionRate({ auto_rate: d.summary.auto_rate, total: d.summary.total }) + }) + .catch(() => {}) + }, []) + + // 自動處置率 — 優先使用 disposition API,fallback 到 incidents 推算 const autoRemediationRate = (() => { + if (dispositionRate && dispositionRate.total > 0) { + return `${Math.round(dispositionRate.auto_rate * 100)}%` + } if (!incidents?.length) return '--' const resolved = incidents.filter(i => i.status === 'resolved' || i.status === 'closed').length return `${((resolved / incidents.length) * 100).toFixed(0)}%` @@ -374,6 +388,9 @@ export default function Home({ params }: { params: { locale: string } }) { // 自動處置率數值 (for progress bar) const autoRemediationPct = (() => { + if (dispositionRate && dispositionRate.total > 0) { + return Math.round(dispositionRate.auto_rate * 100) + } if (!incidents?.length) return 0 const resolved = incidents.filter(i => i.status === 'resolved' || i.status === 'closed').length return Math.round((resolved / incidents.length) * 100)