fix(web): align homepage automation truth metrics
All checks were successful
CD Pipeline / tests (push) Successful in 1m18s
Code Review / ai-code-review (push) Successful in 13s
CD Pipeline / build-and-deploy (push) Successful in 3m26s
CD Pipeline / post-deploy-checks (push) Successful in 1m20s

This commit is contained in:
Your Name
2026-05-25 15:30:00 +08:00
parent d6d7c27152
commit ffe479dbcc
4 changed files with 164 additions and 14 deletions

View File

@@ -142,12 +142,21 @@
"execution": "Execution",
"resolved": "Resolved"
},
"unresolvedIncidents": "Unresolved Incidents",
"activeIncidents": "Active Incidents",
"serviceHealth": "Service Health",
"todayIncidents": "Today Incidents",
"operations24h": "24h Operations",
"operationsTotal": "{total} total",
"autoRemediationRate": "Auto Remediation",
"autoRepairVerified24h": "24h Verified Repair",
"autoRepairVerifiedCount": "verified {verified}/{evaluated}",
"autoRepairAllTime": "history {pct}% / {total}",
"latestIncidentWindow": "latest {shown} shown",
"truthChainCoverage": "truth-chain {loaded}/{shown}",
"truthChainLoading": "truth-chain loading",
"severityBreakdown": "P1:{p1} P2:{p2}",
"stableUnresolved": "{stable} · 0 {label}",
"mttrAvg": "MTTR Avg",
"stable": "Stable",
"normal": "Normal",

View File

@@ -143,12 +143,21 @@
"execution": "執行",
"resolved": "完成"
},
"unresolvedIncidents": "未解事件",
"activeIncidents": "活躍事件",
"serviceHealth": "服務健康",
"todayIncidents": "今日事件",
"operations24h": "近 24h 操作",
"operationsTotal": "總計 {total}",
"autoRemediationRate": "自動處置率",
"autoRepairVerified24h": "24h 驗證修復率",
"autoRepairVerifiedCount": "已驗證 {verified}/{evaluated}",
"autoRepairAllTime": "歷史 {pct}% / {total} 筆",
"latestIncidentWindow": "首屏最新 {shown} 筆",
"truthChainCoverage": "truth-chain {loaded}/{shown}",
"truthChainLoading": "truth-chain 讀取中",
"severityBreakdown": "P1:{p1} P2:{p2}",
"stableUnresolved": "{stable} · 0 {label}",
"mttrAvg": "MTTR 均值",
"stable": "穩定",
"normal": "正常",

View File

@@ -36,6 +36,14 @@ const API_BASE = process.env.NEXT_PUBLIC_API_URL ?? ''
const STATUS_CHAIN_PREFETCH_LIMIT = 25
const HOMEPAGE_INCIDENT_LIMIT = STATUS_CHAIN_PREFETCH_LIMIT
interface HomepageAutomationQualitySummary {
evaluated_total?: number
verified_auto_repair_total?: number
production_claim?: {
can_claim_full_auto_repair?: boolean
}
}
// =============================================================================
// Tab 2: 告警 & 授權 (串接真實 API)
// =============================================================================
@@ -512,7 +520,11 @@ export default function Home({ params }: { params: { locale: string } }) {
pollInterval: 15000,
enablePolling: true,
})
const { statusChains } = useIncidentStatusChains({
const {
statusChains,
requestedIncidentIds,
isLoading: isStatusChainsLoading,
} = useIncidentStatusChains({
incidentIds: incidents?.map(incident => incident.incident_id) ?? [],
limit: STATUS_CHAIN_PREFETCH_LIMIT,
refreshKey: incidentsLastUpdated?.toISOString() ?? null,
@@ -534,6 +546,7 @@ export default function Home({ params }: { params: { locale: string } }) {
// 2026-04-07 Claude Code: Sprint 4 E2 — 從 disposition API 取得真實自動化率
const [dispositionRate, setDispositionRate] = useState<{ auto_rate: number; total: number } | null>(null)
const [automationQuality, setAutomationQuality] = useState<HomepageAutomationQualitySummary | null>(null)
useEffect(() => {
fetch(`${API_BASE}/api/v1/stats/disposition`)
.then(r => r.json())
@@ -542,22 +555,57 @@ export default function Home({ params }: { params: { locale: string } }) {
})
.catch(() => {})
}, [])
useEffect(() => {
const controller = new AbortController()
fetch(`${API_BASE}/api/v1/platform/truth-chain/quality/summary?project_id=awoooi&hours=24&limit=30`, {
signal: controller.signal,
})
.then(r => r.ok ? r.json() : null)
.then(d => {
if (!d) return
setAutomationQuality({
evaluated_total: typeof d.evaluated_total === 'number' ? d.evaluated_total : undefined,
verified_auto_repair_total: typeof d.verified_auto_repair_total === 'number' ? d.verified_auto_repair_total : undefined,
production_claim: d.production_claim,
})
})
.catch(() => {})
return () => controller.abort()
}, [])
// 自動處置率 — 優先使用 disposition APIfallback 到 incidents 推算
// 自動處置率 — 首頁 KPI 使用 24h truth-chain 驗證率,避免把歷史 disposition 總表誤讀成今日閉環。
const evaluatedAutomationTotal = automationQuality?.evaluated_total ?? 0
const verifiedAutomationTotal = automationQuality?.verified_auto_repair_total ?? 0
const autoRemediationRate = (() => {
if (dispositionRate && dispositionRate.total > 0) {
return `${Math.round(dispositionRate.auto_rate * 100)}%`
if (evaluatedAutomationTotal > 0) {
return `${Math.round((verifiedAutomationTotal / evaluatedAutomationTotal) * 100)}%`
}
return '--'
})()
// 自動處置率數值 (for progress bar)
const autoRemediationPct = (() => {
if (dispositionRate && dispositionRate.total > 0) {
return Math.round(dispositionRate.auto_rate * 100)
if (evaluatedAutomationTotal > 0) {
return Math.round((verifiedAutomationTotal / evaluatedAutomationTotal) * 100)
}
return 0
})()
const autoRemediationTone = automationQuality?.production_claim?.can_claim_full_auto_repair
? '#22C55E'
: evaluatedAutomationTotal > 0
? '#F59E0B'
: '#141413'
const autoRemediationDetail = evaluatedAutomationTotal > 0
? tDashboard('autoRepairVerifiedCount', {
verified: verifiedAutomationTotal,
evaluated: evaluatedAutomationTotal,
})
: dispositionRate && dispositionRate.total > 0
? tDashboard('autoRepairAllTime', {
pct: Math.round(dispositionRate.auto_rate * 100),
total: dispositionRate.total.toLocaleString(),
})
: null
// ── 5 KPI Cards (Sprint 5R 設計稿批准版) ────────────────────────────────────
@@ -567,6 +615,16 @@ export default function Home({ params }: { params: { locale: string } }) {
const p2Count = incidents?.filter(i => i.severity === 'P2').length ?? 0
const visibleIncidents = incidents?.slice(0, HOMEPAGE_INCIDENT_LIMIT) ?? []
const hiddenIncidentCount = Math.max(incidentCount - visibleIncidents.length, 0)
const truthChainCoveredCount = requestedIncidentIds.filter((incidentId) => {
const chain = statusChains[incidentId]
return Boolean(chain && !chain.fetch_error && chain.source_id)
}).length
const truthChainCoverageLabel = isStatusChainsLoading
? tDashboard('truthChainLoading')
: tDashboard('truthChainCoverage', {
loaded: truthChainCoveredCount,
shown: requestedIncidentIds.length,
})
const liveTopologyGroups = Object.values(
hosts.reduce<Record<string, {
role: string
@@ -676,21 +734,30 @@ export default function Home({ params }: { params: { locale: string } }) {
</div>
{/* 活動事件 */}
<div style={{ flex: 1, background: '#fff', border: '0.5px solid #e0ddd4', borderRadius: 8, padding: '8px 12px' }}>
<div style={{ fontSize: 10, textTransform: 'uppercase', letterSpacing: '0.5px', color: '#87867f', fontWeight: 500 }}>{tDashboard('activeIncidents')}</div>
<div style={{ fontSize: 10, textTransform: 'uppercase', letterSpacing: '0.5px', color: '#87867f', fontWeight: 500 }}>{tDashboard('unresolvedIncidents')}</div>
<div style={{ display: 'flex', alignItems: 'baseline', gap: 6, marginTop: 2 }}>
<span style={{ fontSize: 22, fontWeight: 700, color: incidentCount > 0 ? '#d97757' : '#141413' }}>{incidentCount || '--'}</span>
{incidentCount > 0 && <span style={{ fontSize: 9, color: '#87867f' }}>P1:{p1Count} P2:{p2Count}</span>}
{incidentCount > 0 && (
<span style={{ fontSize: 9, color: '#87867f' }}>
{tDashboard('severityBreakdown', { p1: p1Count, p2: p2Count })}
</span>
)}
</div>
{visibleIncidents.length > 0 && (
<div style={{ marginTop: 4, fontSize: 9, color: '#87867f' }}>
{tDashboard('latestIncidentWindow', { shown: visibleIncidents.length })}
</div>
)}
</div>
{/* 自動修復率 */}
<div style={{ flex: 1, background: '#fff', border: '0.5px solid #e0ddd4', borderRadius: 8, padding: '8px 12px' }}>
<div style={{ fontSize: 10, textTransform: 'uppercase', letterSpacing: '0.5px', color: '#87867f', fontWeight: 500 }}>{tDashboard('autoRemediationRate')}</div>
<div style={{ fontSize: 10, textTransform: 'uppercase', letterSpacing: '0.5px', color: '#87867f', fontWeight: 500 }}>{tDashboard('autoRepairVerified24h')}</div>
<div style={{ display: 'flex', alignItems: 'baseline', gap: 6, marginTop: 2 }}>
<span style={{ fontSize: 22, fontWeight: 700, color: '#22C55E' }}>{autoRemediationRate}</span>
{autoRemediationPct > 0 && <span style={{ fontSize: 10, fontWeight: 700, color: '#22C55E' }}>{tDashboard('trendUp', { pct: autoRemediationPct })}</span>}
<span style={{ fontSize: 22, fontWeight: 700, color: autoRemediationTone }}>{autoRemediationRate}</span>
{autoRemediationDetail && <span style={{ fontSize: 10, fontWeight: 700, color: autoRemediationTone }}>{autoRemediationDetail}</span>}
</div>
<div style={{ height: 3, borderRadius: 2, background: '#ebe8df', marginTop: 4, overflow: 'hidden' }}>
<div style={{ width: `${autoRemediationPct}%`, height: '100%', borderRadius: 2, background: 'linear-gradient(90deg,#22C55E,#4ade80)' }} />
<div style={{ width: `${autoRemediationPct}%`, height: '100%', borderRadius: 2, background: autoRemediationTone }} />
</div>
</div>
{/* 待審批 */}
@@ -738,7 +805,7 @@ export default function Home({ params }: { params: { locale: string } }) {
}}>
<div style={{ width: 6, height: 6, borderRadius: '50%', background: '#d97757', flexShrink: 0 }} />
<span style={{ fontSize: 14, fontWeight: 700, color: '#141413', letterSpacing: '0.5px' }}>
{tDashboard('activeIncidents')}
{tDashboard('unresolvedIncidents')}
</span>
{(incidents?.length ?? 0) > 0 && (
<span style={{
@@ -749,6 +816,19 @@ export default function Home({ params }: { params: { locale: string } }) {
{incidents?.length}
</span>
)}
{requestedIncidentIds.length > 0 && (
<span style={{
fontSize: 11,
color: truthChainCoveredCount === requestedIncidentIds.length && !isStatusChainsLoading ? '#17602a' : '#8a5a08',
background: truthChainCoveredCount === requestedIncidentIds.length && !isStatusChainsLoading ? '#f0faf2' : '#fff7e8',
border: '0.5px solid #e0ddd4',
padding: '2px 7px',
borderRadius: 10,
fontWeight: 700,
}}>
{truthChainCoverageLabel}
</span>
)}
<a
href={`/${locale}/alerts`}
style={{ marginLeft: 'auto', fontSize: 11, color: '#4A90D9', cursor: 'pointer', fontWeight: 500, textDecoration: 'none' }}
@@ -765,7 +845,12 @@ export default function Home({ params }: { params: { locale: string } }) {
) : (incidents?.length ?? 0) === 0 ? (
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', padding: 48, gap: 8 }}>
<div style={{ width: 8, height: 8, borderRadius: '50%', background: '#22C55E' }} />
<span style={{ fontSize: 13, color: '#87867f' }}>{tDashboard('stable')} · 0 {tDashboard('activeIncidents')}</span>
<span style={{ fontSize: 13, color: '#87867f' }}>
{tDashboard('stableUnresolved', {
stable: tDashboard('stable'),
label: tDashboard('unresolvedIncidents'),
})}
</span>
</div>
) : (
<div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>

View File

@@ -19956,3 +19956,50 @@ format_governance_alert_card("knowledge_degradation", legacy payload)
- CI/CD runner hygiene約 96%。
- 治理告警可讀性 / 可處置性:約 90%。
- 完整 AI 自動化管理產品化:約 89.6%。
---
## 2026-05-25 T180 — 首頁 KPI / 小龍蝦流程條對齊 truth-chain
**背景**
- 首頁上方 KPI 顯示「自動處置率 35%」,來源是 `/api/v1/stats/disposition` 的歷史累計總表;但 AwoooP Automation Evidence / truth-chain quality 近 24h 顯示 `verified_auto_repair_total=0 / evaluated_total=25`,容易讓 operator 誤解成目前 35% 事件已完成 AI 自動修復。
- 「活躍事件」實際取自 `/api/v1/incidents` 未解事件列表,語意應改成「未解事件」,避免和真正 running / active stage 混淆。
- 首頁事件流程條本身已能吃 status-chain truth source缺口是畫面沒有明確顯示這批首屏事件的 truth-chain coverage使用者無法看出流程條是 DB/truth-chain 還是 heuristic fallback。
**本輪修正**
- 首頁自動修復 KPI 改用 `/api/v1/platform/truth-chain/quality/summary?project_id=awoooi&hours=24&limit=30` 作為主要資料源。
- KPI 顯示「24h 驗證修復率」與 `已驗證 {verified}/{evaluated}`,當 `production_claim.can_claim_full_auto_repair=false` 時用 amber 呈現,不再用綠色成功口吻包裝未驗證自動修復。
- `/api/v1/stats/disposition` 只保留為 truth-chain quality 尚未載入時的歷史 fallback 文案,不再作為首頁主判斷。
- 首頁事件區塊改為「未解事件」,並新增 `truth-chain {loaded}/{shown}` 覆蓋率 chip讓 operator 直接看到小龍蝦流程條是否已對到 status-chain evidence。
- 新增 zh-TW / en i18n key未新增 mock data、未新增內網 IP、未新增新 API contract。
**local validation完成**
```text
jq empty apps/web/messages/zh-TW.json apps/web/messages/en.json
git diff --check
pnpm --dir apps/web exec tsc --noEmit --tsBuildInfoFile /tmp/awoooi-t180-tsconfig.tsbuildinfo
pnpm --dir apps/web lint -- --file 'src/app/[locale]/page.tsx'
NEXT_PUBLIC_API_URL=https://awoooi.wooo.work pnpm --dir apps/web run build
```
**local browser note**
- 本機 `localhost:30180` 預覽會被 production API CORS 擋住,所以 local Playwright 只能驗證靜態殼;首頁真實資料仍需以 Gitea CD 後的 production Playwright 為準。
**目前整體進度**
- AwoooP 告警可觀測鏈:約 99.34%。
- 低風險自動修復閉環:約 95.8%。
- 前端 AI 自動化管理介面同步:約 98.1%。
- 首頁 KPI / 小龍蝦流程 truth alignment約 96.5%。
- Telegram 詳情 / 歷史可追溯:約 95.5%。
- callback / DB replayability約 96.0%。
- MCP / 自建 MCP 可視化:約 88%。
- Sentry / SigNoz source correlation約 88%。
- Ansible / PlayBook 可視化:約 85.2%。
- KM governance約 84%。
- AI Provider lane visibility約 92%。
- 完整 AI 自動化管理產品化:約 95.6%。