84 of 100 incidents are titled "Ems — TGID 9048" because /upload never resolves the talkgroup name #34

Closed
opened 2026-08-23 03:20:35 -04:00 by logan · 1 comment
Owner

84 of 100 incidents in the 2026-08-23 correlation dump are named "Ems — TGID 9048", "Other — TGID 9600", "Police — TGID 9563" — the fallback title, not a description of anything. Only 16 got a real name like "Medical Assistance at Noel Drive". The title is the primary label everywhere: incident list rows, map pins, Discord alerts.

_create_incident (internal/incident_correlator.py:1832-1849) builds it from two inputs:

tg_label = talkgroup_name or (f"TGID {talkgroup_id}" if talkgroup_id else "Unknown Talkgroup")
content_tags = [t for t in tags if t != "auto-generated"]
primary_tag  = _tag_to_title(content_tags[0]) if content_tags else None
title = _compose_title(primary_tag, location, tg_label) if primary_tag else f"{_tag_to_title(incident_type)} — {tg_label}"

Both inputs are failing, but the second is the cheap fix:

talkgroup_name arrives as None on the correlation path, and nobody looks it up. It is a plain multipart form field on /upload (routers/upload.py:24), forwarded straight through _run_intelligence_pipeline into the correlator. The edge node only sends it when OP25 supplied a tgid_name (call_recorder.py:752 — if talkgroup_name:), which is exactly the case that fails for talkgroups missing from the loaded tags file.

The lookup that fixes it already exists — in the other path. internal/mqtt_handler.py:222-231 resolves it on call_start:

tgid_name = payload.get("tgid_name") or ""
if not tgid_name and system_id and payload.get("tgid"):
    system_doc = await fstore.doc_get_cached("systems", system_id)
    for tg in system_doc.get("config", {}).get("talkgroups", []):
        if int(tg.get("id", -1)) == tgid_int:
            tgid_name = tg.get("name", "")

So the call document already holds the right name (written at call_start), and /upload fetches that very document a few lines earlier for the dedup check — then ignores its talkgroup_name in favour of the empty form field. C2 owns the systems collection with all 125 talkgroup definitions; it should never be titling an incident TGID 9048 while Ossining - Police Dispatch sits in the system config.

Fixing just this turns 84 titles from "Ems — TGID 9048" into "Ems — Ossining Police Dispatch". Still not a description, but it names a place instead of an integer.

The remaining half is extraction. content_tags is empty on those same 84, so intelligence.py returned no usable tag for the founding call. That is a prompt/extraction problem and deserves its own pass — worth measuring again once the talkgroup name is fixed, since some of these calls are genuinely content-free radio chatter that should arguably not be opening an incident at all (see server-26#5).

Fix shape:

  • Extract the mqtt_handler lookup into a shared internal/talkgroups.py helper.
  • Call it from /upload, preferring the form value, then the call doc, then the system config.
  • Use it in mqtt_handler too so there is one implementation.
84 of 100 incidents in the 2026-08-23 correlation dump are named `"Ems — TGID 9048"`, `"Other — TGID 9600"`, `"Police — TGID 9563"` — the fallback title, not a description of anything. Only 16 got a real name like `"Medical Assistance at Noel Drive"`. The title is the primary label everywhere: incident list rows, map pins, Discord alerts. `_create_incident` (`internal/incident_correlator.py:1832-1849`) builds it from two inputs: ```python tg_label = talkgroup_name or (f"TGID {talkgroup_id}" if talkgroup_id else "Unknown Talkgroup") content_tags = [t for t in tags if t != "auto-generated"] primary_tag = _tag_to_title(content_tags[0]) if content_tags else None title = _compose_title(primary_tag, location, tg_label) if primary_tag else f"{_tag_to_title(incident_type)} — {tg_label}" ``` Both inputs are failing, but the second is the cheap fix: **`talkgroup_name` arrives as None on the correlation path, and nobody looks it up.** It is a plain multipart form field on `/upload` (`routers/upload.py:24`), forwarded straight through `_run_intelligence_pipeline` into the correlator. The edge node only sends it when OP25 supplied a `tgid_name` (`call_recorder.py:752` — `if talkgroup_name:`), which is exactly the case that fails for talkgroups missing from the loaded tags file. The lookup that fixes it **already exists** — in the other path. `internal/mqtt_handler.py:222-231` resolves it on `call_start`: ```python tgid_name = payload.get("tgid_name") or "" if not tgid_name and system_id and payload.get("tgid"): system_doc = await fstore.doc_get_cached("systems", system_id) for tg in system_doc.get("config", {}).get("talkgroups", []): if int(tg.get("id", -1)) == tgid_int: tgid_name = tg.get("name", "") ``` So the **call document already holds the right name** (written at call_start), and `/upload` fetches that very document a few lines earlier for the dedup check — then ignores its `talkgroup_name` in favour of the empty form field. C2 owns the systems collection with all 125 talkgroup definitions; it should never be titling an incident `TGID 9048` while `Ossining - Police Dispatch` sits in the system config. Fixing just this turns 84 titles from `"Ems — TGID 9048"` into `"Ems — Ossining Police Dispatch"`. Still not a description, but it names a place instead of an integer. **The remaining half is extraction.** `content_tags` is empty on those same 84, so `intelligence.py` returned no usable tag for the founding call. That is a prompt/extraction problem and deserves its own pass — worth measuring again once the talkgroup name is fixed, since some of these calls are genuinely content-free radio chatter that should arguably not be opening an incident at all (see server-26#5). Fix shape: - Extract the mqtt_handler lookup into a shared `internal/talkgroups.py` helper. - Call it from `/upload`, preferring the form value, then the call doc, then the system config. - Use it in `mqtt_handler` too so there is one implementation.
Author
Owner

Fixed in 039a06d.

internal/talkgroups.py is now the single resolver — caller hint, then the call document, then the system config — and both mqtt_handler (call_start) and _run_intelligence_pipeline (the funnel /upload and /calls/{id}/reprocess share) use it. The resolved name is written back to the call document when that was what was missing, so the archive and orphan panel stop showing bare TGIDs as well.

12 unit tests pin the preference order in tests/test_talkgroups.py; full c2-core suite is 183 green.

The extraction half — content_tags empty on those same 84 incidents — is untouched and belongs with server-26#5. Worth re-measuring the fallback-title rate on the next AI window now that the label is real.

Fixed in 039a06d. `internal/talkgroups.py` is now the single resolver — caller hint, then the call document, then the system config — and both `mqtt_handler` (call_start) and `_run_intelligence_pipeline` (the funnel /upload and /calls/{id}/reprocess share) use it. The resolved name is written back to the call document when that was what was missing, so the archive and orphan panel stop showing bare TGIDs as well. 12 unit tests pin the preference order in `tests/test_talkgroups.py`; full c2-core suite is 183 green. The extraction half — `content_tags` empty on those same 84 incidents — is untouched and belongs with server-26#5. Worth re-measuring the fallback-title rate on the next AI window now that the label is real.
logan closed this issue 2026-08-23 03:24:14 -04:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: logan/server-26#34