feat(awooop): classify callback trace gaps
This commit is contained in:
@@ -127,6 +127,8 @@ class CallbackReplyAuditSummary(BaseModel):
|
||||
outbound_reply_markup_missing_trace_ref_recent_1h_total: int = 0
|
||||
outbound_reply_markup_missing_trace_ref_recent_24h_total: int = 0
|
||||
outbound_reply_markup_missing_trace_ref_latest_sent_at: datetime | None = None
|
||||
outbound_reply_markup_trace_ref_gap_status: str = "clean"
|
||||
outbound_reply_markup_trace_ref_gap_next_action: str = "none"
|
||||
outbound_reply_markup_missing_incident_ref_top_prefixes: list[
|
||||
OutboundReplyMarkupGapPrefix
|
||||
] = Field(default_factory=list)
|
||||
|
||||
@@ -738,6 +738,20 @@ def _callback_reply_audit_summary_from_row(
|
||||
top_missing_trace_prefixes = _reply_markup_gap_prefixes_from_value(
|
||||
row.get("outbound_reply_markup_missing_trace_ref_top_prefixes")
|
||||
)
|
||||
missing_trace_total = _safe_int(
|
||||
row.get("outbound_reply_markup_missing_trace_ref_total")
|
||||
)
|
||||
missing_trace_recent_1h = _safe_int(
|
||||
row.get("outbound_reply_markup_missing_trace_ref_recent_1h_total")
|
||||
)
|
||||
missing_trace_recent_24h = _safe_int(
|
||||
row.get("outbound_reply_markup_missing_trace_ref_recent_24h_total")
|
||||
)
|
||||
trace_gap_status, trace_gap_next_action = _trace_ref_gap_decision(
|
||||
total=missing_trace_total,
|
||||
recent_1h=missing_trace_recent_1h,
|
||||
recent_24h=missing_trace_recent_24h,
|
||||
)
|
||||
|
||||
if callback_total <= 0:
|
||||
snapshot_status = "no_callback"
|
||||
@@ -785,18 +799,18 @@ def _callback_reply_audit_summary_from_row(
|
||||
"outbound_reply_markup_missing_incident_ref_latest_sent_at": row.get(
|
||||
"outbound_reply_markup_missing_incident_ref_latest_sent_at"
|
||||
),
|
||||
"outbound_reply_markup_missing_trace_ref_total": _safe_int(
|
||||
row.get("outbound_reply_markup_missing_trace_ref_total")
|
||||
"outbound_reply_markup_missing_trace_ref_total": missing_trace_total,
|
||||
"outbound_reply_markup_missing_trace_ref_recent_1h_total": (
|
||||
missing_trace_recent_1h
|
||||
),
|
||||
"outbound_reply_markup_missing_trace_ref_recent_1h_total": _safe_int(
|
||||
row.get("outbound_reply_markup_missing_trace_ref_recent_1h_total")
|
||||
),
|
||||
"outbound_reply_markup_missing_trace_ref_recent_24h_total": _safe_int(
|
||||
row.get("outbound_reply_markup_missing_trace_ref_recent_24h_total")
|
||||
"outbound_reply_markup_missing_trace_ref_recent_24h_total": (
|
||||
missing_trace_recent_24h
|
||||
),
|
||||
"outbound_reply_markup_missing_trace_ref_latest_sent_at": row.get(
|
||||
"outbound_reply_markup_missing_trace_ref_latest_sent_at"
|
||||
),
|
||||
"outbound_reply_markup_trace_ref_gap_status": trace_gap_status,
|
||||
"outbound_reply_markup_trace_ref_gap_next_action": trace_gap_next_action,
|
||||
"outbound_reply_markup_missing_incident_ref_top_prefixes": (
|
||||
top_missing_prefixes
|
||||
),
|
||||
@@ -822,6 +836,22 @@ def _callback_reply_audit_summary_from_row(
|
||||
}
|
||||
|
||||
|
||||
def _trace_ref_gap_decision(
|
||||
*,
|
||||
total: int,
|
||||
recent_1h: int,
|
||||
recent_24h: int,
|
||||
) -> tuple[str, str]:
|
||||
"""Classify reply_markup messages without any source_refs into operator actions."""
|
||||
if total <= 0:
|
||||
return "clean", "none"
|
||||
if recent_1h > 0:
|
||||
return "active_gap", "inspect_recent_outbound_source_refs"
|
||||
if recent_24h > 0:
|
||||
return "recent_backlog", "watch_24h_decay"
|
||||
return "legacy_backlog", "backfill_or_archive_legacy_callbacks"
|
||||
|
||||
|
||||
def _reply_markup_gap_prefixes_from_value(value: Any) -> list[dict[str, Any]]:
|
||||
if not isinstance(value, list):
|
||||
return []
|
||||
|
||||
@@ -681,6 +681,8 @@ def test_list_callback_replies_response_preserves_callback_evidence() -> None:
|
||||
"outbound_reply_markup_missing_trace_ref_latest_sent_at": (
|
||||
datetime(2026, 5, 18, 7, 40, 0)
|
||||
),
|
||||
"outbound_reply_markup_trace_ref_gap_status": "recent_backlog",
|
||||
"outbound_reply_markup_trace_ref_gap_next_action": "watch_24h_decay",
|
||||
"outbound_reply_markup_missing_incident_ref_top_prefixes": [
|
||||
{
|
||||
"prefix": "silence",
|
||||
@@ -755,6 +757,12 @@ def test_list_callback_replies_response_preserves_callback_evidence() -> None:
|
||||
assert dumped["summary"][
|
||||
"outbound_reply_markup_missing_trace_ref_latest_sent_at"
|
||||
] == "2026-05-18T07:40:00"
|
||||
assert dumped["summary"]["outbound_reply_markup_trace_ref_gap_status"] == (
|
||||
"recent_backlog"
|
||||
)
|
||||
assert dumped["summary"]["outbound_reply_markup_trace_ref_gap_next_action"] == (
|
||||
"watch_24h_decay"
|
||||
)
|
||||
assert dumped["summary"][
|
||||
"outbound_reply_markup_missing_incident_ref_top_prefixes"
|
||||
][0] == {
|
||||
@@ -856,6 +864,10 @@ def test_callback_reply_audit_summary_marks_missing_snapshots() -> None:
|
||||
assert summary["outbound_reply_markup_missing_trace_ref_latest_sent_at"] == (
|
||||
datetime(2026, 5, 25, 8, 42, 22)
|
||||
)
|
||||
assert summary["outbound_reply_markup_trace_ref_gap_status"] == "active_gap"
|
||||
assert summary["outbound_reply_markup_trace_ref_gap_next_action"] == (
|
||||
"inspect_recent_outbound_source_refs"
|
||||
)
|
||||
assert summary[
|
||||
"outbound_reply_markup_missing_incident_ref_recent_24h_total"
|
||||
] == 3
|
||||
@@ -949,6 +961,10 @@ def test_callback_reply_audit_summary_marks_mixed_legacy_snapshots_partial() ->
|
||||
assert summary["outbound_reply_markup_missing_trace_ref_latest_sent_at"] == (
|
||||
datetime(2026, 5, 25, 12, 13, 1)
|
||||
)
|
||||
assert summary["outbound_reply_markup_trace_ref_gap_status"] == "recent_backlog"
|
||||
assert summary["outbound_reply_markup_trace_ref_gap_next_action"] == (
|
||||
"watch_24h_decay"
|
||||
)
|
||||
assert summary["outbound_reply_markup_missing_trace_ref_top_prefixes"][0][
|
||||
"recent_24h_total"
|
||||
] == 23
|
||||
|
||||
Reference in New Issue
Block a user