feat: whitelist phrases + panneau paramètres avancés dans la GUI
- Nouvelle section whitelist_phrases dans dictionnaires.yml : phrases qui ne doivent jamais être anonymisées (FP récurrents) - Fonction _apply_whitelist : restaure les phrases whitelistées après anonymisation, même si des mots ont été remplacés par des placeholders - GUI : section "Paramètres avancés" repliable avec : - Zone texte whitelist (phrases à exclure) - Zone texte blacklist (mots à toujours masquer) - Bouton sauvegarder → persiste dans le YAML - Phrases initiales : "classification internationale", "prise en charge", "bas de contention", "date de naissance", "code postal", etc. Score évaluation maintenu à 100.0/100 (A+) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3106,6 +3106,49 @@ def _mask_ville_gazetteers(text: str) -> tuple:
|
||||
return "".join(result), masked_originals
|
||||
|
||||
|
||||
# ----------------- Whitelist (phrases à ne jamais anonymiser) -----------------
|
||||
|
||||
def _apply_whitelist(text: str, phrases: List[str], audit: List[PiiHit]) -> str:
|
||||
"""Restaure les phrases whitelistées qui ont été masquées à tort.
|
||||
|
||||
Pour chaque phrase de la whitelist, construit un pattern flexible qui
|
||||
accepte des placeholders [XXX] entre les mots originaux.
|
||||
Ex: "bas de contention" matche "bas [NOM] contention" ou "bas de [NOM]".
|
||||
"""
|
||||
_PH = r"\[[A-Z_]+\]" # placeholder pattern
|
||||
|
||||
for phrase in phrases:
|
||||
if not phrase or not phrase.strip():
|
||||
continue
|
||||
words = phrase.strip().split()
|
||||
if len(words) < 2:
|
||||
continue
|
||||
|
||||
# Construire un pattern où chaque mot de la phrase peut être
|
||||
# remplacé par un placeholder OU être présent tel quel.
|
||||
# Entre les mots : espace(s) optionnel(s)
|
||||
parts = []
|
||||
for w in words:
|
||||
# Le mot original OU un placeholder
|
||||
parts.append(rf"(?:{re.escape(w)}|{_PH})")
|
||||
# Joindre avec des espaces flexibles
|
||||
pattern = r"(?i)" + r"[\s]+".join(parts)
|
||||
|
||||
try:
|
||||
rx = re.compile(pattern)
|
||||
except re.error:
|
||||
continue
|
||||
|
||||
for m in rx.finditer(text):
|
||||
matched = m.group(0)
|
||||
# Ne restaurer que si au moins un placeholder est présent
|
||||
# (sinon la phrase est déjà intacte, pas besoin de toucher)
|
||||
if "[" in matched:
|
||||
text = text[:m.start()] + phrase + text[m.end():]
|
||||
|
||||
return text
|
||||
|
||||
|
||||
# ----------------- Selective safety rescan -----------------
|
||||
|
||||
def selective_rescan(text: str, cfg: Dict[str, Any] | None = None) -> str:
|
||||
@@ -4038,6 +4081,11 @@ 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)
|
||||
|
||||
# Sauvegardes
|
||||
base = pdf_path.stem
|
||||
txt_path = out_dir / f"{base}.pseudonymise.txt"
|
||||
|
||||
Reference in New Issue
Block a user