Two drb-correlation-review passes, both must-fixes applied:
Fix 1 — the gate.llm=orphan + rules=new resolves to orphan without a tiebreak only when _call_is_substanceless(ctx):
severity not moderate/major
no coords, tags, or vehicles (incident_correlator.has_event_substance, extracted from the creation gate so the two can't drift)
no recent incident on the same system+talkgroup (the "unit dispatched, thin ack 10-30s later" case — keeps the tiebreak)
no resolved incident_type and not reassignment (round-2 fix: a routine-severity fire/medical call, or a unit pulled to a new job, would otherwise be dropped where rules would open an incident)
Any one false → unchanged escalation to tiebreak.
Fix 2 — corr_path=location. A non-pursuit location link now needs unit overlap OR ≤ 0.2 km (was: anywhere in the 0.5 km radius). Unit-overlap links write corr_fit_signal="location_unit_overlap" (distinct bucket for the #35 tally); distance-only keep location_proximity. Candidates are scored and the nearest qualifying one is linked (was: break on first in an unsorted list).
Permanence. Gated orphans get GATED_ORPHAN_SWEEP_ATTEMPTS=10 (vs 3) in the re-correlation sweep, link-only — so a substantive call landing on the TG within ~20 min still pulls the earlier thin calls in. Not made creation-eligible (a substanceless call resolves no incident_type, so it'd be inert, and minting an incident from a content-free call is the failure this gate prevents).
Residual (not blocking, follow-ups):
_LOCATION_TIGHT_PROXIMITY_KM = 0.2 is a hardcoded literal beside the configurable location_proximity_km — same shape as #86. Should move to config.
On a busy dispatch backbone a recent same-TG incident is nearly always live, so the gate mostly fires on quiet channels — recovers less of the window-#2 21/21 than the raw count suggests. Measure the llm_orphan_gate bucket in a window #3 (it appears in orphaned_calls, not the linked histogram).
The upstream fix (dispatch-vs-chatter classifier so "10-4"/"98"/roll-call never create a scene) is still the real lever — this PR is the stopgap.
## PR #125 — ready for merge (commit 15a9d10, 3 commits, sandboxed pytest 327 green)
Two `drb-correlation-review` passes, both must-fixes applied:
**Fix 1 — the gate.** `llm=orphan` + `rules=new` resolves to **orphan without a tiebreak** only when `_call_is_substanceless(ctx)`:
- severity not moderate/major
- no coords, tags, or vehicles (`incident_correlator.has_event_substance`, extracted from the creation gate so the two can't drift)
- no recent incident on the same system+talkgroup (the "unit dispatched, thin ack 10-30s later" case — keeps the tiebreak)
- **no resolved `incident_type` and not `reassignment`** (round-2 fix: a routine-severity fire/medical call, or a unit pulled to a new job, would otherwise be dropped where rules would open an incident)
Any one false → unchanged escalation to `tiebreak`.
**Fix 2 — `corr_path=location`.** A non-pursuit location link now needs unit overlap OR ≤ 0.2 km (was: anywhere in the 0.5 km radius). Unit-overlap links write `corr_fit_signal="location_unit_overlap"` (distinct bucket for the #35 tally); distance-only keep `location_proximity`. Candidates are scored and the nearest qualifying one is linked (was: `break` on first in an unsorted list).
**Permanence.** Gated orphans get `GATED_ORPHAN_SWEEP_ATTEMPTS=10` (vs 3) in the re-correlation sweep, link-only — so a substantive call landing on the TG within ~20 min still pulls the earlier thin calls in. Not made creation-eligible (a substanceless call resolves no `incident_type`, so it'd be inert, and minting an incident from a content-free call is the failure this gate prevents).
**Residual (not blocking, follow-ups):**
- `_LOCATION_TIGHT_PROXIMITY_KM = 0.2` is a hardcoded literal beside the configurable `location_proximity_km` — same shape as #86. Should move to config.
- On a busy dispatch backbone a recent same-TG incident is nearly always live, so the gate mostly fires on quiet channels — recovers less of the window-#2 21/21 than the raw count suggests. **Measure the `llm_orphan_gate` bucket in a window #3** (it appears in `orphaned_calls`, not the linked histogram).
- The upstream fix (dispatch-vs-chatter classifier so "10-4"/"98"/roll-call never create a scene) is still the real lever — this PR is the stopgap.
CORRELATION_REVIEW_0907b.md measured that radio housekeeping (unit
check-ins, roll call, 10-8/10-98 clearings) is being promoted to
incidents. Every case reads corr_llm_action=orphan, corr_rules_action=new,
corr_consensus=tiebreak -> new: the cheap LLM correctly reads "not an
incident", the rules engine says `new` only because there is no incident to
link to, and the smart tiebreaker then sides with rules ~21/21. Reframing
the tiebreaker prompt (#116) did nothing. The fix is a consensus-logic gate,
not another prompt.
Fix 1 (routers/upload.py) - LLM-orphan gate in _correlate_with_consensus:
when the cheap LLM says `orphan` and the rules engine says `new` with NO
positive event signal, resolve to `orphan` and skip the tiebreak call
entirely. "No positive signal" = the rules corr_debug carries neither a
positive corr_path (unit-continuity / location / fast/disambig / fast/single)
nor a positive corr_fit_signal (unit_overlap / location_proximity). When it
does carry one, the existing escalation-to-tiebreak is kept so a genuine
event the LLM misreads as orphan still gets the second look. The resolved
outcome records corr_consensus="llm_orphan_gate" (greppable, distinct from
"tiebreak") and keeps corr_llm_reasoning / corr_rules_action /
corr_llm_action populated.
Fix 2 (incident_correlator.py) - tighten corr_path=location: the location
path linked on a bare sub-location_proximity_km (0.5 km) distance with no
unit or content check, which stitched a vehicle lockout to a station-restroom
slip and merged two different churches an hour apart. A location link now
requires unit overlap with the candidate OR a distance under a tighter bar
(_LOCATION_TIGHT_PROXIMITY_KM = 0.2 km). Pursuit incidents keep their
movement-speed-validated wide radius. A surviving location link now also
writes corr_fit_signal (unit_overlap | location_proximity), consistent with
Fix 1's positive-signal set.
Tests: new tests/test_consensus_gate.py (13 cases) - the gate resolves to
orphan without calling tiebreak on a no-signal disagreement; a unit_overlap /
location_proximity / unit-continuity / fast-disambig rules signal still
escalates; llm=link vs rules=new still escalates; the location path drops a
shared-area candidate with neither unit overlap nor tight proximity, links on
unit overlap, and links on tight proximity alone. Full c2-core suite
309 -> 322 passing.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tbknwttzou4s46PAykmtix
The gate added in ca1d8fb checked rules_decision["corr_debug"] for a positive
signal, but that dict is empty at preview time for action=="new" (corr_path is
written at apply time). The check was always False, so the gate fired on real
events — replayed against corr_dump_9-7_pm.json it dropped ~36 linked calls
including a major "extinguishing fire", a moderate fire-alarm, geocoded calls
and pursuit updates.
Gate now runs against ctx (fully populated at preview time). It fires ONLY when
the call is substanceless: routine severity, no vehicle/geocode/tag, and no
incident already running on the same talkgroup. Any of those escalates to the
tiebreak instead. The substance predicate (has_event_substance) is factored out
of incident_correlator's creation gate and shared, so the two cannot diverge.
recorrelation_sweep: a call the gate parked gets a longer link-only retry budget
(10 vs 3) — the gate fires before any incident for the job exists, so the
substantive call that justifies linking can land after the standard ~6 min.
Still create_if_new=False.
incident_correlator location path: evaluate every in-radius candidate and link
the nearest that carries corroboration, instead of the first in an unsorted
`recent`. A unit-overlap location link is now tagged "location_unit_overlap" so
it stops merging into the fast path's bucket in the admin fit-signal histogram.
tests/test_consensus_gate.py: replaced the corr_debug-signal cases with ctx
substance cases (severity, coords, tags, vehicles, same-tg incident); added a
nearest-wins location test; the two location guard tests now assert they reach
the new guard. Full drb-c2-core suite 322 -> 325.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tbknwttzou4s46PAykmtix
_call_is_substanceless mirrored has_event_substance but not the creation gate's type-resolved short-circuit, so a routine-severity fire/medical call with no coords/tags/vehicles — or a reassignment (unit pulled to a new job) — could be gated to orphan where rules would open an incident. Bail out of the gate on incident_type or reassignment.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tbknwttzou4s46PAykmtix
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
PR #125 — ready for merge (commit
15a9d10, 3 commits, sandboxed pytest 327 green)Two
drb-correlation-reviewpasses, both must-fixes applied:Fix 1 — the gate.
llm=orphan+rules=newresolves to orphan without a tiebreak only when_call_is_substanceless(ctx):incident_correlator.has_event_substance, extracted from the creation gate so the two can't drift)incident_typeand notreassignment(round-2 fix: a routine-severity fire/medical call, or a unit pulled to a new job, would otherwise be dropped where rules would open an incident)Any one false → unchanged escalation to
tiebreak.Fix 2 —
corr_path=location. A non-pursuit location link now needs unit overlap OR ≤ 0.2 km (was: anywhere in the 0.5 km radius). Unit-overlap links writecorr_fit_signal="location_unit_overlap"(distinct bucket for the #35 tally); distance-only keeplocation_proximity. Candidates are scored and the nearest qualifying one is linked (was:breakon first in an unsorted list).Permanence. Gated orphans get
GATED_ORPHAN_SWEEP_ATTEMPTS=10(vs 3) in the re-correlation sweep, link-only — so a substantive call landing on the TG within ~20 min still pulls the earlier thin calls in. Not made creation-eligible (a substanceless call resolves noincident_type, so it'd be inert, and minting an incident from a content-free call is the failure this gate prevents).Residual (not blocking, follow-ups):
_LOCATION_TIGHT_PROXIMITY_KM = 0.2is a hardcoded literal beside the configurablelocation_proximity_km— same shape as #86. Should move to config.llm_orphan_gatebucket in a window #3 (it appears inorphaned_calls, not the linked histogram).