Bug hunting for correlator
This commit is contained in:
@@ -283,6 +283,13 @@ async def correlate_call(
|
|||||||
)
|
)
|
||||||
elif len(tg_recent) == 1:
|
elif len(tg_recent) == 1:
|
||||||
candidate = tg_recent[0]
|
candidate = tg_recent[0]
|
||||||
|
logger.info(
|
||||||
|
f"Correlator fast/single: call {call_id} vs incident {candidate['incident_id']} "
|
||||||
|
f"tg_name={talkgroup_name!r} is_dispatch={is_dispatch} "
|
||||||
|
f"idle={round(_incident_idle_minutes(candidate, now), 1)}min "
|
||||||
|
f"call_units={call_units} inc_units={candidate.get('units')} "
|
||||||
|
f"call_coords={'yes' if coords else 'no'} inc_coords={'yes' if candidate.get('location_coords') else 'no'}"
|
||||||
|
)
|
||||||
fit, fit_signal = _call_fits_incident(
|
fit, fit_signal = _call_fits_incident(
|
||||||
candidate, call_units, call_vehicles, coords,
|
candidate, call_units, call_vehicles, coords,
|
||||||
settings.location_proximity_km, is_dispatch=is_dispatch,
|
settings.location_proximity_km, is_dispatch=is_dispatch,
|
||||||
@@ -294,13 +301,14 @@ async def correlate_call(
|
|||||||
"corr_path": "fast/single",
|
"corr_path": "fast/single",
|
||||||
"corr_incident_idle_min": round(_incident_idle_minutes(candidate, now), 1),
|
"corr_incident_idle_min": round(_incident_idle_minutes(candidate, now), 1),
|
||||||
"corr_fit_signal": fit_signal,
|
"corr_fit_signal": fit_signal,
|
||||||
|
"corr_is_dispatch": is_dispatch,
|
||||||
}
|
}
|
||||||
if fit_signal == "unit_overlap" and call_units:
|
if fit_signal == "unit_overlap" and call_units:
|
||||||
inc_unit_set = set(candidate.get("units") or [])
|
inc_unit_set = set(candidate.get("units") or [])
|
||||||
corr_debug["corr_matched_units"] = [u for u in call_units if u in inc_unit_set]
|
corr_debug["corr_matched_units"] = [u for u in call_units if u in inc_unit_set]
|
||||||
logger.info(
|
logger.info(
|
||||||
f"Correlator fast-path: call {call_id} → {candidate['incident_id']} "
|
f"Correlator fast-path: call {call_id} → {candidate['incident_id']} "
|
||||||
f"(signal={fit_signal})"
|
f"(signal={fit_signal}, is_dispatch={is_dispatch})"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
logger.info(
|
logger.info(
|
||||||
@@ -314,6 +322,13 @@ async def correlate_call(
|
|||||||
# Disambiguate picks the best candidate, but still verify the call
|
# Disambiguate picks the best candidate, but still verify the call
|
||||||
# actually fits before committing — a new unrelated call on a busy
|
# actually fits before committing — a new unrelated call on a busy
|
||||||
# dispatch channel should create its own incident, not be force-merged.
|
# dispatch channel should create its own incident, not be force-merged.
|
||||||
|
logger.info(
|
||||||
|
f"Correlator fast/disambig: call {call_id} vs incident {candidate['incident_id']} "
|
||||||
|
f"tg_name={talkgroup_name!r} is_dispatch={is_dispatch} "
|
||||||
|
f"idle={round(_incident_idle_minutes(candidate, now), 1)}min "
|
||||||
|
f"call_units={call_units} inc_units={candidate.get('units')} "
|
||||||
|
f"call_coords={'yes' if coords else 'no'} inc_coords={'yes' if candidate.get('location_coords') else 'no'}"
|
||||||
|
)
|
||||||
fit, fit_signal = _call_fits_incident(
|
fit, fit_signal = _call_fits_incident(
|
||||||
candidate, call_units, call_vehicles, coords,
|
candidate, call_units, call_vehicles, coords,
|
||||||
settings.location_proximity_km, is_dispatch=is_dispatch,
|
settings.location_proximity_km, is_dispatch=is_dispatch,
|
||||||
@@ -326,6 +341,7 @@ async def correlate_call(
|
|||||||
"corr_incident_idle_min": round(_incident_idle_minutes(candidate, now), 1),
|
"corr_incident_idle_min": round(_incident_idle_minutes(candidate, now), 1),
|
||||||
"corr_candidates": len(tg_recent),
|
"corr_candidates": len(tg_recent),
|
||||||
"corr_fit_signal": fit_signal,
|
"corr_fit_signal": fit_signal,
|
||||||
|
"corr_is_dispatch": is_dispatch,
|
||||||
}
|
}
|
||||||
if fit_signal == "unit_overlap" and call_units:
|
if fit_signal == "unit_overlap" and call_units:
|
||||||
inc_unit_set = set(candidate.get("units") or [])
|
inc_unit_set = set(candidate.get("units") or [])
|
||||||
@@ -770,10 +786,12 @@ def _call_fits_incident(
|
|||||||
they are intercepted before it in correlate_call.
|
they are intercepted before it in correlate_call.
|
||||||
"""
|
"""
|
||||||
idle_min = _incident_idle_minutes(inc, now) if now is not None else 9999.0
|
idle_min = _incident_idle_minutes(inc, now) if now is not None else 9999.0
|
||||||
|
inc_id = inc.get("incident_id", "?")
|
||||||
|
|
||||||
# ── 1. Unit overlap ───────────────────────────────────────────────────────
|
# ── 1. Unit overlap ───────────────────────────────────────────────────────
|
||||||
inc_units = set(inc.get("units") or [])
|
inc_units = set(inc.get("units") or [])
|
||||||
if inc_units and call_units and any(u in inc_units for u in call_units):
|
matched_units = [u for u in call_units if u in inc_units] if (inc_units and call_units) else []
|
||||||
|
if matched_units:
|
||||||
if is_dispatch:
|
if is_dispatch:
|
||||||
if call_coords:
|
if call_coords:
|
||||||
# Hard location conflict: geocoded on both sides and clearly different.
|
# Hard location conflict: geocoded on both sides and clearly different.
|
||||||
@@ -784,6 +802,7 @@ def _call_fits_incident(
|
|||||||
inc_coords_u["lat"], inc_coords_u["lng"],
|
inc_coords_u["lat"], inc_coords_u["lng"],
|
||||||
)
|
)
|
||||||
if dist_km > proximity_km:
|
if dist_km > proximity_km:
|
||||||
|
logger.info(f" fits[{inc_id}]: unit_overlap({matched_units}) but location_conflict dist={dist_km:.2f}km → unit_loc_conflict")
|
||||||
return False, "unit_loc_conflict"
|
return False, "unit_loc_conflict"
|
||||||
elif call_embedding and idle_min >= 15:
|
elif call_embedding and idle_min >= 15:
|
||||||
# No geocode available AND old incident: use content divergence as a
|
# No geocode available AND old incident: use content divergence as a
|
||||||
@@ -796,12 +815,15 @@ def _call_fits_incident(
|
|||||||
if inc_emb_u:
|
if inc_emb_u:
|
||||||
sim = _cosine_similarity(call_embedding, inc_emb_u)
|
sim = _cosine_similarity(call_embedding, inc_emb_u)
|
||||||
if sim < 0.82:
|
if sim < 0.82:
|
||||||
|
logger.info(f" fits[{inc_id}]: unit_overlap({matched_units}) but content_divergence sim={sim:.3f} → content_divergence")
|
||||||
return False, "content_divergence"
|
return False, "content_divergence"
|
||||||
|
logger.info(f" fits[{inc_id}]: unit_overlap matched={matched_units} is_dispatch={is_dispatch} → unit_overlap")
|
||||||
return True, "unit_overlap"
|
return True, "unit_overlap"
|
||||||
|
|
||||||
# ── 2. Vehicle overlap ────────────────────────────────────────────────────
|
# ── 2. Vehicle overlap ────────────────────────────────────────────────────
|
||||||
inc_vehicles = set(inc.get("vehicles") or [])
|
inc_vehicles = set(inc.get("vehicles") or [])
|
||||||
if inc_vehicles and call_vehicles and any(v in inc_vehicles for v in call_vehicles):
|
if inc_vehicles and call_vehicles and any(v in inc_vehicles for v in call_vehicles):
|
||||||
|
logger.info(f" fits[{inc_id}]: vehicle_overlap → vehicle_overlap")
|
||||||
return True, "vehicle_overlap"
|
return True, "vehicle_overlap"
|
||||||
|
|
||||||
# ── 3. Location proximity ─────────────────────────────────────────────────
|
# ── 3. Location proximity ─────────────────────────────────────────────────
|
||||||
@@ -812,11 +834,19 @@ def _call_fits_incident(
|
|||||||
inc_coords["lat"], inc_coords["lng"],
|
inc_coords["lat"], inc_coords["lng"],
|
||||||
)
|
)
|
||||||
if dist_km <= proximity_km:
|
if dist_km <= proximity_km:
|
||||||
|
logger.info(f" fits[{inc_id}]: location_proximity dist={dist_km:.2f}km → location_proximity")
|
||||||
return True, "location_proximity"
|
return True, "location_proximity"
|
||||||
# Conflicting location, no other positive signal → different scene.
|
# Conflicting location, no other positive signal → different scene.
|
||||||
|
logger.info(f" fits[{inc_id}]: location_conflict dist={dist_km:.2f}km → location_conflict")
|
||||||
return False, "location_conflict"
|
return False, "location_conflict"
|
||||||
|
|
||||||
# ── 4. No positive signals ────────────────────────────────────────────────
|
# ── 4. No positive signals ────────────────────────────────────────────────
|
||||||
|
logger.info(
|
||||||
|
f" fits[{inc_id}]: no positive signal — is_dispatch={is_dispatch} idle={idle_min:.1f}min "
|
||||||
|
f"inc_units={list(inc_units)} call_units={call_units} "
|
||||||
|
f"inc_vehicles={list(inc_vehicles)} call_vehicles={call_vehicles} "
|
||||||
|
f"call_coords={call_coords is not None} inc_coords={inc_coords is not None}"
|
||||||
|
)
|
||||||
if is_dispatch:
|
if is_dispatch:
|
||||||
# Conversational continuity: the call arrived during the same conversation
|
# Conversational continuity: the call arrived during the same conversation
|
||||||
# thread (< 2 min since last incident activity) with no contradicting evidence.
|
# thread (< 2 min since last incident activity) with no contradicting evidence.
|
||||||
|
|||||||
Reference in New Issue
Block a user