diff --git a/apps/api/src/services/platform_operator_service.py b/apps/api/src/services/platform_operator_service.py index 7a9fa9038..e1bf3e88d 100644 --- a/apps/api/src/services/platform_operator_service.py +++ b/apps/api/src/services/platform_operator_service.py @@ -3282,9 +3282,14 @@ def _callback_reply_audit_summary_from_row( inbound_callback_recent_24h_total = _safe_int( row.get("inbound_callback_recent_24h_total") ) - if inbound_callback_total > 0: + if inbound_callback_recent_24h_total > 0: inbound_callback_mirror_status = "capturing" inbound_callback_next_action = "none" + elif inbound_callback_total > 0: + inbound_callback_mirror_status = "stale" + inbound_callback_next_action = ( + "restore_callback_ingress_and_capture_fresh_receipt" + ) elif callback_total > 0: inbound_callback_mirror_status = "reply_only_gap" inbound_callback_next_action = "press_any_telegram_callback_after_rollout" diff --git a/apps/api/tests/test_awooop_operator_timeline_labels.py b/apps/api/tests/test_awooop_operator_timeline_labels.py index 04e55327f..e13ff9f34 100644 --- a/apps/api/tests/test_awooop_operator_timeline_labels.py +++ b/apps/api/tests/test_awooop_operator_timeline_labels.py @@ -2473,6 +2473,46 @@ def test_callback_reply_audit_summary_marks_missing_snapshots() -> None: ] == datetime(2026, 5, 25, 8, 42, 22) +def test_callback_reply_audit_summary_requires_recent_inbound_receipt() -> None: + latest_callback = datetime(2026, 7, 16, 14, 30, 0) + + summary = _callback_reply_audit_summary_from_row( + { + "inbound_callback_total": 91, + "inbound_callback_recent_24h_total": 1, + "inbound_callback_latest_at": latest_callback, + }, + project_id="awoooi", + ) + + assert summary["inbound_callback_total"] == 91 + assert summary["inbound_callback_recent_24h_total"] == 1 + assert summary["inbound_callback_latest_at"] == latest_callback + assert summary["inbound_callback_mirror_status"] == "capturing" + assert summary["inbound_callback_next_action"] == "none" + + +def test_callback_reply_audit_summary_marks_historical_inbound_receipts_stale() -> None: + latest_callback = datetime(2026, 7, 14, 8, 0, 0) + + summary = _callback_reply_audit_summary_from_row( + { + "inbound_callback_total": 91, + "inbound_callback_recent_24h_total": 0, + "inbound_callback_latest_at": latest_callback, + }, + project_id="awoooi", + ) + + assert summary["inbound_callback_total"] == 91 + assert summary["inbound_callback_recent_24h_total"] == 0 + assert summary["inbound_callback_latest_at"] == latest_callback + assert summary["inbound_callback_mirror_status"] == "stale" + assert summary["inbound_callback_next_action"] == ( + "restore_callback_ingress_and_capture_fresh_receipt" + ) + + def test_callback_reply_audit_summary_marks_mixed_legacy_snapshots_partial() -> None: summary = _callback_reply_audit_summary_from_row( { diff --git a/apps/web/messages/en.json b/apps/web/messages/en.json index c29d1d7fa..b3924aeb8 100644 --- a/apps/web/messages/en.json +++ b/apps/web/messages/en.json @@ -11485,6 +11485,12 @@ "outboundReplyMarkupTopPrefixItem": "{prefix} {total}(24h {recent},最後 {last})", "operatorVerdictTitle": "Operator 判讀", "operatorVerdictDetail": "入站點擊鏡像:{inboundStatus}(累計 {inboundTotal},24h {inboundRecent24h});snapshot:{snapshotStatus} / {snapshotCoverage};trace 復原:{traceRecoveryStatus};下一步:{nextAction}", + "inboundTitle": "Telegram inbound callbacks", + "inboundStatus": "Status: {status}", + "inboundTotal": "Lifetime inbound", + "inboundRecent24h": "Last 24 hours", + "inboundLatest": "Latest inbound", + "inboundNext": "Safe next action", "callbacks": "Callback 回覆", "callbackDetail": "detail {detail} / history {history};Incident {incidents}", "inboundCallbackDetail": "入站 click:{status};累計 {total} / 24h {recent24h};最新 {latest};動作 {action}", @@ -11522,15 +11528,17 @@ "observed": "已記錄" }, "inboundStatuses": { - "capturing": "已入庫", - "reply_only_gap": "只有回覆證據,舊點擊未入庫", - "no_callback_observed": "尚未看到入站點擊", - "observed": "已記錄" + "capturing": "Capturing", + "stale": "Stale", + "reply_only_gap": "Replies exist but inbound clicks are not captured", + "no_callback_observed": "No inbound callback observed", + "observed": "Observed" }, "inboundNextActions": { - "none": "不需補動作", - "press_any_telegram_callback_after_rollout": "新版部署後按一次詳情 / 歷史 / 審批按鈕驗證入庫", - "observed": "等待下一次 Telegram click" + "none": "No action required", + "restore_callback_ingress_and_capture_fresh_receipt": "Restore callback ingress and capture a fresh inbound receipt", + "press_any_telegram_callback_after_rollout": "After rollout, press a detail, history, or approval button to verify capture", + "observed": "Wait for the next Telegram click" }, "nextActions": { "none": "不需補動作", diff --git a/apps/web/messages/zh-TW.json b/apps/web/messages/zh-TW.json index 6acd1c720..1b279750d 100644 --- a/apps/web/messages/zh-TW.json +++ b/apps/web/messages/zh-TW.json @@ -11485,6 +11485,12 @@ "outboundReplyMarkupTopPrefixItem": "{prefix} {total}(24h {recent},最後 {last})", "operatorVerdictTitle": "Operator 判讀", "operatorVerdictDetail": "入站點擊鏡像:{inboundStatus}(累計 {inboundTotal},24h {inboundRecent24h});snapshot:{snapshotStatus} / {snapshotCoverage};trace 復原:{traceRecoveryStatus};下一步:{nextAction}", + "inboundTitle": "Telegram 入站 Callback", + "inboundStatus": "狀態:{status}", + "inboundTotal": "累計入站", + "inboundRecent24h": "近 24 小時", + "inboundLatest": "最新入站", + "inboundNext": "安全下一步", "callbacks": "Callback 回覆", "callbackDetail": "detail {detail} / history {history};Incident {incidents}", "inboundCallbackDetail": "入站 click:{status};累計 {total} / 24h {recent24h};最新 {latest};動作 {action}", @@ -11523,12 +11529,14 @@ }, "inboundStatuses": { "capturing": "已入庫", + "stale": "已過期", "reply_only_gap": "只有回覆證據,舊點擊未入庫", "no_callback_observed": "尚未看到入站點擊", "observed": "已記錄" }, "inboundNextActions": { "none": "不需補動作", + "restore_callback_ingress_and_capture_fresh_receipt": "恢復 callback ingress 並取得新鮮入站 receipt", "press_any_telegram_callback_after_rollout": "新版部署後按一次詳情 / 歷史 / 審批按鈕驗證入庫", "observed": "等待下一次 Telegram click" }, diff --git a/apps/web/src/app/[locale]/awooop/runs/page.tsx b/apps/web/src/app/[locale]/awooop/runs/page.tsx index 58508e4c8..fb3c9c9b8 100644 --- a/apps/web/src/app/[locale]/awooop/runs/page.tsx +++ b/apps/web/src/app/[locale]/awooop/runs/page.tsx @@ -2938,6 +2938,7 @@ function CallbackReplyAuditSummaryPanel({ summary.inbound_callback_mirror_status ?? "no_callback_observed"; const inboundStatusKey = ( inboundStatusRaw === "capturing" || + inboundStatusRaw === "stale" || inboundStatusRaw === "reply_only_gap" || inboundStatusRaw === "no_callback_observed" ) ? inboundStatusRaw : "observed"; @@ -2946,6 +2947,8 @@ function CallbackReplyAuditSummaryPanel({ "press_any_telegram_callback_after_rollout"; const inboundNextActionKey = ( inboundNextActionRaw === "none" || + inboundNextActionRaw === + "restore_callback_ingress_and_capture_fresh_receipt" || inboundNextActionRaw === "press_any_telegram_callback_after_rollout" ) ? inboundNextActionRaw : "observed"; const traceRefAfterGapFirst = formatShortDateTime( @@ -2973,6 +2976,13 @@ function CallbackReplyAuditSummaryPanel({ no_callback: "border-[#d8d3c7] bg-[#faf9f3] text-[#5f5b52]", observed: "border-[#9bb6d9] bg-[#eef5ff] text-[#1f5b9b]", }[snapshotStatus]; + const inboundClass = { + capturing: "border-[#9bc7a4] bg-[#f0faf2] text-[#17602a]", + stale: "border-[#d9b36f] bg-[#fff7e8] text-[#8a5a08]", + reply_only_gap: "border-[#e2a29b] bg-[#fff0ef] text-[#9f2f25]", + no_callback_observed: "border-[#d8d3c7] bg-[#faf9f3] text-[#5f5b52]", + observed: "border-[#9bb6d9] bg-[#eef5ff] text-[#1f5b9b]", + }[inboundStatusKey]; return (
@@ -2981,6 +2991,55 @@ function CallbackReplyAuditSummaryPanel({ {t("operatorVerdictTitle")}

+
+
+

+ {t("inboundTitle")} +

+ + {t("inboundStatus", { status: inboundStatusLabel })} + +
+
+
+

+ {t("inboundTotal")} +

+

+ {summary.inbound_callback_total ?? 0} +

+
+
+

+ {t("inboundRecent24h")} +

+

+ {summary.inbound_callback_recent_24h_total ?? 0} +

+
+
+

+ {t("inboundLatest")} +

+

+ {latestInboundCallback} +

+
+
+

+ {t("inboundNext")} +

+

+ {inboundNextActionLabel} +

+
+
+

{t("outbound")}