Add debugging
This commit is contained in:
@@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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()}>
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user