correlator: give the LLM tier what it needs to link, stop it defaulting to "new" (#115) #116

Merged
logan merged 2 commits from fix/tiebreaker-manufactures-incidents into main 2026-09-07 16:59:46 -04:00
Owner

Addresses #115 — the dominant over-split driver found in the 2026-09-07 measurement (CORRELATION_REVIEW_0907.md).

Finding: the consensus tiebreaker ran on 45% of calls and resolved link/orphan disagreements as new ~24/25 of the time — 1 Mohegan Park car-alarm job → 9 incidents, ~7 incidents opened from radio checks / roll calls.

Fixes:

  1. _inc_summary gave the model id|type|loc|units|tags|idle — no title, no talkgroup. It couldn't see that two "car alarms, Mohegan Park Ave/Avenue" incidents on TG 9560 were one. Now includes both.
  2. _RULES said "orphan when in doubt — conservative is always correct". Backwards for an over-splitting system. Rewritten: prefer link for a plausible same-talkgroup continuation (low bar), reserve new for a genuinely different event, explicitly orphan non-incidents (radio checks, roll call, 10-8/10-98, mileage logs).
  3. _extract_road_ids canonicalises street-type synonyms (Avenue→ave, Street→st, …) so "Mohegan Park Avenue" == "Mohegan Park Ave" for road-overlap.

Testing: tests/test_correlator_115.py (4 cases); full c2-core suite 304 passed on a sandboxed Linux venv. drb-correlation-review running.

Deferred to follow-ups (bigger levers, own review): should a cheap-LLM orphan ever escalate to a tiebreak at all; a first-class road-overlap fit signal in _call_fits_incident; geocode coverage (location_proximity fired 5× in 154 calls).

Merge → then run another measurement window and diff against corr_dump_9-7.

🤖 Generated with Claude Code

Addresses #115 — the dominant over-split driver found in the 2026-09-07 measurement (`CORRELATION_REVIEW_0907.md`). **Finding:** the consensus tiebreaker ran on 45% of calls and resolved link/orphan disagreements as `new` ~24/25 of the time — 1 Mohegan Park car-alarm job → 9 incidents, ~7 incidents opened from radio checks / roll calls. **Fixes:** 1. `_inc_summary` gave the model `id|type|loc|units|tags|idle` — no **title**, no **talkgroup**. It couldn't see that two "car alarms, Mohegan Park Ave/Avenue" incidents on TG 9560 were one. Now includes both. 2. `_RULES` said *"orphan when in doubt — conservative is always correct"*. Backwards for an over-splitting system. Rewritten: prefer `link` for a plausible same-talkgroup continuation (low bar), reserve `new` for a genuinely different event, explicitly `orphan` non-incidents (radio checks, roll call, 10-8/10-98, mileage logs). 3. `_extract_road_ids` canonicalises street-type synonyms (Avenue→ave, Street→st, …) so "Mohegan Park Avenue" == "Mohegan Park Ave" for road-overlap. **Testing:** `tests/test_correlator_115.py` (4 cases); full c2-core suite **304 passed** on a sandboxed Linux venv. `drb-correlation-review` running. **Deferred to follow-ups** (bigger levers, own review): should a cheap-LLM `orphan` ever escalate to a tiebreak at all; a first-class road-overlap fit signal in `_call_fits_incident`; geocode coverage (location_proximity fired 5× in 154 calls). Merge → then run another measurement window and diff against `corr_dump_9-7`. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
logan added 1 commit 2026-09-07 16:53:22 -04:00
The 2026-09-07 measurement window (CORRELATION_REVIEW_0907.md) showed the
consensus tiebreaker was the dominant over-split driver: it ran on 45% of
calls and resolved link/orphan disagreements as "new" ~24/25 of the time,
shattering one Mohegan Park car-alarm job into 9 incidents and opening ~7
incidents from radio checks / roll calls.

Two causes, two fixes:

1. `_inc_summary` gave the model `id|type|loc|units|tags|idle` — no title,
   no talkgroup. It literally could not see that two "car alarms, Mohegan
   Park Ave/Avenue" incidents on TG 9560 were the same. Now includes the
   incident title (the strongest same-event signal) and talkgroup.

2. `_RULES` told the model "orphan when in doubt — conservative is always
   correct". For a system that over-splits, that is backwards: a wrong link
   is cheap, a duplicate incident is the failure. Rewritten to: prefer link
   for a plausible same-talkgroup continuation (low bar), reserve "new" for a
   genuinely different event, and explicitly "orphan" non-incidents (radio
   checks, roll call, 10-8/10-98, mileage logs).

Plus `_extract_road_ids` now canonicalises street-type synonyms
(Avenue→ave, Street→st, Road→rd, ...), so "Mohegan Park Avenue" and
"Mohegan Park Ave" share a road id — that one difference was splitting the
car-alarm incident.

+tests/test_correlator_115.py. Full c2-core suite green (sandboxed venv).
Bigger levers deferred to follow-ups: the consensus escalation itself (should
a cheap-LLM "orphan" ever reach a tiebreak?), a first-class road-overlap fit
signal in _call_fits_incident, geocode coverage.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
logan added 1 commit 2026-09-07 16:59:32 -04:00
drb-correlation-review: ship, with two bounds the low-bar link rule needs.

1. _call_block emitted only the talkgroup NAME while _inc_summary emits
   numeric tg ids, so the "same talkgroup" precondition in _RULES was
   unevaluable and the low link bar applied unconditionally. _call_block now
   prints "Talkgroup: <name> (id <n>)".
2. ctx["recent"] is an unordered Firestore slice with no order_by; a busy 2h
   window (~40 active incidents) showed the model an arbitrary half of the
   candidates. _prompt_incidents() sorts by updated_at desc before the [:20]
   cap — also makes each row's idle: field monotonic.

+2 tests. Full c2-core suite green (sandboxed venv).

Review follow-ups (not blockers): _parse_response demotes an unresolvable
link to orphan (drops the call) rather than falling back to rules — now on
rising link volume; the 45% tiebreak escalation rate / smart-model cost is
untouched; _ROAD_RE swallows leading tokens so "10 Parker Street" still
won't road-overlap "Parker St".

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Author
Owner

drb-correlation-review: ship, with two bounds landed in 1a631d6 — (1) _call_block now prints the call's talkgroup id so the "same talkgroup" rule is evaluable; (2) _prompt_incidents() sorts the ≤20 candidates by updated_at desc (was an arbitrary Firestore slice). Non-blocking follow-ups noted in the commit. Full suite 305 passed.

drb-correlation-review: **ship, with two bounds landed in `1a631d6`** — (1) `_call_block` now prints the call's talkgroup id so the "same talkgroup" rule is evaluable; (2) `_prompt_incidents()` sorts the ≤20 candidates by `updated_at` desc (was an arbitrary Firestore slice). Non-blocking follow-ups noted in the commit. Full suite 305 passed.
logan merged commit bccb3e0316 into main 2026-09-07 16:59:46 -04:00
Sign in to join this conversation.