correlator: gate LLM-orphan against rules-new instead of escalating to tiebreak (#115) #125

Merged
logan merged 3 commits from fix/115-consensus-orphan-gate into main 2026-09-11 23:18:13 -04:00
2 changed files with 27 additions and 0 deletions
Showing only changes of commit 15a9d10666 - Show all commits
+8
View File
@@ -141,6 +141,14 @@ def _call_is_substanceless(ctx: dict) -> bool:
"""
from app.internal import incident_correlator
# The incident-creation gate skips the has_event_substance check entirely
# when a type resolved (incident_correlator._run_decision ~:1397), so a
# typed call — fire/medical/etc. — opens an incident on substance we do not
# re-check here. reassignment=True is dispatch pulling a unit onto a NEW
# job (units are blanked at :296 for exactly that reason): the strongest
# new-incident signal in the pipeline. Either one means "keep the tiebreak".
if ctx.get("incident_type") or ctx.get("reassignment"):
return False
if (ctx.get("call_severity") or "routine") in ("moderate", "major"):
return False
if incident_correlator.has_event_substance(ctx):
+19
View File
@@ -133,6 +133,25 @@ async def test_call_with_vehicles_is_not_gated():
m_tiebreak.assert_called_once()
async def test_call_with_resolved_incident_type_is_not_gated():
# The creation gate skips has_event_substance when a type resolved, so a
# typed call (fire/medical/…) opens an incident on substance the gate does
# not re-check — it must keep the tiebreak, not be dropped.
m_apply, m_tiebreak = await _run_consensus(
_preview("new", {}, ctx={"incident_type": "fire"}), _llm("orphan"),
)
m_tiebreak.assert_called_once()
async def test_reassignment_call_is_not_gated():
# reassignment=True is dispatch pulling a unit onto a NEW job (units are
# blanked for exactly that reason) — the strongest new-incident signal.
m_apply, m_tiebreak = await _run_consensus(
_preview("new", {}, ctx={"reassignment": True}), _llm("orphan"),
)
m_tiebreak.assert_called_once()
async def test_recent_incident_on_same_talkgroup_is_not_gated():
ctx = {
"system_id": "sys-1",