fix: labels DPI masqués (Date, Note, Type, Heure) + whitelist désactivée

- 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) <noreply@anthropic.com>
This commit is contained in:
2026-03-31 12:07:51 +02:00
parent 4d33610655
commit c157205751

View File

@@ -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