diff --git a/apps/api/src/api/v1/platform/operator_runs.py b/apps/api/src/api/v1/platform/operator_runs.py index a88327eb..475653b1 100644 --- a/apps/api/src/api/v1/platform/operator_runs.py +++ b/apps/api/src/api/v1/platform/operator_runs.py @@ -109,6 +109,8 @@ class CallbackReplyAuditSummary(BaseModel): outbound_source_envelope_total: int outbound_source_refs_total: int outbound_incident_ref_total: int + outbound_reply_markup_total: int = 0 + outbound_reply_markup_missing_incident_ref_total: int = 0 outbound_failed_total: int callback_total: int callback_sent_total: int diff --git a/apps/api/src/services/platform_operator_service.py b/apps/api/src/services/platform_operator_service.py index 2ad6f878..ad952ae1 100644 --- a/apps/api/src/services/platform_operator_service.py +++ b/apps/api/src/services/platform_operator_service.py @@ -487,6 +487,16 @@ async def _fetch_callback_reply_audit_summary( '[]'::jsonb ) <> '[]'::jsonb ) AS outbound_incident_ref_total, + COUNT(*) FILTER ( + WHERE source_envelope #>> '{reply_markup,present}' = 'true' + ) AS outbound_reply_markup_total, + COUNT(*) FILTER ( + WHERE source_envelope #>> '{reply_markup,present}' = 'true' + AND COALESCE( + source_envelope #> '{source_refs,incident_ids}', + '[]'::jsonb + ) = '[]'::jsonb + ) AS outbound_reply_markup_missing_incident_ref_total, COUNT(*) FILTER ( WHERE send_status = 'failed' ) AS outbound_failed_total, @@ -607,6 +617,12 @@ def _callback_reply_audit_summary_from_row( row.get("outbound_source_refs_total") ), "outbound_incident_ref_total": outbound_incident_refs, + "outbound_reply_markup_total": _safe_int( + row.get("outbound_reply_markup_total") + ), + "outbound_reply_markup_missing_incident_ref_total": _safe_int( + row.get("outbound_reply_markup_missing_incident_ref_total") + ), "outbound_failed_total": _safe_int(row.get("outbound_failed_total")), "callback_total": callback_total, "callback_sent_total": _safe_int(row.get("callback_sent_total")), diff --git a/apps/api/tests/test_awooop_operator_timeline_labels.py b/apps/api/tests/test_awooop_operator_timeline_labels.py index 7365d1d9..8e1089fa 100644 --- a/apps/api/tests/test_awooop_operator_timeline_labels.py +++ b/apps/api/tests/test_awooop_operator_timeline_labels.py @@ -667,6 +667,8 @@ def test_list_callback_replies_response_preserves_callback_evidence() -> None: "outbound_source_envelope_total": 118, "outbound_source_refs_total": 100, "outbound_incident_ref_total": 80, + "outbound_reply_markup_total": 30, + "outbound_reply_markup_missing_incident_ref_total": 4, "outbound_failed_total": 1, "callback_total": 3, "callback_sent_total": 1, @@ -703,6 +705,8 @@ def test_list_callback_replies_response_preserves_callback_evidence() -> None: assert dumped["items"][0]["evidence_capture_status"]["status"] == "captured" assert dumped["items"][0]["run_detail_href"].endswith("project_id=awoooi") assert dumped["summary"]["outbound_total"] == 120 + assert dumped["summary"]["outbound_reply_markup_total"] == 30 + assert dumped["summary"]["outbound_reply_markup_missing_incident_ref_total"] == 4 assert dumped["summary"]["callback_snapshot_missing_total"] == 1 assert dumped["summary"]["snapshot_status"] == "partial" @@ -723,6 +727,8 @@ def test_callback_reply_audit_summary_marks_missing_snapshots() -> None: "outbound_source_envelope_total": 5256, "outbound_source_refs_total": 5000, "outbound_incident_ref_total": 3200, + "outbound_reply_markup_total": 100, + "outbound_reply_markup_missing_incident_ref_total": 12, "outbound_failed_total": 0, "callback_total": 2, "callback_sent_total": 2, @@ -756,6 +762,8 @@ def test_callback_reply_audit_summary_marks_mixed_legacy_snapshots_partial() -> "outbound_source_envelope_total": 4905, "outbound_source_refs_total": 4676, "outbound_incident_ref_total": 920, + "outbound_reply_markup_total": 1322, + "outbound_reply_markup_missing_incident_ref_total": 684, "outbound_failed_total": 0, "callback_total": 3, "callback_sent_total": 3, diff --git a/apps/web/messages/en.json b/apps/web/messages/en.json index 412b6361..fcfbf756 100644 --- a/apps/web/messages/en.json +++ b/apps/web/messages/en.json @@ -2834,6 +2834,7 @@ "summary": { "outbound": "Outbound mirror", "outboundDetail": "source_refs {sourceRefs}; incident refs {incidentRefs}; coverage {coverage}", + "outboundReplyMarkupDetail": "reply_markup {replyMarkup}; button missing incident refs {missingIncidentRefs}", "callbacks": "Callback replies", "callbackDetail": "detail {detail} / history {history}; incidents {incidents}", "snapshots": "Evidence snapshots", diff --git a/apps/web/messages/zh-TW.json b/apps/web/messages/zh-TW.json index f5ff08ab..68b0e442 100644 --- a/apps/web/messages/zh-TW.json +++ b/apps/web/messages/zh-TW.json @@ -2835,6 +2835,7 @@ "summary": { "outbound": "出站鏡像", "outboundDetail": "source_refs {sourceRefs};incident refs {incidentRefs};覆蓋 {coverage}", + "outboundReplyMarkupDetail": "reply_markup {replyMarkup};按鈕缺 incident refs {missingIncidentRefs}", "callbacks": "Callback replies", "callbackDetail": "detail {detail} / history {history};Incident {incidents}", "snapshots": "Evidence snapshots", diff --git a/apps/web/src/app/[locale]/awooop/runs/page.tsx b/apps/web/src/app/[locale]/awooop/runs/page.tsx index 4ef9fc4f..6a06d9ce 100644 --- a/apps/web/src/app/[locale]/awooop/runs/page.tsx +++ b/apps/web/src/app/[locale]/awooop/runs/page.tsx @@ -145,6 +145,8 @@ interface CallbackReplyAuditSummary { outbound_source_envelope_total?: number; outbound_source_refs_total?: number; outbound_incident_ref_total?: number; + outbound_reply_markup_total?: number; + outbound_reply_markup_missing_incident_ref_total?: number; outbound_failed_total?: number; callback_total?: number; callback_sent_total?: number; @@ -2008,6 +2010,13 @@ function CallbackReplyAuditSummaryPanel({ coverage: sourceRefCoverage, })}

+

+ {t("outboundReplyMarkupDetail", { + replyMarkup: summary.outbound_reply_markup_total ?? 0, + missingIncidentRefs: + summary.outbound_reply_markup_missing_incident_ref_total ?? 0, + })} +

{t("callbacks")}

diff --git a/docs/LOGBOOK.md b/docs/LOGBOOK.md index 74c5c151..36d98412 100644 --- a/docs/LOGBOOK.md +++ b/docs/LOGBOOK.md @@ -20620,3 +20620,52 @@ GET /api/v1/health: - KM governance:約 84.6%。 - AI Provider lane visibility:約 92.2%。 - 完整 AI 自動化管理產品化:約 97.4%。 + +--- + +## 2026-05-25 T187 — Source Refs Gap Breakdown 前端可視化 + +**背景**: + +- T186 已補未來出站鏡像的 button callback incident ref extraction。 +- 但 Run 監控仍只顯示 `source_refs / incident refs / 覆蓋率`,operator 無法判斷缺口是在一般資訊訊息、approval card、還是帶按鈕但未含 incident refs 的舊卡片。 + +**本輪修正**: + +- `/api/v1/platform/runs/callback-replies` summary 新增: + - `outbound_reply_markup_total` + - `outbound_reply_markup_missing_incident_ref_total` +- Run 監控 `TG Callback Evidence` 的 Outbound mirror 卡新增 gap breakdown: + - `reply_markup {replyMarkup};按鈕缺 incident refs {missingIncidentRefs}` +- 這讓 operator 能看出 source matching 缺口中「可由卡片/按鈕 extraction 改善」的部分,而不是只看到一個總覆蓋率。 + +**local validation(完成)**: + +```text +python3 -m py_compile apps/api/src/services/platform_operator_service.py apps/api/src/api/v1/platform/operator_runs.py apps/api/tests/test_awooop_operator_timeline_labels.py +jq empty apps/web/messages/zh-TW.json apps/web/messages/en.json +git diff --check +PYTHONPATH=. DATABASE_URL='postgresql+asyncpg://test:test@localhost/test' /Users/ogt/.pyenv/shims/pytest tests/test_awooop_operator_timeline_labels.py -q + 53 passed in 0.76s +pnpm --dir apps/web exec tsc --noEmit --tsBuildInfoFile /tmp/awoooi-t187-tsconfig.tsbuildinfo +pnpm --dir apps/web lint -- --file 'src/app/[locale]/awooop/runs/page.tsx' + pass with pre-existing i18next/no-literal-string warnings in the same file +NEXT_PUBLIC_API_URL=https://awoooi.wooo.work pnpm --dir apps/web run build + pass; Sentry global-error / instrumentation-client warnings are pre-existing +``` + +**目前整體進度**: + +- AwoooP 告警可觀測鏈:約 99.61%。 +- 低風險自動修復閉環:約 95.8%。 +- 前端 AI 自動化管理介面同步:約 99.2%。 +- 首頁 KPI / 小龍蝦流程 truth alignment:約 96.5%。 +- Telegram 詳情 / 歷史可追溯:約 99.0%。 +- Telegram outbound / callback DB coverage 可視化:約 99.15%。 +- callback / DB replayability:約 98.5%。 +- MCP / 自建 MCP 可視化:約 95.1%。 +- Sentry / SigNoz source correlation:約 94.0%。 +- Ansible / PlayBook 可視化:約 92.6%。 +- KM governance:約 84.6%。 +- AI Provider lane visibility:約 92.2%。 +- 完整 AI 自動化管理產品化:約 97.45%。