From 8e43d8d1ae0df5e474d1340df41b93aa7ac145c3 Mon Sep 17 00:00:00 2001 From: Domi31tls Date: Wed, 15 Apr 2026 17:21:54 +0200 Subject: [PATCH] =?UTF-8?q?fix(detect):=20accepter=20pr=C3=A9noms=203=20ch?= =?UTF-8?q?ars=20apr=C3=A8s=20Dr/Mme=20(Ute,=20Eva,=20L=C3=A9o=E2=80=A6)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Audit manuel après batch QC : 20 occurrences de "Dr Ute" dans trackare-03020576-23175616 non masquées. Audit jsonl confirme : 0 hit pour "Ute" → pas détecté. Cause : _add_candidate (deux implémentations, lignes 1908 et 2225) filtrait len(token) < 4, empêchant la création du NameCandidate pour "Ute" (3 chars) même avec bypass_stopwords=True. La cross-validation écrasait alors all_names avec validated_names (vide pour Ute), et _apply_extracted_names ne recevait donc jamais Ute. Le commit 2f79f7c avait fait le fix uniquement dans _apply_extracted_names. Fix incomplet : le filtre amont _add_candidate rejetait avant. Correctif symétrique sur _add_candidate (×2) + _add_tokens_force_first : accepter 3 chars UNIQUEMENT si bypass=True (contexte Dr/Mme) ET majuscule initiale ET alpha pur. 2 chars reste filtré (initiales ambigues). Validation : - "DR. DURANTEAU Ute" matche bien RE_EXTRACT_DR_DEST et capture "DURANTEAU Ute" - Audit produit "Ute DURANTEAU" en bloc + "DURANTEAU" seul (41 hits total) - PDF redacted : 0 résiduel "Ute" (avant : 38) Cas protégés : - "Ute" accepté : bypass=True, U majuscule, alpha ✓ - "Les" refusé : bypass=True mais stopword (filtré ailleurs) ✓ - "JF" refusé : 2 chars, filtre longueur < 3 ✓ 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 b595899..5346d58 100644 --- a/anonymizer_core_refactored_onnx.py +++ b/anonymizer_core_refactored_onnx.py @@ -1903,9 +1903,13 @@ def _extract_trackare_identity(full_text: str) -> Tuple[set, List[PiiHit], set, force_names: set = set() # noms issus de contextes structurés (DR., Signé, etc.) → bypass stop words def _add_candidate(token: str, source: str, strength: str, bypass: bool): - """Ajoute un NameCandidate à la liste.""" + """Ajoute un NameCandidate à la liste. + Accepte les prénoms courts 3 chars (Ute, Eva, Léo…) si bypass=True + (contexte Dr/Mme fort) ET majuscule initiale + alpha pur.""" token = token.strip(" .-'(),") - if len(token) < 4: + if len(token) < 3: + return + if len(token) == 3 and not (bypass and token[0].isupper() and token.isalpha()): return candidates.append(NameCandidate( token=token, source=source, @@ -2220,9 +2224,13 @@ def _extract_document_names(full_text: str, cfg: Dict[str, Any]) -> Tuple[set, s candidates: List[NameCandidate] = [] def _add_candidate(token: str, source: str, strength: str, bypass: bool): - """Ajoute un NameCandidate à la liste (dédupliqué par token+source).""" + """Ajoute un NameCandidate à la liste (dédupliqué par token+source). + Accepte les prénoms courts 3 chars (Ute, Eva, Léo…) si bypass=True + (contexte Dr/Mme fort) ET majuscule initiale + alpha pur.""" token = token.strip(" .-'") - if len(token) < 4: + if len(token) < 3: + return + if len(token) == 3 and not (bypass and token[0].isupper() and token.isalpha()): return candidates.append(NameCandidate( token=token, source=source, @@ -2270,12 +2278,21 @@ def _extract_document_names(full_text: str, cfg: Dict[str, Any]) -> Tuple[set, s Après Dr/Mme, tous les tokens sont des noms — même s'ils sont homonymes de termes médicaux (ex: Dr Laurence MASSE). + + Accepte les prénoms courts 3 chars (Dr Ute, Dr Eva, Dr Léo) : le + contexte Dr/Mme est suffisamment fort pour lever le filtre de + longueur, à condition que le token soit alpha et commence par + une majuscule. 2 chars reste filtré (trop ambigu : initiales). """ _add_compound(match_str) tokens = match_str.split() for token in tokens: token = token.strip(" .-'") - if len(token) < 4: + if len(token) < 3: + continue + # 3 chars : accepter uniquement si majuscule initiale + alpha + # (évite "Les", "Des" mais accepte "Ute", "Eva"). + if len(token) == 3 and not (token[0].isupper() and token.isalpha()): continue if token.upper() in wl_sections or token in wl_phrases: continue