STT quality initiative: raw transcription accuracy is the ceiling every downstream fix (correction, extraction, correlation) is hostage to — research + candidate angles #163
Open
opened 2026-09-20 21:56:50 -04:00 by logan
·
0 comments
No Branch/Tag Specified
Labels
Clear labels
area/ai-pipeline
area/auth
area/billing
area/correlation
area/discord
area/frontend
area/governance
area/growth
area/infra-deploy
area/legal
area/map
area/mqtt
area/node-ota
area/osint
area/recording-audio
area/sdr-radio
area/transcription
board
board:agenda
minutes:draft
minutes:final
role:ceo
role:cfo
role:cio
role:ciso
role:cmo
role:coo
role:cto
Scene/entity extraction, summarizer, AI providers, embeddings
AuthN/Z, roles, tenancy/org scoping, API keys, credentials
Plans, entitlements, AI spend accounting and caps
Incident correlation, linking, lifecycle, severity, corr_debug
Discord bot, radio bots, voice streams, bot tokens, webhooks
Web UI: Next.js app and the edge-node local dashboard
Board process, minutes, unattended runner, agent tooling
Go-to-market, outreach, cold email, discovery conversations
CI/CD, VM, registry, Firestore rules/indexes deploy, Terraform/Caddy
ToS, retention, licences/DAAs, CAN-SPAM, E&O, name suppression
Live map: pins, paths, tiles, legend, geocoding
Node-C2 MQTT, checkins, commands, node sweeper, dynsec
Node install, enrollment, OTA updater, image skew
External feeds: cameras, 511, ADS-B, AIS, outages, and their licences
Call capture, boundaries, PulseAudio, ring buffer, trimming, attribution
OP25 decode, SDR hardware/tuning, conventional, second SDR, mesh radio
STT, transcript correction, place verification, name accuracy
Board process: minutes and board-filed items
Needs board scoping at the next sitting; CEO removes the label once scoped
One exec pre-read, closed by CEO
CEO ruling; open until enacted
Filed by the CEO
Filed by the CFO
Filed by the CIO
Filed by the CISO
Filed by the CMO
Filed by the COO
Filed by the CTO
kind
behavior-bug
Wrong product behavior; the instrument reads it correctly
kind
measurement-bug
Corrupts the recording/instrument (debug data, evals, health checks), not behavior
priority
p0
Drop everything: prod broken, live exposure, or owner-ruled P0
priority
p1
Next up: blocks a gate, a customer, or core incident quality
priority
p2
Normal: real defect or planned work, schedule in turn
priority
p3
Low: minor, cosmetic, or someday
status
blocked
Waiting on an external party, spend, or another issue
status
in-progress
Branch or PR open against it
status
needs-decision
Waiting on an owner or board ruling before work
status
needs-info
Root cause or repro unknown; needs a reading first
type
bug
Something built is wrong: code, data, or a measurement
type
chore
Maintenance, refactor, tests, pinning, cleanup: no user-visible change
type
decision
A ruling to make or record: board minutes, owner calls, legal reviews
type
docs
Docs, runbooks, CLAUDE.md, skills, SOPs
type
feature
New capability or an extension of an existing one
type
security
Security, privacy, credential or tenant-isolation exposure
type
tracking
Tracker, checklist, initiative or punch-list spanning several changes
Milestone
No items
No Milestone
Projects
Clear projects
No projects
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: logan/server-26#163
Reference in New Issue
Block a user
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.
Prompted by server-26#162 (transcript_correction.py silently swapping ten-codes) and the underlying observation that drove it: raw Whisper (
whisper-1) output on this project's P25 audio contains real, non-code mishearings — "Holmes Street and 4th and Rowe" for "Home Street and Forest Ave", "Oklahoma City" for "10-4 Central", "That was a heavy centrifuge" (no clear real referent), "Someone's wearing an alleyway" — that a post-hoc correction pass can only partially and riskily fix. #162 made the correction pass safer; it does not make the underlying transcription more accurate. This issue is the research pass on the actual STT layer, not the correction layer.Current implementation (
drb-c2-core/app/internal/transcription.py): OpenAI hostedwhisper-1,language="en",temperature=0, a fixed generic priming prompt (deliberately excludes real per-system vocabulary — documented reason:whisper-1treats the prompt as a transcription prior and echoes vocabulary into silence/noise, which is how the "10-4. 10-5. 10-6…" hallucination bug happened previously).verbose_jsongives per-segmentno_speech_prob, which_is_degenerate()/the silence filter both depend on for hallucination rejection. Audio reaching Whisper is already lossless FLAC at capture's native 22050 Hz (deliberately not pre-resampled — correct, already reasoned through incall_recorder.py's comments) — this part of the pipeline is already solid, not a target for rework.The floor under all of this: P25 uses the IMBE vocoder (4400 bps voice + 2800 bps FEC). Published vocoder research is blunt about it — intelligibility "starts to degrade" as SNR approaches 0 dB and "degrades significantly" below it. No STT model, ours or anyone's, recovers information the vocoder itself never encoded. Some of what reads as "bad STT" may be weak-signal reception at the antenna, not a model problem — worth ruling in or out before spending on the model side (see Angle 5).
Angle 1 — Switch hosted model:
gpt-4o-transcribe/gpt-4o-mini-transcribeOpenAI's own published benchmark (Robust Speech Bench, noisy conditions):
gpt-4o-mini-transcribe0.046 WER,gpt-4o-transcribe0.047 WER, vswhisper-10.053 WER. Real, quantified, and already supported inconfig.py'sstt_modelenum — but not a drop-in swap:verbose_json(segment timestamps +no_speech_prob) iswhisper-1-only per OpenAI's API (already noted in a comment intranscription.py). Switching means_is_degenerate()and the leading/trailing-silence hallucination filters lose their input entirely and need an equivalent built for plainjsonoutput, or the switch trades one failure mode (mishearings) for another (unfiltered hallucinations). Also untested: whether these newer models have the same "prompt-as-continuation" hallucination risk that made this project exclude real vocabulary from the Whisper prompt — they're a different underlying architecture (GPT-4o audio, not Whisper's encoder-decoder) and may not inherit that failure mode, which would reopen vocabulary-primed prompting as an option. Needs an A/B eval on real saved DRB call audio before committing, not just trusting the published benchmark (which isn't P25-specific).Angle 2 — Self-hosted, larger Whisper (large-v3 / large-v3-turbo)
The closest real prior art found (a hobbyist project transcribing P25 police scanner audio directly, not through this codebase) used local Whisper
large-v3, thenlarge-v3-turbofor speed — not the hostedwhisper-1API, which is roughlylarge-v2-class. Their explicit finding: street names were still the main error class even on the bigger model ("would be nice to fine-tune the model with the streets of my city" — never implemented). Self-hosting removes per-minute OpenAI cost and may raise the accuracy ceiling further than Angle 1, but is a real infra commitment (GPU capacity, a service to run and keep up, deploy/ops ownership) — this is a CIO-scale decision, not a code change, and shouldn't be started without CIO sign-off on the infra cost/ops tradeoff.Angle 3 — In-model vocabulary/keyword boosting (Deepgram Nova-3, AssemblyAI, others)
This is the angle that most directly targets the root cause of #162's whole incident: Whisper has no safe way to bias toward known local vocabulary (the project already tried and rejected prompt-based priming for exactly this reason), which pushed all vocabulary correction into a post-hoc LLM rewrite pass that turned out to be unconstrained enough to swap real ten-codes. Deepgram Nova-3's "Keyterm Prompting" is built into the model's decoding, not a prompt hack: up to 100 terms per request, the model biases decoding toward them "when the acoustic evidence supports them" (i.e., it isn't supposed to have Whisper's continue-the-pattern failure mode), with a vendor-published "up to 625% uplift" in correct recognition for boosted terms, at roughly $0.26/hr batch pricing. Feeding it this system's own
local_knowledge/vocabulary/unit-callsign list as keyterms could shrink transcription-time mishearings directly, reducing how much weight the correction pass has to carry (and how much room it has to do damage). AssemblyAI has an equivalent word-boost feature.Real, load-bearing caveat: published research on P25 specifically found AWS Transcribe and Azure Speech both produced "largely gibberish" on this exact audio type — general-purpose ASR vendors do not uniformly handle vocoded radio well, and none of the vendor benchmarks above were measured on P25 audio. Deepgram/AssemblyAI's fit for this project is unverified and must be tested on real saved DRB call samples before any commitment, not assumed from generic noisy-office/phone-call benchmarks.
Angle 4 — Audio preprocessing (denoise, gain normalization) before STT
Mixed evidence, explicitly NOT a free win: one recent study (SAM-Audio preprocessing vs raw noisy speech) found denoising consistently increased WER/CER despite improving signal-level/perceptual quality — a generic denoiser can remove information an ASR model was actually using. If this is pursued at all, it needs controlled A/B testing on DRB's own vocoded audio (denoised vs raw, same clips, compare transcripts against a human-verified reference), not a blind pipeline addition.
Angle 5 — The RF/decode side ("better radio audio" upstream of any software fix)
P25/IMBE intelligibility falls off a cliff below 0 dB SNR. That's a receive-chain property: SDR antenna placement/gain, OP25 discriminator tap and squelch settings, distance/terrain to the transmitter. No STT model change fixes audio that was already unintelligible at the antenna. Worth a dedicated, separate look at node-002's current OP25 gain/squelch config and whether weak-signal talkgroups (vs strong ones) correlate with the worst transcripts — if they do, the highest-leverage fix might be a fleet/RF change, not a model change. Out of scope for this issue to resolve; flagging so it isn't missed while everyone's attention is on the software side.
Angle 6 — Already solid, do not rework
FLAC lossless capture at the native sample rate (fixed from a prior lossy-MP3 mistake, well-reasoned in
call_recorder.py's own comments), the single resample happening inside Whisper rather than client-side,language="en"+temperature=0, and the existing_is_degenerate()/no_speech_probhallucination filtering are all sound. Nothing here should be touched as part of this initiative.Recommended sequencing (not a ruling — flagging for a CTO/board call given the cost and infra tradeoffs across angles 1–3)
gpt-4o-transcribe/mini) against currentwhisper-1on that set, including designing the non-verbose_jsonhallucination-filter replacement it would need.Related: #36 (per-talkgroup transcript correction — appears already implemented, flagged separately) and #6/#159 (geocode_max_km rejecting legitimate far-talkgroup results — fixed, same underlying "no anchor / no reference point" family of gap this project keeps hitting on the AI pipeline's edges).
Sources consulted (web research, 2026-09-21, general ASR benchmarks — none P25-specific except where noted):