Spaces:
Running
Running
| """Tests Sprint 80 โ A.I.7 : sur-normalisation lexicale. | |
| Couvre : | |
| 1. ``compute_lexical_modernization`` : | |
| - Token GT modernisรฉ systรฉmatiquement โ 100 % | |
| - Token GT prรฉservรฉ โ 0 % | |
| - Plusieurs variantes hyp pour un mรชme gt | |
| - Stop-list filtre les tokens | |
| - Casse insensible par dรฉfaut | |
| - Token GT supprimรฉ (lacuna) โ modernisรฉ vers โ | |
| - GT vide โ tokens vide | |
| 2. ``aggregate_lexical_modernization`` : | |
| - Somme correcte sur N docs | |
| 3. ``top_modernized_tokens`` : | |
| - Tri dรฉcroissant par rate | |
| - ``min_total`` filtre les anecdotiques | |
| - Tokens ร 0 % exclus | |
| 4. Rendu HTML : | |
| - Tableau, ``""`` si data None ou aucun modernisรฉ | |
| - Anti-injection | |
| 5. Complรฉtude i18n FR/EN. | |
| """ | |
| from __future__ import annotations | |
| import json | |
| from pathlib import Path | |
| from picarones.evaluation.metrics.lexical_modernization import ( | |
| aggregate_lexical_modernization, | |
| compute_lexical_modernization, | |
| top_modernized_tokens, | |
| ) | |
| from picarones.reports.html.renderers.lexical_modernization import ( | |
| build_lexical_modernization_html, | |
| ) | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # 1. compute_lexical_modernization | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| class TestCompute: | |
| def test_systematic_modernization(self) -> None: | |
| gt = "maistre maistre maistre" | |
| hyp = "maรฎtre maรฎtre maรฎtre" | |
| result = compute_lexical_modernization(gt, hyp) | |
| slot = result["tokens"]["maistre"] | |
| assert slot["n_total"] == 3 | |
| assert slot["n_modernized"] == 3 | |
| assert slot["rate_modernized"] == 1.0 | |
| assert slot["variants"] == {"maรฎtre": 3} | |
| def test_preserved_token(self) -> None: | |
| gt = "nostre nostre" | |
| hyp = "nostre nostre" | |
| result = compute_lexical_modernization(gt, hyp) | |
| slot = result["tokens"]["nostre"] | |
| assert slot["n_total"] == 2 | |
| assert slot["n_modernized"] == 0 | |
| assert slot["rate_modernized"] == 0.0 | |
| def test_partial_modernization(self) -> None: | |
| gt = "maistre maistre maistre maistre" | |
| hyp = "maรฎtre maistre maรฎtre maรฎtre" | |
| result = compute_lexical_modernization(gt, hyp) | |
| slot = result["tokens"]["maistre"] | |
| assert slot["n_total"] == 4 | |
| assert slot["n_modernized"] == 3 | |
| assert slot["rate_modernized"] == 0.75 | |
| def test_multiple_variants(self) -> None: | |
| gt = "veoir veoir veoir" | |
| hyp = "voir voyr voir" | |
| result = compute_lexical_modernization(gt, hyp) | |
| slot = result["tokens"]["veoir"] | |
| assert slot["n_total"] == 3 | |
| assert slot["n_modernized"] == 3 | |
| assert slot["variants"] == {"voir": 2, "voyr": 1} | |
| def test_stop_list_filter(self) -> None: | |
| gt = "maistre le veoir" | |
| hyp = "maรฎtre la voir" | |
| result = compute_lexical_modernization( | |
| gt, hyp, stop_list=["le"], | |
| ) | |
| # ยซ le ยป filtrรฉ, mais maistre et veoir prรฉsents | |
| assert "le" not in result["tokens"] | |
| assert "maistre" in result["tokens"] | |
| assert "veoir" in result["tokens"] | |
| def test_case_insensitive_default(self) -> None: | |
| gt = "Maistre maistre" | |
| hyp = "Maรฎtre maรฎtre" | |
| result = compute_lexical_modernization(gt, hyp) | |
| # Les deux formes sont distinctes en sortie display mais | |
| # appariรฉes correctement en match | |
| assert result["tokens"]["Maistre"]["n_modernized"] == 1 | |
| assert result["tokens"]["maistre"]["n_modernized"] == 1 | |
| def test_deletion_counted_as_modernized(self) -> None: | |
| gt = "maistre veoir" | |
| hyp = "maรฎtre" # veoir manque | |
| result = compute_lexical_modernization(gt, hyp) | |
| # veoir โ โ comptรฉ comme modernisรฉ | |
| slot = result["tokens"]["veoir"] | |
| assert slot["n_modernized"] == 1 | |
| assert "โ " in slot["variants"] | |
| def test_empty_gt(self) -> None: | |
| result = compute_lexical_modernization("", "anything") | |
| assert result["tokens"] == {} | |
| assert result["n_gt_tokens"] == 0 | |
| def test_none_inputs(self) -> None: | |
| result = compute_lexical_modernization(None, None) | |
| assert result["tokens"] == {} | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # 2. aggregate | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| class TestAggregate: | |
| def test_sum_across_docs(self) -> None: | |
| d1 = compute_lexical_modernization( | |
| "maistre maistre", "maรฎtre maรฎtre", | |
| ) | |
| d2 = compute_lexical_modernization( | |
| "maistre", "maรฎtre", | |
| ) | |
| agg = aggregate_lexical_modernization([d1, d2]) | |
| assert agg["tokens"]["maistre"]["n_total"] == 3 | |
| assert agg["tokens"]["maistre"]["n_modernized"] == 3 | |
| assert agg["tokens"]["maistre"]["rate_modernized"] == 1.0 | |
| def test_empty_iterable(self) -> None: | |
| agg = aggregate_lexical_modernization([]) | |
| assert agg["tokens"] == {} | |
| assert agg["n_gt_tokens"] == 0 | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # 3. top_modernized_tokens | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| class TestTop: | |
| def test_sorted_by_rate_desc(self) -> None: | |
| gt = "a a b b c c d d" | |
| hyp = "x x y b z c d d" | |
| # a: 100% (2/2 modernisรฉ), b: 50%, c: 50%, d: 0% | |
| result = compute_lexical_modernization(gt, hyp) | |
| top = top_modernized_tokens(result, n=10) | |
| # a en premier | |
| assert top[0][0] == "a" | |
| # d exclu (0%) | |
| names = [t[0] for t in top] | |
| assert "d" not in names | |
| def test_min_total_filter(self) -> None: | |
| gt = "rare maistre maistre maistre" | |
| hyp = "moderne maรฎtre maรฎtre maรฎtre" | |
| result = compute_lexical_modernization(gt, hyp) | |
| # Avec min_total=2 : rare (1) exclu, maistre (3) conservรฉ | |
| top = top_modernized_tokens(result, min_total=2) | |
| names = [t[0] for t in top] | |
| assert "rare" not in names | |
| assert "maistre" in names | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # 4. Rendu HTML | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| class TestRender: | |
| def test_returns_empty_when_none(self) -> None: | |
| assert build_lexical_modernization_html(None) == "" | |
| def test_returns_empty_when_no_modernizations(self) -> None: | |
| result = compute_lexical_modernization("a b c", "a b c") | |
| # Aucun modernisรฉ | |
| assert build_lexical_modernization_html(result) == "" | |
| def test_renders_table(self) -> None: | |
| result = compute_lexical_modernization( | |
| "maistre veoir", "maรฎtre voir", | |
| ) | |
| html = build_lexical_modernization_html(result) | |
| assert "<table" in html | |
| assert "maistre" in html | |
| assert "maรฎtre" in html | |
| def test_rate_displayed_as_percent(self) -> None: | |
| result = compute_lexical_modernization( | |
| "maistre maistre maistre maistre", | |
| "maรฎtre maistre maรฎtre maรฎtre", | |
| ) | |
| html = build_lexical_modernization_html(result) | |
| # 75% prรฉsent | |
| assert "75%" in html | |
| def test_anti_injection_token(self) -> None: | |
| gt = "<script>alert(1)</script> normal" | |
| hyp = "MODERNIZED normal" | |
| result = compute_lexical_modernization(gt, hyp) | |
| html = build_lexical_modernization_html(result) | |
| assert "<script>alert" not in html | |
| assert "<script>" in html | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # 5. Complรฉtude i18n | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| class TestI18nCompleteness: | |
| def _load(self, lang: str) -> dict: | |
| path = ( | |
| Path(__file__).parent.parent.parent | |
| / "picarones" / "reports" / "i18n" / f"{lang}.json" | |
| ) | |
| return json.loads(path.read_text(encoding="utf-8")) | |
| def test_all_keys_fr(self) -> None: | |
| d = self._load("fr") | |
| for key in ( | |
| "lexmod_title", "lexmod_note", "lexmod_gt_label", | |
| "lexmod_hyp_label", "lexmod_n_label", "lexmod_rate_label", | |
| ): | |
| assert key in d, f"manque clรฉ FR : {key}" | |
| def test_all_keys_en(self) -> None: | |
| d_fr = self._load("fr") | |
| d_en = self._load("en") | |
| for key in d_fr: | |
| if key.startswith("lexmod_"): | |
| assert key in d_en, f"manque clรฉ EN : {key}" | |