diff --git a/apps/web/messages/en.json b/apps/web/messages/en.json index 40b251ed6..889497681 100644 --- a/apps/web/messages/en.json +++ b/apps/web/messages/en.json @@ -7692,6 +7692,44 @@ "ready": "資料可判讀", "degraded": "資料不完整" }, + "truth": { + "claimEyebrow": "AI 自動化真相", + "claimReady": "可宣稱閉環", + "claimBlocked": "尚未達到全自動閉環", + "nextEyebrow": "下一步焦點", + "blockers": { + "dataIncomplete": "來源或品質資料尚未完整讀回;先補資料鏈,不把空值當成功。", + "repairVerifierGap": "已驗證自動修復 {verified}/{evaluated};仍需補執行、驗證或學習回寫。", + "manualGate": "仍有待處理工作 {open}、人工閘門 {gates};需要 owner / verifier 判斷。", + "ready": "來源、修復驗證與學習回寫已達可宣稱門檻;持續監看新事件。", + "watch": "目前沒有足夠條件宣稱全自動;先看 Runs、工作項與批准卡點。" + }, + "nextFocus": { + "workItems": "先處理卡住的工作項", + "approvals": "先清人工閘門", + "runs": "先補執行與驗證", + "observe": "持續監看新來源" + }, + "stats": { + "verified": "驗證", + "openWork": "工作項", + "gates": "閘門" + }, + "links": { + "runs": { + "label": "Runs 時間線", + "detail": "看 AI、MCP、PlayBook、verifier 跑到哪。" + }, + "workItems": { + "label": "工作項", + "detail": "把 manual/no-action 轉成 owner SOP rail。" + }, + "approvals": { + "label": "批准", + "detail": "確認風險門、owner、rollback 與 verifier。" + } + } + }, "flow": { "ingest": { "label": "告警 / 來源", diff --git a/apps/web/messages/zh-TW.json b/apps/web/messages/zh-TW.json index 40b251ed6..889497681 100644 --- a/apps/web/messages/zh-TW.json +++ b/apps/web/messages/zh-TW.json @@ -7692,6 +7692,44 @@ "ready": "資料可判讀", "degraded": "資料不完整" }, + "truth": { + "claimEyebrow": "AI 自動化真相", + "claimReady": "可宣稱閉環", + "claimBlocked": "尚未達到全自動閉環", + "nextEyebrow": "下一步焦點", + "blockers": { + "dataIncomplete": "來源或品質資料尚未完整讀回;先補資料鏈,不把空值當成功。", + "repairVerifierGap": "已驗證自動修復 {verified}/{evaluated};仍需補執行、驗證或學習回寫。", + "manualGate": "仍有待處理工作 {open}、人工閘門 {gates};需要 owner / verifier 判斷。", + "ready": "來源、修復驗證與學習回寫已達可宣稱門檻;持續監看新事件。", + "watch": "目前沒有足夠條件宣稱全自動;先看 Runs、工作項與批准卡點。" + }, + "nextFocus": { + "workItems": "先處理卡住的工作項", + "approvals": "先清人工閘門", + "runs": "先補執行與驗證", + "observe": "持續監看新來源" + }, + "stats": { + "verified": "驗證", + "openWork": "工作項", + "gates": "閘門" + }, + "links": { + "runs": { + "label": "Runs 時間線", + "detail": "看 AI、MCP、PlayBook、verifier 跑到哪。" + }, + "workItems": { + "label": "工作項", + "detail": "把 manual/no-action 轉成 owner SOP rail。" + }, + "approvals": { + "label": "批准", + "detail": "確認風險門、owner、rollback 與 verifier。" + } + } + }, "flow": { "ingest": { "label": "告警 / 來源", diff --git a/apps/web/src/app/[locale]/awooop/page.tsx b/apps/web/src/app/[locale]/awooop/page.tsx index 1624759d3..c55dd11eb 100644 --- a/apps/web/src/app/[locale]/awooop/page.tsx +++ b/apps/web/src/app/[locale]/awooop/page.tsx @@ -723,6 +723,72 @@ function OperationsDecisionMap({ const verifiedPercent = percentValue(verified, evaluated); const qualityHealthyPercent = percentValue(green, green + yellow + red); const isDegraded = sourceError || qualityError; + const productionClaimReady = Boolean(quality?.production_claim.can_claim_full_auto_repair); + const blockerKey = isDegraded + ? "dataIncomplete" + : verified < evaluated + ? "repairVerifierGap" + : openWork + manualGates + numberValue(snapshot.approvals) > 0 + ? "manualGate" + : productionClaimReady + ? "ready" + : "watch"; + const nextFocusKey = openWork > 0 + ? "workItems" + : manualGates + numberValue(snapshot.approvals) > 0 + ? "approvals" + : verified < evaluated + ? "runs" + : "observe"; + const blockerText = (() => { + switch (blockerKey) { + case "dataIncomplete": + return t("truth.blockers.dataIncomplete"); + case "repairVerifierGap": + return t("truth.blockers.repairVerifierGap", { verified, evaluated }); + case "manualGate": + return t("truth.blockers.manualGate", { + open: openWork, + gates: manualGates + numberValue(snapshot.approvals), + }); + case "ready": + return t("truth.blockers.ready"); + default: + return t("truth.blockers.watch"); + } + })(); + const nextFocusText = (() => { + switch (nextFocusKey) { + case "workItems": + return t("truth.nextFocus.workItems"); + case "approvals": + return t("truth.nextFocus.approvals"); + case "runs": + return t("truth.nextFocus.runs"); + default: + return t("truth.nextFocus.observe"); + } + })(); + const truthLinks = [ + { + key: "runs", + href: "/awooop/runs?project_id=awoooi", + value: sourceError ? "--" : linkedRuns, + tone: linkedPercent >= 80 ? "good" : linkedPercent > 0 ? "warn" : "neutral", + }, + { + key: "workItems", + href: "/awooop/work-items?project_id=awoooi", + value: openWork, + tone: openWork === 0 ? "good" : "warn", + }, + { + key: "approvals", + href: "/awooop/approvals?project_id=awoooi", + value: manualGates + numberValue(snapshot.approvals), + tone: manualGates + numberValue(snapshot.approvals) === 0 ? "good" : "warn", + }, + ] as const; return (
@@ -745,6 +811,80 @@ function OperationsDecisionMap({ +
+
+

+ {t("truth.claimEyebrow")} +

+

+ {productionClaimReady ? t("truth.claimReady") : t("truth.claimBlocked")} +

+

+ {blockerText} +

+
+ +
+

+ {t("truth.nextEyebrow")} +

+

+ {nextFocusText} +

+
+
+

{t("truth.stats.verified")}

+

{verified}/{evaluated}

+
+
+

{t("truth.stats.openWork")}

+

{openWork}

+
+
+

{t("truth.stats.gates")}

+

+ {manualGates + numberValue(snapshot.approvals)} +

+
+
+
+ +
+ {truthLinks.map((item) => ( + +
+

+ {t(`truth.links.${item.key}.label` as never)} +

+

+ {item.value} +

+

+ {t(`truth.links.${item.key}.detail` as never)} +

+
+ + + + ))} +
+
+