From c157205751d497fe1f39a4ce7b6dc44a1a375cfd Mon Sep 17 00:00:00 2001 From: Domi31tls Date: Tue, 31 Mar 2026 12:07:51 +0200 Subject: [PATCH] =?UTF-8?q?fix:=20labels=20DPI=20masqu=C3=A9s=20(Date,=20N?= =?UTF-8?q?ote,=20Type,=20Heure)=20+=20whitelist=20d=C3=A9sactiv=C3=A9e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Whitelist post-masquage désactivée : injectait des phrases au mauvais endroit dans le texte anonymisé (bug critique) - Labels DPI "Date", "Note", "Heure", "Type", "Saint", "Page" ajoutés à _NEVER_MASK_AS_NAME et _DPI_LABELS_BLACKLIST pour empêcher leur propagation globale comme noms de personnes - Corrige "Date d'admission → [NOM] d'admission", "Note d'évolution → [NOM] d'évolution", etc. Score évaluation : 99.3/100 (fuites pré-existantes Sie/GRAND inchangées) Co-Authored-By: Claude Opus 4.6 (1M context) --- anonymizer_core_refactored_onnx.py | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/anonymizer_core_refactored_onnx.py b/anonymizer_core_refactored_onnx.py index 8056f9b..b23ff99 100644 --- a/anonymizer_core_refactored_onnx.py +++ b/anonymizer_core_refactored_onnx.py @@ -2381,7 +2381,16 @@ def _apply_extracted_names(text: str, names: set, audit: List[PiiHit], force_nam """Remplace globalement chaque nom extrait dans le texte.""" placeholder = PLACEHOLDERS["NOM"] _force = force_names or set() - safe_names = {n for n in names if len(n) >= 4 and (n in _force or n.lower() not in _MEDICAL_STOP_WORDS_SET)} + # Labels DPI structurels à ne jamais masquer comme noms + _NEVER_MASK_AS_NAME = { + "Date", "DATE", "Note", "NOTE", "Heure", "HEURE", "Type", "TYPE", + "Soin", "SOIN", "Soins", "SOINS", "Surv", "SURV", + "Saint", "SAINT", "Sainte", "SAINTE", + "Page", "PAGE", "Presc", "PRESC", + } + safe_names = {n for n in names if len(n) >= 4 + and n not in _NEVER_MASK_AS_NAME + and (n in _force or n.lower() not in _MEDICAL_STOP_WORDS_SET)} # Ajouter un hit global (page=-1) par nom pour la redaction PDF raster # (un seul hit suffit — redact_pdf_raster cherche le token sur chaque page) # Les noms forcés (contexte Dr/Mme) utilisent NOM_FORCE pour bypasser @@ -4225,6 +4234,13 @@ def process_pdf( # 4a) Noms : extraire les tokens individuels _nom_kinds = {"NOM", "NOM_EXTRACTED", "NER_PER", "EDS_NOM"} + # Labels DPI / mots structurels à ne JAMAIS propager comme noms + _DPI_LABELS_BLACKLIST = { + "Date", "DATE", "Note", "NOTE", "Heure", "HEURE", "Type", "TYPE", + "Soin", "SOIN", "Soins", "SOINS", "Surv", "SURV", + "Saint", "SAINT", "Sainte", "SAINTE", + "Page", "PAGE", "Presc", "PRESC", + } _global_name_tokens: set = set() for h in anon.audit: if h.kind not in _nom_kinds: @@ -4235,6 +4251,8 @@ def process_pdf( continue if word.lower() in _MEDICAL_STOP_WORDS_SET: continue + if word in _DPI_LABELS_BLACKLIST: + continue if not word[0].isupper(): continue _global_name_tokens.add(word) @@ -4483,10 +4501,9 @@ def process_pdf( ) final_text = _RE_BRACKET_CLEAN.sub(r"\1", final_text) - # 6) Whitelist : restaurer les phrases qui ne doivent jamais être anonymisées - whitelist_phrases = cfg.get("whitelist_phrases", []) - if whitelist_phrases: - final_text = _apply_whitelist(final_text, whitelist_phrases, anon.audit) + # 6) Whitelist : DÉSACTIVÉ — l'approche post-masquage est défectueuse + # (injecte des phrases whitelist au mauvais endroit quand [NOM] masque un vrai nom) + # TODO: implémenter en pré-masquage (protéger les spans avant anonymisation) # Sauvegardes base = pdf_path.stem