Stop a back-dated call from silently disabling every recency gate (server-26#74)
Build & Deploy / Build & push images (push) Successful in 4m11s
Build & Deploy / Deploy to VM (push) Successful in 1m44s
Build & Deploy / Report a failed deploy (push) Skipped

_call_fits_incident measured incident idle with the signed helper while every
other recency gate in the file uses the unsigned one. On the re-correlation
sweep, `now` is the call's own started_at, which can precede the incident's
last activity, so the value went negative.

Negative idle made `idle_min >= 15` false, which meant the content-divergence
veto never ran and unit overlap was accepted unconditionally -- on a shared
dispatch backbone that is the feedback loop that lets one incident absorb a
whole talkgroup. It also made `idle_min < 20.0` true at any back-dating, so a
tactical channel returned tactical_default for every swept orphan out to the
90-minute bound.

One variable feeds all four gates in the function, so this is a one-line change
at the source. The signed value is untouched where it belongs: callers still
compute corr_incident_idle_min themselves, so debug output keeps its meaning.

Direction is toward more splitting, on the sweep path only, which is the point
-- the bug was suppressing an over-merge veto. Forward-dated calls and anything
inside the thresholds behave exactly as before.

Two tests added alongside the existing idle-gate cases; both fail on the old
line and pass on the new one. 266 pass, 0 fail.

Refs server-26#74, #5, #80.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Logan Cusano
2026-08-28 02:51:07 -04:00
co-authored by Claude Opus 5
parent 187b8c1500
commit e30d594eea
2 changed files with 54 additions and 2 deletions
@@ -1598,7 +1598,15 @@ def _call_fits_incident(
Thin calls (no units/vehicles/coords) never reach this function —
they are intercepted before it in correlate_call.
"""
idle_min = _incident_idle_minutes(inc, now) if now is not None else 9999.0
# Gate comparisons in this function must use the unsigned distance, not the
# signed value: the re-correlation sweep anchors `now` to the call's own
# started_at, which can be earlier than the incident's last activity and
# send the signed value negative — silently defeating every `idle_min`
# gate below (content-divergence veto and the tactical default alike).
# See `_idle_gate_minutes` docstring. The signed value is reported to
# callers separately as `corr_incident_idle_min` (they compute it via
# `_incident_idle_minutes` themselves) — nothing here needs it.
idle_min = _idle_gate_minutes(inc, now) if now is not None else 9999.0
inc_id = inc.get("incident_id", "?")
# ── 1. Unit overlap ───────────────────────────────────────────────────────