Files
anonymisation/tests/unit/test_synthetic_review.py
Domi31tls 7242b5350e fix(detect): labels structurels Nom de jeune fille / Prénom / Ville (#7 #8 #9)
Trois nouveaux patterns cœur dans `_mask_structured_line` pour des
labels génériques qui n'étaient pas couverts par le pipeline kv_value
(le split key:value laissait fuir la valeur quand le label dépassait
les patterns existants `RE_EXTRACT_NOM_NAISSANCE`, `RE_EXTRACT_PRENOM`,
`RE_EXTRACT_VILLE_RESIDENCE`).

`RE_LABEL_NOM_VARIANTES` capture :
- Nom de jeune fille / de famille / de naissance(.)
- Nom d'usage / Nom marital / Nom marié

`RE_LABEL_PRENOM` capture :
- Prénom : / Prénoms : / Prénom de naissance / utilisé(e) / usuel
- Capture jusqu'à fin de ligne pour les énumérations virgulées
  (Prénoms : Sabine, Marie → tout masqué).

`RE_LABEL_VILLE` capture :
- Ville : / Ville de résidence : / Ville de naissance :
- Capture jusqu'à fin de ligne (gère "Saint-Jean-de-Luz",
  "Saint-Denis (974)", composés multi-tokens).

Effets de bord positifs :
- Le bug "Saint-Jean-de-Luz → [ETABLISSEMENT]-de-Luz" est corrigé :
  le matcher `RE_LABEL_VILLE` masque toute la valeur en `[VILLE]`
  AVANT que le gazetteer FINESS Aho-Corasick ne grignote "Saint-Jean".
  Cas 006_trackare_soignants et 008_anesthesie_complete : alignement
  des expected.txt sur cette amélioration.

Choix d'architecture (cf cadrage docs/cadrage-projet-anonymisation.md
section 10.1) : ces labels sont des règles cœur génériques applicables
à tout établissement de santé français. Légitimes en hardcodé. Les
patterns layout-specific (Bordeaux suffixe, CHCB en fin de phrase,
email cassé par force_term) seront branchés via admin_rules dans
l'étape suivante.

Cas 010_fiche_admission_minimale passe désormais (retiré de
KNOWN_FAILURES). Le xfail strict aurait signalé xpass.

Tests : 9 passed, 2 xfailed (avant : 8 passed, 3 xfailed sur
test_synthetic_review).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 22:30:40 +02:00

81 lines
2.5 KiB
Python

#!/usr/bin/env python3
"""
Gate pytest sur le corpus synthétique de revue humaine (couche 2).
Chaque cas dans tests/synthetic_review/cases/ doit produire un texte
identique à expected.txt, satisfaire ses expectations.json et ne révéler
aucune fuite via le LeakScanner.
Les cas listés dans KNOWN_FAILURES sont marqués xfail(strict=True) :
ils sont attendus en échec aujourd'hui car ils révèlent des bugs réels
du moteur. Quand un bug est fixé, le cas correspondant passe → pytest
signale le xpass strict, ce qui force à retirer son entrée de
KNOWN_FAILURES.
"""
from __future__ import annotations
import sys
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parents[2]
if str(ROOT) not in sys.path:
sys.path.insert(0, str(ROOT))
from tools.run_synthetic_review_corpus import ( # noqa: E402
CASES_DIR,
run_case,
)
KNOWN_FAILURES: dict[str, str] = {
"005_bacterio_complete": (
"RPPS avec qualificateur (`RPPS prescripteur :`) non détecté — "
"fuite identifiant médecin."
),
"009_multi_etablissements": (
"Fuites résiduelles : suffixe `de Bordeaux` après "
"[ETABLISSEMENT], CHCB en fin de phrase. À traiter via "
"admin_rules (étape B suivante)."
),
}
def _case_dirs() -> list[Path]:
if not CASES_DIR.exists():
return []
return sorted(path for path in CASES_DIR.iterdir() if path.is_dir())
def _make_param(case_dir: Path) -> "pytest.ParameterSet":
if case_dir.name in KNOWN_FAILURES:
return pytest.param(
case_dir,
id=case_dir.name,
marks=pytest.mark.xfail(
strict=True,
reason=KNOWN_FAILURES[case_dir.name],
),
)
return pytest.param(case_dir, id=case_dir.name)
def test_synthetic_review_inventory():
"""Le corpus doit contenir 10 cas (cible de cadrage produit)."""
assert CASES_DIR.exists(), f"Dossier corpus introuvable : {CASES_DIR}"
case_dirs = _case_dirs()
assert len(case_dirs) == 10, (
f"Attendu 10 cas dans synthetic_review/cases, trouvé {len(case_dirs)} : "
f"{[c.name for c in case_dirs]}"
)
@pytest.mark.parametrize("case_dir", [_make_param(c) for c in _case_dirs()])
def test_synthetic_review_case(case_dir: Path):
result = run_case(case_dir)
assert not result["failures"], (
f"{case_dir.name} : {', '.join(result['failures'])}\n"
f"Diff disponible dans {result['output_dir']}/diff.txt"
)