Closes #1949. Settings offers three notations. Only Respelling substitutes text today; IPA and CMU rows save cleanly, are validated, get a badge and can be toggled on, then get dropped before term matching and are never read again. That much is Phase 1 behaving as designed. The defect is that it was INVISIBLE: "Test a sentence" answered "No entries match — spoken as written" for a term that does match. Not a degraded answer, a wrong one — and it sent the user off to re-type an entry that was already correct, or to convert it to Respelling, where a phoneme string is then read as graphemes. docs/specs/01-expressive-tts.md asked for exactly the opposite: such entries "passed through and flagged 'phoneme not honored on this engine' (parity-rule: visible degradation)". That flag was never implemented. This is it. The dry run reports inert entries separately, and the panel names them. The substitution path is deliberately untouched — this does NOT start feeding raw phoneme strings into the grapheme stream, which is the thing Phase 1 refuses on purpose, and a test pins that it still refuses. Not Phase 2. Lowering IPA/CMU to engine markup is a real feature per engine and stays open; what changes here is that the gap is now honest rather than silent. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017ypcgSsh5j2PEonSJiAU1S
78 lines
2.9 KiB
Python
78 lines
2.9 KiB
Python
"""#1949 — an entry that cannot be applied must say so, not look like no match.
|
|
|
|
Settings offers three notations: Respelling, IPA, CMU. Only respelling
|
|
substitutes text today. IPA and CMU rows save cleanly, are validated, get a
|
|
badge and can be toggled on — then get dropped before term matching, and
|
|
nothing downstream reads them.
|
|
|
|
That is Phase 1 behaving as designed. The defect is that it was INVISIBLE:
|
|
"Test a sentence" answered "No entries match; spoken as written" for a term
|
|
that does match. Not a degraded answer, a wrong one — and it sent the user off
|
|
to re-type an entry that was already correct.
|
|
|
|
docs/specs/01-expressive-tts.md asked for the opposite: such entries "passed
|
|
through and flagged 'phoneme not honored on this engine' (parity-rule: visible
|
|
degradation)". These tests pin that flag.
|
|
"""
|
|
import pytest
|
|
|
|
from services.pronunciation import (
|
|
apply_pronunciation,
|
|
entries_for_language,
|
|
inert_entries_for_language,
|
|
)
|
|
|
|
|
|
def _row(term, replacement, etype, language="*", enabled=1):
|
|
return {
|
|
"term": term,
|
|
"replacement": replacement,
|
|
"type": etype,
|
|
"language": language,
|
|
"enabled": enabled,
|
|
}
|
|
|
|
|
|
def test_a_cmu_entry_is_reported_as_inert():
|
|
rows = [_row("Alphan", "AA0 L P HH AA0 N", "cmu")]
|
|
assert entries_for_language(rows, "tr") == {}
|
|
assert inert_entries_for_language(rows, "tr") == [{"term": "Alphan", "type": "cmu"}]
|
|
|
|
|
|
def test_an_ipa_entry_is_reported_as_inert():
|
|
rows = [_row("gif", "dʒɪf", "ipa")]
|
|
assert inert_entries_for_language(rows, "en") == [{"term": "gif", "type": "ipa"}]
|
|
|
|
|
|
def test_respelling_is_not_reported_because_it_works():
|
|
rows = [_row("gif", "jiff", "respelling")]
|
|
assert inert_entries_for_language(rows, "en") == []
|
|
assert apply_pronunciation("a gif", rows, "en") == "a jiff"
|
|
|
|
|
|
def test_a_disabled_entry_is_not_reported():
|
|
# The user turned it off; saying it was ignored would be noise.
|
|
rows = [_row("gif", "dʒɪf", "ipa", enabled=0)]
|
|
assert inert_entries_for_language(rows, "en") == []
|
|
|
|
|
|
def test_language_scoping_matches_the_applied_path():
|
|
# An entry scoped to another language is not "ignored", it simply does not
|
|
# apply here — reporting it would be misleading in the other direction.
|
|
rows = [_row("gif", "dʒɪf", "ipa", language="de")]
|
|
assert inert_entries_for_language(rows, "en") == []
|
|
assert inert_entries_for_language(rows, "de") == [{"term": "gif", "type": "ipa"}]
|
|
|
|
|
|
def test_the_substitution_itself_is_unchanged():
|
|
# This reports; it must not start feeding raw phoneme strings into the
|
|
# grapheme stream, which is the thing Phase 1 deliberately refuses.
|
|
rows = [_row("Alphan", "AA0 L P HH AA0 N", "cmu")]
|
|
assert apply_pronunciation("Merhaba, ben Alphan.", rows, "tr") == "Merhaba, ben Alphan."
|
|
|
|
|
|
@pytest.mark.parametrize("etype", ["CMU", "Ipa", " ipa "])
|
|
def test_type_matching_tolerates_casing_and_padding(etype):
|
|
rows = [_row("x", "y", etype)]
|
|
assert len(inert_entries_for_language(rows, "en")) == 1
|