feat: Sprint 4 Phase E+F — 前端處置統計 + 週報處置分佈
Some checks failed
CD Pipeline / build-and-deploy (push) Failing after 1m26s
Type Sync Check / check-type-sync (push) Failing after 1m2s

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 <noreply@anthropic.com>
This commit is contained in:
OG T
2026-04-07 13:02:20 +08:00
parent 37bddbb430
commit de3935d1d4
3 changed files with 59 additions and 3 deletions

View File

@@ -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 APIfallback 到 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)