Skip to content

fix(audio): stop Soniox writing Urdu in Devanagari, and never render it if it slips through (bd-bfy69) - #310

Merged
hyasin270 merged 1 commit into
developfrom
bd-bfy69-devanagari
Aug 19, 2026
Merged

fix(audio): stop Soniox writing Urdu in Devanagari, and never render it if it slips through (bd-bfy69)#310
hyasin270 merged 1 commit into
developfrom
bd-bfy69-devanagari

Conversation

@hyasin270

Copy link
Copy Markdown
Contributor

Answering the question first

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

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

…and stored the session-level label as en — the minority language of its own token counts. Across the fleet those labels have been en, hindi, javanese and sindhi on Urdu classroom audio. 67 sessions since 2026-08-11 came back written in Devanagari.

Note hi was never in the hints. Hints only bias the language identifier; they do not restrict it.

Why it must never render

There is no Devanagari font in bot/shared/fonts/, and the render container has no system fonts. Unlike the Urdu tofu in #307, this cannot be fixed by naming a fallback face — we do not ship the face. It reaches a coach in the FICO evidence box (R62/R63/R64, with a screenshot) and a teacher in the report.

Three layers, cheapest first

  1. Do not invite it. language_hints was a hardcoded ['en','ur','es','ar','pa','ta'] — six languages, four of which this deployment does not serve. The default now comes from LANGUAGE_OFFER (ur, en), the single source of truth, 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.
  2. Retry. Devanagari present and the caller left the language open → transcribe once more with a single forced ur hint, leaving the identifier no room to choose Hindi.
  3. The guarantee. Still Devanagari → transliterate to Perso-Arabic. 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.

Every layer logs at level='error'.

Layer 3 is lossy, and says so

It is a legibility rescue, not a transliterator with a lexicon — a coach sees words instead of boxes. Urdu does not write short vowels; श/ष both fold to ش; ण and न both fold to ن; Arabic-origin spellings come out phonetic (تریکے, not طریقے). The positional rules are where the quality actually lives, and each is tested:

rule without it with it
geminate collapse بچچوں بچوں
aspirate geminate اچچھا اچھا
ے final vs ی medial مےں میں
medial vowel takes a hamza بتاااے بتائیے

Tests

38 new. Includes a mutation pass that breaks each guard in memory and confirms the matcher goes red — no vacuous assertions. Comments are stripped before any source assertion (language-protocol §7.1).

Coaching suite: 12 failing suites before and after, zero new; +38 passing. circular-deps and unresolved-requires fail identically on untouched develop.

Refs: bd-bfy69

🤖 Generated with Claude Code

…it if it slips through

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>
@hyasin270
hyasin270 merged commit 7fc2cfa into develop Aug 19, 2026
2 checks passed
@hyasin270
hyasin270 deleted the bd-bfy69-devanagari branch August 19, 2026 19:06
hyasin270 added a commit that referenced this pull request Aug 19, 2026
 (bd-bfy69) (#311)

* fix(audio): stop Soniox writing Urdu in Devanagari, and never render 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>

* chore(logger): the two photo-gate ❌ calls now pass level='error' — main'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>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
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