correlator/intelligence: close the incident-clearance gap (dispatch-to-10-8 lifecycle)
Build & Deploy / Build & push images (push) Successful in 4m10s
Build & Deploy / Deploy to VM (push) Failing after 3m25s
Build & Deploy / Report a failed deploy (push) Successful in 1s

Two independent fixes, found by tracing why incidents never actually close
(only 2/281 incidents in 5 correlation debug windows ever got a non-empty
units_cleared; 183 resolved via the 90-min idle sweep instead of a real clear):

1. Pattern B (re-dispatch accept, no explicit 10-8): reassignment=True already
   fired correctly and suppressed the unit from re-linking to its prior
   incident, but nothing ever released the unit FROM that incident — it just
   sat "active" until the idle sweep timed it out. _release_reassigned_units
   now scans other active incidents for unit overlap on a reassignment and
   clears the unit there, reusing the same units_active/units_cleared merge
   (factored out as _apply_unit_clearance) that explicit 10-8 extraction uses.

2. Pattern A (self-clear) extraction was inconsistent for two reasons: no
   per-system unit ID format awareness anywhere in the pipeline (formats vary
   by department with zero shared convention), and the cleared_units prompt
   rule only accepted a unit self-reporting, missing dispatch confirming a
   unit's status back to them. Added system.unit_format_hint (owner-authored
   free text, GET/PUT /systems/{id}/unit-format, no auto-induction yet) fed
   into the extraction prompt, and broadened the cleared_units rule while
   still requiring an identifiable unit ID (guards against bare "10-8"/"clear"
   noise, including Whisper hallucination runs already caught upstream by
   _is_garbage_transcript).

Verified: 401 pass, 0 fail (local Linux venv ~/venvs/drb-5c — see CLAUDE.md
testing-reality note).

server-26#pending — not yet filed, Gitea unreachable from this sandbox.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Logan Cusano
2026-09-20 14:25:01 -04:00
co-authored by Claude Sonnet 5
parent a9197709f8
commit fb0bb15c22
5 changed files with 386 additions and 11 deletions
+36
View File
@@ -24,6 +24,10 @@ class TenCodesBody(BaseModel):
ten_codes: Dict[str, str]
class UnitFormatBody(BaseModel):
unit_format_hint: str
class PendingTermBody(BaseModel):
talkgroup_id: int
term: str
@@ -155,6 +159,38 @@ async def update_ten_codes(
return {"ok": True, "ten_codes": body.ten_codes}
# ── Unit ID format hint ─────────────────────────────────────────────────────────
@router.get("/{system_id}/unit-format")
async def get_unit_format(system_id: str):
"""Return the unit-ID format hint for a system."""
system = await fstore.doc_get("systems", system_id)
if not system:
raise HTTPException(404, f"System '{system_id}' not found.")
return {"unit_format_hint": system.get("unit_format_hint") or ""}
@router.put("/{system_id}/unit-format")
async def update_unit_format(
system_id: str,
body: UnitFormatBody,
_: dict = Depends(require_admin_token),
):
"""
Set the free-text unit-ID format hint fed into intelligence.py's
extraction prompt (server-26#<pending>). Departments have no shared unit
ID convention — e.g. "5-David"/bare "David" vs "SAM-1"/"airport-3" — and
the extraction prompt has no way to recognise a format it hasn't been
told about. Own route for the same reason ten-codes has one: not carried
by the systems form, so folding it into PUT /{id} would wipe it.
"""
existing = await fstore.doc_get("systems", system_id)
if not existing:
raise HTTPException(404, f"System '{system_id}' not found.")
await fstore.doc_update("systems", system_id, {"unit_format_hint": body.unit_format_hint})
return {"ok": True, "unit_format_hint": body.unit_format_hint}
# ── Area context ──────────────────────────────────────────────────────────────
@router.get("/{system_id}/area-context")