fix(detect): F5 — masque la continuation orpheline d'un nom composé (EJNAINI)
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) <noreply@anthropic.com>
This commit is contained in:
@@ -4502,6 +4502,21 @@ def process_pdf(
|
|||||||
# 3) Rescan selectif
|
# 3) Rescan selectif
|
||||||
final_text = selective_rescan(final_text, cfg=cfg)
|
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)
|
# 3b) Nettoyage post-masquage : codes postaux orphelins (5 chiffres collés à un placeholder)
|
||||||
# et téléphones fragmentés sur plusieurs lignes
|
# et téléphones fragmentés sur plusieurs lignes
|
||||||
_re_cp_orphan = re.compile(r"(\[(?:ADRESSE|NOM|VILLE)\])\s*(\d{5})\b")
|
_re_cp_orphan = re.compile(r"(\[(?:ADRESSE|NOM|VILLE)\])\s*(\d{5})\b")
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
Reference in New Issue
Block a user