File size: 6,820 Bytes
b72bff3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
979f3c3
b72bff3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
"""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