fix(detect): F5 — masque la continuation orpheline d'un nom composé (EJNAINI)

Dernière fuite de l'audit_30. Cas Trackare : un nom composé "NOCENT-EJNAINI"
éclaté en colonnes devient "[NOM]-\nEJNAINI" — le 1er composant est masqué
par le NER mais le 2e reste en clair (ni span NER intact ni candidat regex ne
le couvre ; être dans paranames ne suffit pas sans candidat).

Fix : post-passe dans process_pdf (étape 3a-bis), après selective_rescan, qui
masque le token majuscule orphelin suivant immédiatement un "[NOM]-". Couvre
le texte ET le raster (NOM_GLOBAL). Réfute la conclusion de Qwen ("paranames
résoudra EJNAINI").

Validation audit_30 (29 docs) : score 98.3 → 98.5/100, LEAK SCORE 100/100
(0 fuite), 0 régression FP. tests/unit 85 passed. BA127127 : EJNAINI 7→0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-03 12:02:53 +02:00
parent ae73abe65d
commit 33543b6e2b
2 changed files with 90 additions and 0 deletions

View File

@@ -4502,6 +4502,21 @@ def process_pdf(
# 3) Rescan selectif
final_text = selective_rescan(final_text, cfg=cfg)
# 3a-bis) Nettoyage post-masquage : continuation orpheline d'un nom composé
# coupé par saut de ligne. Cas Trackare en colonnes : "NOCENT-EJNAINI"
# est éclaté en "[NOM]-\nEJNAINI" → le 1er composant est masqué par le
# NER mais le 2e (token majuscule orphelin juste après "[NOM]-") reste
# en clair, car ni span NER intact ni candidat regex ne le couvre.
# On masque ce token dans le texte ET via NOM_GLOBAL (raster).
_re_nom_orphan = re.compile(r"(\[NOM\]-\s*\n?\s*)([A-ZÀ-Ÿ][A-ZÀ-Ÿ'\-]{3,})\b")
def _clean_nom_orphan(m):
tok = m.group(2)
if tok.lower() in _MEDICAL_STOP_WORDS_SET:
return m.group(0)
anon.audit.append(PiiHit(-1, "NOM_GLOBAL", tok, PLACEHOLDERS["NOM"]))
return m.group(1) + PLACEHOLDERS["NOM"]
final_text = _re_nom_orphan.sub(_clean_nom_orphan, final_text)
# 3b) Nettoyage post-masquage : codes postaux orphelins (5 chiffres collés à un placeholder)
# et téléphones fragmentés sur plusieurs lignes
_re_cp_orphan = re.compile(r"(\[(?:ADRESSE|NOM|VILLE)\])\s*(\d{5})\b")