Skip to content

fix(audio): stop Soniox writing Urdu in Devanagari — cherry-pick of #310 (bd-bfy69) - #311

Merged
hyasin270 merged 2 commits into
mainfrom
main-cherry-bfy69-1787167160
Aug 19, 2026
Merged

fix(audio): stop Soniox writing Urdu in Devanagari — cherry-pick of #310 (bd-bfy69)#311
hyasin270 merged 2 commits into
mainfrom
main-cherry-bfy69-1787167160

Conversation

@hyasin270

Copy link
Copy Markdown
Contributor

Cherry-pick of #310, merged to develop and live on NIETE staging as 7fc2cfa (bot, sqs-worker, sqs-worker-video, portal — all SUCCESS).

The finding, restated

Soniox is not returning an Urdu-English label. On a real prod session (2026-08-19):

tokens language Soniox assigned
4,926 hi
139 en
0 ur

…and it stored the session-level label as en — the minority language of its own token counts. Fleet-wide those labels have been en, hindi, javanese, sindhi. 67 sessions since 2026-08-11 came back in Devanagari. hi was never in the hints: hints only bias the identifier, they do not restrict it.

Devanagari cannot be drawn — there is no Devanagari font in bot/shared/fonts/. Unlike #307 this cannot be fixed with a fallback face.

Three layers

  1. Do not invite itlanguage_hints now comes from LANGUAGE_OFFER (ur, en) instead of a hardcoded ['en','ur','es','ar','pa','ta']. Callers passing an explicit language (reading assessment) are untouched.
  2. Retry — Devanagari + no caller-pinned language → transcribe once more with a single forced ur hint.
  3. Guarantee — still Devanagari → transliterate to Perso-Arabic, and overwrite the language label to ur so resolveReportLanguage cannot send the report down the Latin arm.

Every layer logs at level='error'. Layer 3 is lossy and says so — a legibility rescue, not a transliterator with a lexicon.

Two commits

  1. 7b2fdd3 — the pick. Clean, no conflicts.
  2. cf4ca83 — the same unrelated ratchet fix that was open as fix(report): Urdu rendered as tofu on prod whenever the STT mislabelled the language — cherry-pick of #307 #309, folded in here so prod takes one deploy rather than two. The logToFile severity ratchet was already red on plain origin/main (391 live vs 389 allowlisted); the photo-gate sweep had been added without re-baselining. Both callsites are genuine error paths and now pass level='error', which is what the gate's own header asks for, rather than growing the grandfathered list. fix(report): Urdu rendered as tofu on prod whenever the STT mislabelled the language — cherry-pick of #307 #309 should be closed as superseded.

Tests on this main-based tree

devanagari-guard 24 · devanagari-transcribe-recovery 14 · hero-report-urdu-font-fallback 20 · hero-report-language-resolution 12 · logger-level 17 — all green, ratchet included.

Closes: bd-bfy69

🤖 Generated with Claude Code

hyasin270 and others added 2 commits August 20, 2026 00:19
…it if it slips through (#310)

Answering the question directly: Soniox is NOT returning an Urdu-English label.
On a real prod session (2026-08-19) it returned 4,926 tokens tagged `hi`, 139
tagged `en`, and NOT ONE tagged `ur`. The session-level label it stored was
'en' — the minority language of its own token counts. Across the fleet the
labels have been 'en', 'hindi', 'javanese' and 'sindhi' on Urdu classroom
audio, and 67 sessions since 2026-08-11 came back written in Devanagari.

That script reaches a coach in the FICO evidence box (HITL R62/R63/R64, with a
screenshot) and a teacher in the report. It cannot be drawn: there is no
Devanagari font in bot/shared/fonts/ and the render container has no system
fonts, so unlike the Urdu tofu of bd-osmk0 this one cannot be fixed by naming
a fallback face.

Three layers, cheapest first.

LAYER 1 — stop inviting it. language_hints was a hardcoded
['en','ur','es','ar','pa','ta']: six languages, four of which this deployment
does not serve. Hints only BIAS Soniox's language identification, they do not
restrict it, and a wide list widens the search — Urdu and Hindi are the same
spoken language, so it settled on Hindi. The default now comes from
LANGUAGE_OFFER (ur, en), the single source of truth for what we serve, rather
than a second hardcoded list that can drift from it. Callers passing an
explicit language (reading assessment) take the single-hint branch and are
untouched.

LAYER 2 — if it arrives anyway, re-transcribe once with a single forced `ur`
hint, which leaves the identifier no room to choose Hindi. Only attempted when
the caller left the language open; a caller that already pinned one would get
the identical answer back.

LAYER 3 — the guarantee. If it STILL arrives, transliterate to Perso-Arabic so
nothing in Devanagari can ever reach a rendered surface. The language label is
overwritten to 'ur' at the same time: it was part of the same wrong answer, and
resolveReportLanguage reads that field to choose the report's script branch, so
leaving it would send an Urdu report down the Latin arm.

Layer 3 is LOSSY and says so loudly — every layer logs at level='error'. Urdu
does not write short vowels, श/ष both fold to ش, ن absorbs ण, and Arabic-origin
spellings are phonetic (تریکے, not طریقے). It is a legibility rescue, not a
transliterator with a lexicon: a coach sees words instead of boxes. The
positional rules matter and are tested — geminates collapse (بچوں not بچچوں),
aspirate geminates too (اچھا not اچچھا), ے is word-final while ی is medial
(میں not مےں), and a medial independent vowel takes a hamza carrier (بتائیے
not بتاااے).

Tests: 38 new, including a mutation pass that breaks each guard and confirms
the matcher goes red. Comments are stripped before any source assertion
(language-protocol §7.1). Coaching suite: 12 failing suites before and after,
zero new; +38 passing.

Refs: bd-bfy69

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…in's ratchet was red before this PR

Unrelated to the report fix; found because it blocked the cherry-pick's CI.
The logger ratchet was already failing on plain origin/main (391 live vs 389
allowlisted, verified by running it on the untouched branch tip) — the
photo-gate sweep was added to main without re-baselining. develop is green
because its allowlist carries these two entries.

Fixed rather than grandfathered: both are genuine error paths an on-caller
would want to see, and the gate's own header says a NEW violation should get
the level, not the allowlist. Live count drops 391 -> 389 and matches the
existing allowlist, so no re-baseline is needed and the backlog does not grow.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@hyasin270
hyasin270 merged commit 043a16b into main Aug 19, 2026
2 checks passed
@hyasin270
hyasin270 deleted the main-cherry-bfy69-1787167160 branch August 19, 2026 19:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant