correlator: always run the dispatch-strict fit test, not name-guessed (#134)
is_dispatch was computed from _is_dispatch_channel(talkgroup_name) and picked between two _call_fits_incident evaluation orders: dispatch (requires a positive signal, runs location-conflict/content-divergence vetoes on unit overlap) vs tactical (skips both vetoes, defaults to True on no signal at all within 20 min). Per #133's reasoning, a name not literally containing dispatch/patched/primary got the unvetoed, default-True path solely because of its label. Hardcoded is_dispatch=True at its one real call site; the tactical branch and its own tests stay in place, unreached. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tbknwttzou4s46PAykmtix
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
4df801c5e0
commit
0473e6a583
@@ -97,11 +97,13 @@ class Settings(BaseSettings):
|
|||||||
# Across that dump every correct thin attach was <= 3.4 min idle and every wrong
|
# Across that dump every correct thin attach was <= 3.4 min idle and every wrong
|
||||||
# one was >= 8.2, so 5 separates them with room on both sides. Genuine
|
# one was >= 8.2, so 5 separates them with room on both sides. Genuine
|
||||||
# back-and-forth is handled by the 30-second tier-1 path above this.
|
# back-and-forth is handled by the 30-second tier-1 path above this.
|
||||||
# Second consumer (server-26#115): routers/upload.py's LLM-orphan-gate escape
|
# Second consumer (server-26#115): routers/upload.py's LLM-orphan-gate
|
||||||
# hatch (_recent_incident_on_same_talkgroup) reuses this same value, selected
|
# escape hatch (_recent_incident_on_same_talkgroup) always uses this same
|
||||||
# the same way (dispatch vs tactical) via _is_dispatch_channel. Retuning this
|
# value now — no dispatch/tactical branch there since #133. Retuning this
|
||||||
# for fast/thin reasons moves that gate's behavior too — check both call
|
# for fast/thin reasons moves that gate's behavior too — check both call
|
||||||
# sites before changing it.
|
# sites before changing it. (tg_thin_idle_minutes below is now unused in
|
||||||
|
# production — is_dispatch is hardcoded True at its only real call site,
|
||||||
|
# incident_correlator.py's fast-path — see server-26#134.)
|
||||||
tg_dispatch_thin_idle_minutes: int = 5
|
tg_dispatch_thin_idle_minutes: int = 5
|
||||||
# Every other channel: tier-2 thin calls attach to a lone candidate idle < this.
|
# Every other channel: tier-2 thin calls attach to a lone candidate idle < this.
|
||||||
# Non-dispatch talkgroups previously had NO tier-2 bound at all — they used the
|
# Non-dispatch talkgroups previously had NO tier-2 bound at all — they used the
|
||||||
|
|||||||
@@ -976,7 +976,11 @@ def _run_decision(ctx: dict) -> dict:
|
|||||||
# directly on the Firestore call doc). Fall back to the call doc so that
|
# directly on the Firestore call doc). Fall back to the call doc so that
|
||||||
# dispatch-channel strictness works regardless of how the call arrived.
|
# dispatch-channel strictness works regardless of how the call arrived.
|
||||||
effective_talkgroup_name = talkgroup_name or call_doc.get("talkgroup_name")
|
effective_talkgroup_name = talkgroup_name or call_doc.get("talkgroup_name")
|
||||||
is_dispatch = _is_dispatch_channel(effective_talkgroup_name)
|
# server-26#134: always dispatch-strict now, not name-guessed — a
|
||||||
|
# channel labeled "tac"/"tactical" is rare and no less scrutinized in
|
||||||
|
# practice than any other. _call_fits_incident's tactical branch is
|
||||||
|
# kept, unreached, in case that's ever wrong.
|
||||||
|
is_dispatch = True
|
||||||
if effective_talkgroup_name != talkgroup_name:
|
if effective_talkgroup_name != talkgroup_name:
|
||||||
logger.info(
|
logger.info(
|
||||||
f"Correlator: talkgroup_name missing from request for call {call_id}, "
|
f"Correlator: talkgroup_name missing from request for call {call_id}, "
|
||||||
|
|||||||
@@ -153,12 +153,22 @@ def test_thin_call_with_no_overlap_does_not_attach_on_a_tactical_channel():
|
|||||||
assert decision["action"] == "orphan"
|
assert decision["action"] == "orphan"
|
||||||
|
|
||||||
|
|
||||||
def test_tactical_thin_call_still_attaches_inside_its_own_window():
|
def test_tactical_named_channel_uses_the_dispatch_window_now():
|
||||||
"""Bounded, not removed — a "10-4" on a working channel is still context."""
|
"""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)
|
inc = _incident(idle_minutes=settings.tg_thin_idle_minutes - 1)
|
||||||
decision = _run_decision(_ctx(
|
decision = _run_decision(_ctx(
|
||||||
all_active=[inc], recent=[inc], talkgroup_name=TACTICAL_TG,
|
all_active=[inc], recent=[inc], talkgroup_name=TACTICAL_TG,
|
||||||
))
|
))
|
||||||
|
assert decision["action"] == "orphan"
|
||||||
|
|
||||||
|
|
||||||
|
def test_tactical_named_channel_still_attaches_inside_the_dispatch_window():
|
||||||
|
inc = _incident(idle_minutes=settings.tg_dispatch_thin_idle_minutes - 1)
|
||||||
|
decision = _run_decision(_ctx(
|
||||||
|
all_active=[inc], recent=[inc], talkgroup_name=TACTICAL_TG,
|
||||||
|
))
|
||||||
assert decision["action"] == "link"
|
assert decision["action"] == "link"
|
||||||
assert decision["corr_debug"]["corr_path"] == "fast/thin"
|
assert decision["corr_debug"]["corr_path"] == "fast/thin"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user