Picarones / tests /measurements /test_sprint78_equivalence_profile.py
Claude
test: rΓ©organiser les 110 fichiers tests/test_*.py par cercle architectural
d109222 unverified
Raw
History Blame
6.82 kB
"""Tests Sprint 78 β€” A.I.5 : Γ©quivalences diplomatiques en curseur fin.
Couvre :
1. Catalogue ``BUILTIN_EQUIVALENCES`` :
- Au moins une règle par profil
- Règles canoniques nommées (longs_s, u_eq_v, etc.)
- Pas de noms doublons
2. ``list_equivalences_by_profile`` :
- Sans filtre → toutes les règles
- Avec filtre → règles du profil seulement
3. ``apply_selected_equivalences`` :
- Application sΓ©lective : seul ``longs_s`` actif β†’ ΕΏβ†’s mais
pas u→v
- Liste vide β†’ pas de changement
- Texte vide / None β†’ ``""``
- Règle inconnue silencieusement ignorée
4. ``compute_cer_with_equivalences`` :
- Sans Γ©quivalences : CER Γ©levΓ©
- Avec les bonnes Γ©quivalences : CER baisse
- Application bilatΓ©rale (GT et hyp)
"""
from __future__ import annotations
from picarones.measurements.equivalence_profile import (
BUILTIN_EQUIVALENCES,
EquivalenceRule,
apply_selected_equivalences,
compute_cer_with_equivalences,
list_equivalences_by_profile,
)
# ──────────────────────────────────────────────────────────────────────────
# 1. Catalogue
# ──────────────────────────────────────────────────────────────────────────
class TestCatalog:
def test_canonical_rules_present(self) -> None:
for name in (
"longs_s", "u_eq_v", "i_eq_j", "y_eq_i", "vv_eq_w",
"ae_ligature", "oe_ligature", "thorn_th", "eth_th",
"yogh_y",
):
assert name in BUILTIN_EQUIVALENCES, (
f"règle canonique manquante : {name}"
)
def test_rule_structure(self) -> None:
for rule in BUILTIN_EQUIVALENCES.values():
assert isinstance(rule, EquivalenceRule)
assert rule.name
assert rule.source
assert rule.target
assert rule.description
assert rule.profile_tag
def test_unique_names(self) -> None:
names = list(BUILTIN_EQUIVALENCES.keys())
assert len(names) == len(set(names))
def test_longs_s_correct(self) -> None:
rule = BUILTIN_EQUIVALENCES["longs_s"]
assert rule.source == "ΕΏ"
assert rule.target == "s"
# ──────────────────────────────────────────────────────────────────────────
# 2. list_equivalences_by_profile
# ──────────────────────────────────────────────────────────────────────────
class TestListByProfile:
def test_no_filter_returns_all(self) -> None:
all_rules = list_equivalences_by_profile()
assert len(all_rules) == len(BUILTIN_EQUIVALENCES)
def test_filter_by_medieval_french(self) -> None:
rules = list_equivalences_by_profile("medieval_french")
assert all(r.profile_tag == "medieval_french" for r in rules)
assert len(rules) > 0
def test_unknown_profile_returns_empty(self) -> None:
rules = list_equivalences_by_profile("nonexistent")
assert rules == []
# ──────────────────────────────────────────────────────────────────────────
# 3. apply_selected_equivalences
# ──────────────────────────────────────────────────────────────────────────
class TestApply:
def test_selective_longs_s_only(self) -> None:
result = apply_selected_equivalences("ΕΏeparare", ["longs_s"])
assert result == "separare"
def test_selective_excludes_unselected(self) -> None:
# u_eq_v non sΓ©lectionnΓ© β†’ "u" doit rester
result = apply_selected_equivalences("ΕΏupra", ["longs_s"])
assert result == "supra"
def test_multiple_selected(self) -> None:
# Avec plusieurs règles, toutes appliquées
result = apply_selected_equivalences(
"ΕΏupra", ["longs_s", "u_eq_v"],
)
# ſ→s puis u→v → "svpra"
assert "ΕΏ" not in result
def test_empty_selection_unchanged(self) -> None:
assert apply_selected_equivalences("ΕΏeparare", []) == "ΕΏeparare"
def test_empty_text(self) -> None:
assert apply_selected_equivalences("", ["longs_s"]) == ""
assert apply_selected_equivalences(None, ["longs_s"]) == ""
def test_unknown_rule_ignored(self, caplog) -> None:
result = apply_selected_equivalences(
"ΕΏeparare", ["longs_s", "nonexistent_rule"],
)
# longs_s appliqué, règle inconnue ignorée
assert result == "separare"
# ──────────────────────────────────────────────────────────────────────────
# 4. compute_cer_with_equivalences
# ──────────────────────────────────────────────────────────────────────────
class TestComputeCer:
def test_cer_drops_with_equivalences(self) -> None:
gt = "ΕΏeparare"
hyp = "separare"
cer_no_eq = compute_cer_with_equivalences(gt, hyp, [])
cer_with_eq = compute_cer_with_equivalences(gt, hyp, ["longs_s"])
assert cer_no_eq > 0
assert cer_with_eq == 0.0
def test_bilateral_application(self) -> None:
# Les deux cΓ΄tΓ©s sont normalisΓ©s : si gt et hyp se
# neutralisent par la règle, CER = 0
gt = "ΕΏupra" # avec ΕΏ
hyp = "ΕΏupra" # avec ΕΏ aussi
cer = compute_cer_with_equivalences(gt, hyp, ["longs_s"])
assert cer == 0.0
def test_unrelated_diff_remains(self) -> None:
# DiffΓ©rence indΓ©pendante des Γ©quivalences sΓ©lectionnΓ©es
gt = "ΕΏalpha"
hyp = "ΕΏbeta"
cer = compute_cer_with_equivalences(gt, hyp, ["longs_s"])
# ΕΏ β†’ s appliquΓ© aux deux : "salpha" vs "sbeta" β†’ CER > 0
assert cer > 0
def test_empty_inputs(self) -> None:
assert compute_cer_with_equivalences("", "", ["longs_s"]) == 0.0