diff --git a/anonymizer_core_refactored_onnx.py b/anonymizer_core_refactored_onnx.py index a53a132..d42d359 100644 --- a/anonymizer_core_refactored_onnx.py +++ b/anonymizer_core_refactored_onnx.py @@ -2378,6 +2378,25 @@ 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() + + # F1 — décomposition des noms à trait d'union. + # Si "BILLON-GRAND" ou "NOCENT-EJNAINI" est validé comme nom, on ajoute + # aussi chaque sous-token (≥4 chars) à safe_names. Sinon le regex + # \bBILLON-GRAND\b ne matche pas le motif "BILLON-\nGRAND" produit par + # le formatage Trackare en colonnes étroites. Les sous-tokens héritent + # du bypass_stopwords du composé (cas Dr/Mme + nom composé). + expanded_names = set(names) + expanded_force = set(_force) + for n in list(names): + if "-" in n and len(n) >= 5: + parts = [p for p in n.split("-") if len(p) >= 4] + for part in parts: + expanded_names.add(part) + if n in _force: + expanded_force.add(part) + names = expanded_names + _force = expanded_force + safe_names = set() for n in names: if len(n) < 4 and n not in _force: @@ -4766,6 +4785,36 @@ def process_pdf( residual_count = 0 for pat, _label in _residual_pii_patterns: residual_count += len(pat.findall(final_text)) + + # F4 — filet de rescan élargi aux noms INSEE en MAJUSCULES. + # OPT-IN : désactivé par défaut. Sur le corpus audit_30, INSEE contient + # beaucoup de mots français courants (VOIR, ALLO, POLYGONE, MIDI, …) + # qui produisent un fort taux de faux positifs et mettent quasi tous + # les documents en quarantaine. À utiliser quand on tolère le sur- + # masquage et qu'on veut zéro fuite (ex: profil "paranoid"). + # Pour activer : passer cfg["rescan"]["check_insee_names"] = True. + _check_insee = False + if isinstance(cfg, dict): + _check_insee = bool((cfg.get("rescan", {}) or {}).get("check_insee_names", False)) + if _check_insee: + _placeholder_bare = {p.strip("[]") for p in PLACEHOLDERS.values()} + _wl_terms = [] + if isinstance(cfg, dict): + _wl_terms = (cfg.get("whitelist", {}) or {}).get("terms", []) or [] + _wl_norm = {_normalize_nfkd_upper(str(w)) for w in _wl_terms} + for token in re.findall(r"\b[A-ZÀ-Ÿ]{4,}\b", final_text): + if token in _placeholder_bare: + continue + norm = _normalize_nfkd_upper(token) + if norm not in _INSEE_NOMS_FAMILLE: + continue + if norm.lower() in _MEDICAL_STOP_WORDS_SET: + continue + if norm in _wl_norm: + continue + residual_count += 1 + log.warning("Residual INSEE name detected: %s (in %s)", token, pdf_path.name) + if residual_count > SEUIL_RESCAN_RESIDUEL: if quarantine_mgr is not None: quarantine_mgr.flag(