From 33543b6e2b5bd105f504cacf9827d0d1dd46fa1f Mon Sep 17 00:00:00 2001 From: Domi31tls Date: Wed, 3 Jun 2026 12:02:53 +0200 Subject: [PATCH] =?UTF-8?q?fix(detect):=20F5=20=E2=80=94=20masque=20la=20c?= =?UTF-8?q?ontinuation=20orpheline=20d'un=20nom=20compos=C3=A9=20(EJNAINI)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dernière fuite de l'audit_30. Cas Trackare : un nom composé "NOCENT-EJNAINI" éclaté en colonnes devient "[NOM]-\nEJNAINI" — le 1er composant est masqué par le NER mais le 2e reste en clair (ni span NER intact ni candidat regex ne le couvre ; être dans paranames ne suffit pas sans candidat). Fix : post-passe dans process_pdf (étape 3a-bis), après selective_rescan, qui masque le token majuscule orphelin suivant immédiatement un "[NOM]-". Couvre le texte ET le raster (NOM_GLOBAL). Réfute la conclusion de Qwen ("paranames résoudra EJNAINI"). Validation audit_30 (29 docs) : score 98.3 → 98.5/100, LEAK SCORE 100/100 (0 fuite), 0 régression FP. tests/unit 85 passed. BA127127 : EJNAINI 7→0. Co-Authored-By: Claude Opus 4.8 (1M context) --- anonymizer_core_refactored_onnx.py | 15 ++++ .../2026-06-03_claude_ejnaini-root-cause.md | 75 +++++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 docs/coordination/inbox/for-dom/2026-06-03_claude_ejnaini-root-cause.md diff --git a/anonymizer_core_refactored_onnx.py b/anonymizer_core_refactored_onnx.py index 54153c1..23b1c95 100644 --- a/anonymizer_core_refactored_onnx.py +++ b/anonymizer_core_refactored_onnx.py @@ -4502,6 +4502,21 @@ def process_pdf( # 3) Rescan selectif final_text = selective_rescan(final_text, cfg=cfg) + # 3a-bis) Nettoyage post-masquage : continuation orpheline d'un nom composé + # coupé par saut de ligne. Cas Trackare en colonnes : "NOCENT-EJNAINI" + # est éclaté en "[NOM]-\nEJNAINI" → le 1er composant est masqué par le + # NER mais le 2e (token majuscule orphelin juste après "[NOM]-") reste + # en clair, car ni span NER intact ni candidat regex ne le couvre. + # On masque ce token dans le texte ET via NOM_GLOBAL (raster). + _re_nom_orphan = re.compile(r"(\[NOM\]-\s*\n?\s*)([A-ZÀ-Ÿ][A-ZÀ-Ÿ'\-]{3,})\b") + def _clean_nom_orphan(m): + tok = m.group(2) + if tok.lower() in _MEDICAL_STOP_WORDS_SET: + return m.group(0) + anon.audit.append(PiiHit(-1, "NOM_GLOBAL", tok, PLACEHOLDERS["NOM"])) + return m.group(1) + PLACEHOLDERS["NOM"] + final_text = _re_nom_orphan.sub(_clean_nom_orphan, final_text) + # 3b) Nettoyage post-masquage : codes postaux orphelins (5 chiffres collés à un placeholder) # et téléphones fragmentés sur plusieurs lignes _re_cp_orphan = re.compile(r"(\[(?:ADRESSE|NOM|VILLE)\])\s*(\d{5})\b") diff --git a/docs/coordination/inbox/for-dom/2026-06-03_claude_ejnaini-root-cause.md b/docs/coordination/inbox/for-dom/2026-06-03_claude_ejnaini-root-cause.md new file mode 100644 index 0000000..6fc3d9e --- /dev/null +++ b/docs/coordination/inbox/for-dom/2026-06-03_claude_ejnaini-root-cause.md @@ -0,0 +1,75 @@ +--- +from: claude +to: dom +date: 2026-06-03 +topic: ejnaini-root-cause (T-H) +status: RÉSOLU — fix F5 implémenté + validé audit_30 +references: + - file: docs/coordination/inbox/for-dom/2026-06-02_qwen_ejnaini-investigation.md + - doc: trackare-BA127127-23135726 +--- + +# EJNAINI — cause racine confirmée (et conclusion de Qwen corrigée) + +## Test empirique (moteur actuel, paranames + stopwords nettoyés) + +Re-traitement du PDF source `433_23135726/trackare-BA127127...pdf` : + +| Token | Avant | Maintenant | +|---|---|---| +| `NOCENT` | 7 fuites | **0** ✅ | +| `EJNAINI` | 7 fuites | **7** ❌ | + +Pattern résiduel **déterministe** (7×) : +``` +[NOM]- +EJNAINI +``` + +## Conclusion de Qwen RÉFUTÉE + +Qwen concluait : « paranames résoudra EJNAINI ». **Faux.** +- `EJNAINI` **est** dans paranames (`noms_famille_world.txt.gz`) et chargé dans le core (vérifié). +- Il reste pourtant non masqué. + +## Vraie cause racine + +Deux chemins de masquage parallèles dans le moteur : +1. **Spans NER** → remplacement direct du span détecté. Capte `Cécilia NOCENT-EJNAINI` **là où il est intact** (166 hits NOM). +2. **NameCandidates** (regex) → cross-validation (NER/INSEE/paranames) → `safe_names` → remplacement global **+ redaction raster** (`NOM_GLOBAL`). + +En zone tableau Trackare, le nom est éclaté sur deux lignes : `NOCENT-` en fin de ligne, `EJNAINI` orphelin plus bas. +- Le span intact `Cécilia NOCENT-EJNAINI` n'existe pas → chemin 1 ne le voit pas. +- Aucun candidat regex ne propose `EJNAINI` seul → chemin 2 ne le voit pas (donc paranames jamais consulté pour lui). +- F1 ne décompose que les tokens **uniques** à trait d'union déjà dans `names`, pas les **spans multi-mots** NER. + +`NOCENT` finit masqué (`[NOM]-`) par un autre artefact de remplacement, mais `EJNAINI`, n'étant ni dans un span intact ni dans `safe_names`, fuit. + +## Fix recommandé (robuste, corrige texte ET PDF) + +Quand le NER détecte un nom **multi-mots ou à trait d'union** (PER), **décomposer** le span et injecter ses tokens composants (≥4 chars, non stop-word, confirmés gazetteer/NER) comme **NameCandidates haute confiance** → ils entrent dans `safe_names` → remplacement global + `NOM_GLOBAL` (raster). + +C'est une généralisation de F1 aux spans NER multi-mots. Avantage : masque `EJNAINI` partout (texte + raster), pas un patch cosmétique sur le `.txt`. + +## Fix implémenté (F5) — post-passe orpheline + +Au lieu de la décomposition des spans NER (le span n'existe pas en zone tableau), +la solution retenue cible directement le pattern résiduel : après `selective_rescan`, +on masque le token majuscule orphelin qui suit immédiatement un `[NOM]-` (cas +`[NOM]-\nEJNAINI`). Implémenté dans `process_pdf` (étape 3a-bis), même style que le +nettoyage des codes postaux orphelins. Masque le texte **et** le raster (via `NOM_GLOBAL`). + +## Validation (2026-06-03) + +- BA127127 re-traité : `NOCENT=0, EJNAINI=0` ✅ +- `tests/unit` : 85 passed +- **audit_30 complet** (29 docs, 1 quarantiné) : + - **Score global 98.5/100 (A+)** (baseline 97.0, +1.5) + - **Leak score 100/100** — 0 fuite audit, 0 regex, 0 INSEE contexte fort + - FP score 95 (+5 vs baseline), 0 terme médical masqué + - **Aucune régression** + +EJNAINI était la dernière fuite de l'audit_30. **Leak score = 100%.** + +— Claude +