Add debugging

This commit is contained in:
Logan
2026-05-04 01:46:56 -04:00
parent e704df1a62
commit 97f4286810
3 changed files with 70 additions and 2 deletions
@@ -128,6 +128,7 @@ async def correlate_call(
coords: Optional[dict] = location_coords or call_doc.get("location_coords") coords: Optional[dict] = location_coords or call_doc.get("location_coords")
matched_incident: Optional[dict] = None matched_incident: Optional[dict] = None
corr_debug: dict = {}
# A "thin" call carries no scene-identifying information — it is a pure # A "thin" call carries no scene-identifying information — it is a pure
# status transmission (10-4, en route, acknowledgement). Detected by the # status transmission (10-4, en route, acknowledgement). Detected by the
@@ -170,6 +171,10 @@ async def correlate_call(
# Status/ack call — no scene data to reason about. # Status/ack call — no scene data to reason about.
# Attach to whichever recent incident was most recently active on this TGID. # Attach to whichever recent incident was most recently active on this TGID.
matched_incident = max(tg_recent, key=lambda inc: inc.get("updated_at", "")) matched_incident = max(tg_recent, key=lambda inc: inc.get("updated_at", ""))
corr_debug = {
"corr_path": "fast/thin",
"corr_incident_idle_min": round(_incident_idle_minutes(matched_incident, now), 1),
}
logger.info( logger.info(
f"Correlator fast-path (thin→last TGID incident): " f"Correlator fast-path (thin→last TGID incident): "
f"call {call_id}{matched_incident['incident_id']}" f"call {call_id}{matched_incident['incident_id']}"
@@ -181,6 +186,10 @@ async def correlate_call(
settings.location_proximity_km, is_dispatch=is_dispatch, settings.location_proximity_km, is_dispatch=is_dispatch,
): ):
matched_incident = candidate matched_incident = candidate
corr_debug = {
"corr_path": "fast/single",
"corr_incident_idle_min": round(_incident_idle_minutes(candidate, now), 1),
}
logger.info( logger.info(
f"Correlator fast-path: call {call_id}{candidate['incident_id']}" f"Correlator fast-path: call {call_id}{candidate['incident_id']}"
) )
@@ -193,6 +202,11 @@ async def correlate_call(
matched_incident = _disambiguate( matched_incident = _disambiguate(
tg_recent, call_units, call_vehicles, coords, call_embedding tg_recent, call_units, call_vehicles, coords, call_embedding
) )
corr_debug = {
"corr_path": "fast/disambig",
"corr_incident_idle_min": round(_incident_idle_minutes(matched_incident, now), 1),
"corr_candidates": len(tg_recent),
}
logger.info( logger.info(
f"Correlator fast-path (disambig {len(tg_recent)} candidates): " f"Correlator fast-path (disambig {len(tg_recent)} candidates): "
f"call {call_id}{matched_incident['incident_id']}" f"call {call_id}{matched_incident['incident_id']}"
@@ -210,6 +224,7 @@ async def correlate_call(
) )
if dist_km <= settings.location_proximity_km: if dist_km <= settings.location_proximity_km:
matched_incident = inc matched_incident = inc
corr_debug = {"corr_path": "location", "corr_distance_km": round(dist_km, 3)}
logger.info( logger.info(
f"Correlator location-path: call {call_id}{inc['incident_id']} " f"Correlator location-path: call {call_id}{inc['incident_id']} "
f"(dist={dist_km:.2f}km)" f"(dist={dist_km:.2f}km)"
@@ -246,10 +261,15 @@ async def correlate_call(
best_cross_inc = inc best_cross_inc = inc
if best_cross_inc and best_cross_score >= settings.embedding_cross_tg_threshold: if best_cross_inc and best_cross_score >= settings.embedding_cross_tg_threshold:
matched_incident = best_cross_inc matched_incident = best_cross_inc
shared = len(call_unit_set & set(best_cross_inc.get("units") or []))
corr_debug = {
"corr_path": "cross-tg",
"corr_score": round(best_cross_score, 4),
"corr_shared_units": shared,
}
logger.info( logger.info(
f"Correlator cross-TG path: call {call_id}{best_cross_inc['incident_id']} " f"Correlator cross-TG path: call {call_id}{best_cross_inc['incident_id']} "
f"(sim={best_cross_score:.3f}, " f"(sim={best_cross_score:.3f}, shared_units={shared})"
f"shared_units={len(call_unit_set & set(best_cross_inc.get('units') or []))})"
) )
# ── 3. Slow path: embedding similarity (time-limited, same type) ────────── # ── 3. Slow path: embedding similarity (time-limited, same type) ──────────
@@ -282,6 +302,11 @@ async def correlate_call(
) )
if dist_km <= settings.location_proximity_km * 4: if dist_km <= settings.location_proximity_km * 4:
matched_incident = best_inc matched_incident = best_inc
corr_debug = {
"corr_path": "slow",
"corr_score": round(best_score, 4),
"corr_distance_km": round(dist_km, 3),
}
logger.info( logger.info(
f"Correlator slow-path: call {call_id}{best_inc['incident_id']} " f"Correlator slow-path: call {call_id}{best_inc['incident_id']} "
f"(sim={best_score:.3f}, dist={dist_km:.2f}km)" f"(sim={best_score:.3f}, dist={dist_km:.2f}km)"
@@ -290,6 +315,10 @@ async def correlate_call(
# High-confidence semantic match; geocode unavailable on one or # High-confidence semantic match; geocode unavailable on one or
# both sides — content similarity alone is sufficient evidence. # both sides — content similarity alone is sufficient evidence.
matched_incident = best_inc matched_incident = best_inc
corr_debug = {
"corr_path": "slow/no-location",
"corr_score": round(best_score, 4),
}
logger.info( logger.info(
f"Correlator slow-path (high-confidence, no location): " f"Correlator slow-path (high-confidence, no location): "
f"call {call_id}{best_inc['incident_id']} (sim={best_score:.3f})" f"call {call_id}{best_inc['incident_id']} (sim={best_score:.3f})"
@@ -309,10 +338,19 @@ async def correlate_call(
tags, location, location_coords, tags, location, location_coords,
call_units, call_vehicles, call_embedding, call_severity, now, call_units, call_vehicles, call_embedding, call_severity, now,
) )
corr_debug["corr_path"] = "new"
else: else:
# No match and either no type or creation suppressed — nothing to do # No match and either no type or creation suppressed — nothing to do
return None return None
# Persist the correlation decision to the call document so it can be
# inspected in Firestore or the admin UI without log-scraping.
if corr_debug:
try:
await fstore.doc_set("calls", call_id, corr_debug)
except Exception as e:
logger.warning(f"Could not write corr_debug for call {call_id}: {e}")
return incident_id return incident_id
+23
View File
@@ -138,6 +138,29 @@ export function CallRow({ call, systemName, isAdmin }: Props) {
</div> </div>
)} )}
{/* Correlation debug — admin only */}
{isAdmin && call.corr_path && (
<div className="flex flex-wrap gap-x-3 gap-y-0.5 text-xs font-mono text-gray-600">
<span>corr:</span>
<span className="text-gray-400">{call.corr_path}</span>
{call.corr_incident_idle_min != null && (
<span>idle {call.corr_incident_idle_min}min</span>
)}
{call.corr_score != null && (
<span>sim={call.corr_score.toFixed(3)}</span>
)}
{call.corr_distance_km != null && (
<span>dist={call.corr_distance_km}km</span>
)}
{call.corr_shared_units != null && (
<span>{call.corr_shared_units} shared units</span>
)}
{call.corr_candidates != null && (
<span>{call.corr_candidates} candidates</span>
)}
</div>
)}
{/* Transcript */} {/* Transcript */}
{editing ? ( {editing ? (
<div className="space-y-2" onClick={(e) => e.stopPropagation()}> <div className="space-y-2" onClick={(e) => e.stopPropagation()}>
+7
View File
@@ -56,6 +56,13 @@ export interface CallRecord {
location: string | null; location: string | null;
tags: string[]; tags: string[];
status: "active" | "ended"; status: "active" | "ended";
// Correlation debug — written by the correlator, present after a call is linked
corr_path?: string | null;
corr_score?: number | null;
corr_distance_km?: number | null;
corr_incident_idle_min?: number | null;
corr_shared_units?: number | null;
corr_candidates?: number | null;
} }
export interface IncidentRecord { export interface IncidentRecord {