diff --git a/apps/web/src/app/[locale]/page.tsx b/apps/web/src/app/[locale]/page.tsx
index e7470142..43a92bc0 100644
--- a/apps/web/src/app/[locale]/page.tsx
+++ b/apps/web/src/app/[locale]/page.tsx
@@ -32,6 +32,7 @@ import { DispositionMini } from '@/components/shared/disposition-mini'
import { RecentActivity } from '@/components/shared/recent-activity'
import { PendingApprovalsCard } from '@/components/shared/pending-approvals-card'
import { AIModelStatus } from '@/components/shared/ai-model-status'
+import { FlywheelKPICard } from '@/components/dashboard/flywheel-kpi-card'
const API_BASE = process.env.NEXT_PUBLIC_API_URL ?? ''
@@ -924,6 +925,9 @@ export default function Home({ params }: { params: { locale: string } }) {
{/* 待審批任務 (S7) */}
+ {/* 飛輪健康度 KPI (ADR-073-C C2) */}
+
+
{/* 基礎架構 — Toggle: 拓撲圖 / 主機網格 */}
(null)
+ const [error, setError] = useState(false)
+
+ useEffect(() => {
+ let cancelled = false
+
+ const load = () => {
+ fetch(`${API_BASE}/api/v1/stats/summary`)
+ .then(r => r.ok ? r.json() : Promise.reject(r.status))
+ .then(d => { if (!cancelled) { setData(d); setError(false) } })
+ .catch(() => { if (!cancelled) setError(true) })
+ }
+
+ load()
+ const id = setInterval(load, 30_000)
+ return () => { cancelled = true; clearInterval(id) }
+ }, [])
+
+ const fmt = (n: number | undefined, digits = 0) =>
+ n == null ? '--' : n.toLocaleString(undefined, { maximumFractionDigits: digits })
+
+ const pct = (n: number | undefined) =>
+ n == null ? '--' : `${Math.round(n * 100)}%`
+
+ const kpis = [
+ {
+ label: 'Playbooks',
+ value: fmt(data?.playbook_count),
+ color: data?.playbook_count != null && data.playbook_count >= 20 ? '#22C55E' : '#d97757',
+ hint: '目標 ≥ 20',
+ },
+ {
+ label: '修復成功率',
+ value: pct(data?.execution_success_rate),
+ color: data?.execution_success_rate != null && data.execution_success_rate >= 0.3 ? '#22C55E' : '#F59E0B',
+ hint: '目標 ≥ 30%',
+ },
+ {
+ label: '今日轉化',
+ value: fmt(data?.flywheel_conversions_today),
+ color: '#4A90D9',
+ hint: '今日新增 KM',
+ },
+ {
+ label: 'KM 向量化率',
+ value: pct(data?.km_vectorized_rate),
+ color: data?.km_vectorized_rate != null && data.km_vectorized_rate >= 0.95 ? '#22C55E' : '#F59E0B',
+ hint: '目標 ≥ 95%',
+ },
+ ]
+
+ return (
+
+ {/* Header */}
+
+
+ 飛輪健康度
+ {error && (
+
API 離線
+ )}
+ {data && !error && (
+
+ {new Date(data.computed_at).toLocaleTimeString('zh-TW', { hour: '2-digit', minute: '2-digit' })}
+
+ )}
+
+
+ {/* KPI Grid */}
+
+ {kpis.map(({ label, value, color, hint }) => (
+
+
+ {label}
+
+
{value}
+
{hint}
+
+ ))}
+
+
+ {/* Stuck incidents warning */}
+ {data?.incidents_stuck != null && data.incidents_stuck > 0 && (
+
+ {data.incidents_stuck} 筆 Incident 卡在 INVESTIGATING {'>'} 24h
+
+ )}
+
+ )
+}