test(review): étendre couche 2 à 10 cas et brancher gate pytest avec xfail strict
Couche 2 (revue humaine sur documents complets) : ajout de 6 cas
synthétiques pour atteindre la cible cadrage produit (10 cas).
Cas ajoutés :
- 005_bacterio_complete : layout BACTERIO N° venue rejeté avant IPP
+ RPPS prescripteur (pattern qualifié non détecté).
- 006_trackare_soignants : export Trackare avec activités HH:MM NOM,
Note IDE/médicale, Signé — médicament greedy.
- 007_lettre_sortie_complete : courrier médecin→médecin, multi-villes,
email institutionnel @chcb.fr (cassé par le force_term CHCB).
- 008_anesthesie_complete : protocole anesthésique avec molécules
BDPM, prénoms basques rares (Maddi, Pantxoa).
- 009_multi_etablissements : 3 établissements distincts (CHCB, CHU
Bordeaux, Clinique Aguilera), prénoms basques avec ñ (Beñat).
- 010_fiche_admission_minimale : fiche administrative dense, labels
variés (Nom de jeune fille :, Prénom :, Ville :, Mutuelle :).
Gate pytest (tests/unit/test_synthetic_review.py) :
- vérifie l'inventaire (10 cas) et fait passer chaque cas via run_case.
- 3 cas marqués xfail(strict=True) pour révéler 9 fuites de PII et
2 patterns partiels que le moteur ne couvre pas aujourd'hui :
* 005 — RPPS avec qualificateur (RPPS prescripteur :)
* 009 — Bordeaux résiduel après [ETAB], CHCB en fin de phrase,
Biarritz sur ligne Ville :, ñ qui casse Beñat → [NOM]ñat
* 010 — Nom de jeune fille / Prénom / Ville sans label "Patient :",
NIR au format espacé partiellement consommé en TEL,
numéro de mutuelle MGEN non couvert
- xfail strict force pytest à signaler un xpass quand un fix passe :
rappel automatique de retirer l'entrée de KNOWN_FAILURES.
Le runner tools/run_synthetic_review_corpus.py reste utilisable en
direct (sortie diff/audit/summary) pour la revue humaine. Les sorties
actual/ sont gitignorées (régénérées à chaque exécution).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
85
tests/unit/test_synthetic_review.py
Normal file
85
tests/unit/test_synthetic_review.py
Normal file
@@ -0,0 +1,85 @@
|
||||
#!/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": (
|
||||
"Plusieurs fuites : suffixe `de Bordeaux` après [ETABLISSEMENT], "
|
||||
"CHCB en fin de phrase, Biarritz sur ligne `Ville :`, caractère "
|
||||
"`ñ` qui casse Beñat → [NOM]ñat."
|
||||
),
|
||||
"010_fiche_admission_minimale": (
|
||||
"Labels `Nom de jeune fille :`, `Prénom :`, `Ville :` non "
|
||||
"couverts — ELIZONDO, Sabine, Bayonne fuient. NIR au format "
|
||||
"espacé partiellement masqué (consommé en TEL)."
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
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"
|
||||
)
|
||||
Reference in New Issue
Block a user