From 9b802aa7c66e71d7ab78cedfdb0b8439b41706d0 Mon Sep 17 00:00:00 2001
From: Your Name
Date: Mon, 25 May 2026 19:06:10 +0800
Subject: [PATCH] feat(awooop): surface telegram source ref gaps
---
apps/api/src/api/v1/platform/operator_runs.py | 2 +
.../src/services/platform_operator_service.py | 16 ++++++
.../test_awooop_operator_timeline_labels.py | 8 +++
apps/web/messages/en.json | 1 +
apps/web/messages/zh-TW.json | 1 +
.../web/src/app/[locale]/awooop/runs/page.tsx | 9 ++++
docs/LOGBOOK.md | 49 +++++++++++++++++++
7 files changed, 86 insertions(+)
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%。