correlator: remove is_dispatch and the tactical fit path entirely (#134)

Full removal, not a hardcoded flag: _is_dispatch_channel, _DISPATCH_TG_RE, the is_dispatch parameter, and _call_fits_incident's tactical branch are gone. One evaluation path for every channel.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

Claude-Session: https://claude.ai/code/session_01Tbknwttzou4s46PAykmtix
This commit is contained in:
Logan Cusano
2026-09-13 14:49:25 -04:00
co-authored by Claude Sonnet 5
parent 0473e6a583
commit 014f5e071f
9 changed files with 110 additions and 238 deletions
+20 -42
View File
@@ -31,8 +31,10 @@ from app.internal.incident_correlator import (
NOW = datetime(2026, 8, 20, 7, 0, 0, tzinfo=timezone.utc)
# TG 383 from the dump: "Ch 1 (Patched with 155.310)". _DISPATCH_TG_RE matches
# "patched", so this is a shared dispatch backbone carrying the whole department.
# TG 383 from the dump: "Ch 1 (Patched with 155.310)", a shared dispatch
# backbone carrying the whole department. Kept as two distinct fixture names
# for readability even though the channel's name no longer affects behavior
# (server-26#134).
DISPATCH_TG = "Ch 1 (Patched with 155.310)"
TACTICAL_TG = "Fireground 2"
@@ -140,12 +142,9 @@ def test_thin_call_with_no_overlap_does_not_attach_on_a_dispatch_channel():
assert _run_decision(_ctx(all_active=[inc], recent=[inc]))["action"] == "orphan"
def test_thin_call_with_no_overlap_does_not_attach_on_a_tactical_channel():
"""
The widest version of the bug: non-dispatch talkgroups skipped the tiering
entirely and used the whole 90-minute fast-path window with no
single-candidate requirement, so ANY thin call joined whatever was newest.
"""
def test_thin_call_with_no_overlap_does_not_attach_on_a_tactical_named_channel():
"""A channel's name no longer changes anything (server-26#134) — same
assertion as the dispatch-named case above, different fixture name."""
inc = _incident(idle_minutes=40)
decision = _run_decision(_ctx(
all_active=[inc], recent=[inc], talkgroup_name=TACTICAL_TG,
@@ -154,10 +153,9 @@ def test_thin_call_with_no_overlap_does_not_attach_on_a_tactical_channel():
def test_tactical_named_channel_uses_the_dispatch_window_now():
"""server-26#134: is_dispatch is hardcoded True — a channel's name no
longer changes the thin window. 14 min (the old tactical bound minus 1,
inside the old 15-min window) is now past the 5-min dispatch window."""
inc = _incident(idle_minutes=settings.tg_thin_idle_minutes - 1)
"""server-26#134: 14 min was inside the old 15-min tactical window; now
every channel uses the 5-min window regardless of name."""
inc = _incident(idle_minutes=14)
decision = _run_decision(_ctx(
all_active=[inc], recent=[inc], talkgroup_name=TACTICAL_TG,
))
@@ -234,47 +232,27 @@ def test_back_dated_thin_call_does_not_sail_through_the_recency_gate():
def test_back_dated_call_does_not_bypass_the_content_divergence_veto(monkeypatch):
"""
Same `9d376ffe` failure mode, but exercised directly against
`_call_fits_incident` on a dispatch channel: unit overlap plus a
back-dated call (incident updated 45 minutes AFTER the call's own
`started_at`, which the sweep passes as `now`) used to make the signed
idle -45, so `idle_min >= 15` read False and the content-divergence
veto never ran — unit overlap alone forced the merge regardless of
what the call was actually about. With the gate fixed to compare
distance, idle_min is 45 (>= 15), the veto runs, and a divergent
embedding (patched below so the assertion doesn't depend on numpy
being installed in this environment) fails it.
Same `9d376ffe` failure mode, exercised directly against
`_call_fits_incident`: unit overlap plus a back-dated call (incident
updated 45 minutes AFTER the call's own `started_at`, which the sweep
passes as `now`) used to make the signed idle -45, so `idle_min >= 15`
read False and the content-divergence veto never ran — unit overlap
alone forced the merge regardless of what the call was actually about.
With the gate fixed to compare distance, idle_min is 45 (>= 15), the
veto runs, and a divergent embedding (patched below so the assertion
doesn't depend on numpy being installed in this environment) fails it.
"""
monkeypatch.setattr(correlator_mod, "_cosine_similarity", lambda a, b: 0.0)
inc = _incident(idle_minutes=-45, units=["6-Adam"])
inc["embedding"] = [1.0, 0.0]
fits, signal = _call_fits_incident(
inc, call_units=["6-Adam"], call_vehicles=[], call_coords=None,
proximity_km=settings.location_proximity_km, is_dispatch=True,
proximity_km=settings.location_proximity_km,
call_embedding=[0.0, 1.0], now=NOW,
)
assert (fits, signal) == (False, "content_divergence")
def test_back_dated_call_on_tactical_channel_does_not_get_the_default():
"""
Tactical-channel counterpart: no unit/vehicle/location signal, so the
function falls through to step 4's `idle_min < 20.0` default. A
back-dated call (incident updated 45 minutes after the call's own
started_at) used to read idle_min as -45, which is always < 20.0, so
`tactical_default` fired unconditionally no matter how stale the
incident actually was relative to this call. Fixed, idle_min is the
45-minute distance, which is not < 20.0.
"""
inc = _incident(idle_minutes=-45)
fits, signal = _call_fits_incident(
inc, call_units=[], call_vehicles=[], call_coords=None,
proximity_km=settings.location_proximity_km, is_dispatch=False,
call_embedding=None, now=NOW,
)
assert (fits, signal) == (False, "tactical_idle")
# ---------------------------------------------------------------------------
# 4. Hard caps — path-independent, because pairwise fit tests can't see shape
# ---------------------------------------------------------------------------