Étend `RE_RPPS` pour tolérer 0 à 3 mots qualificateurs entre `RPPS`
et le séparateur `:` ou `-`. Couvre les variantes observées :
- RPPS prescripteur :
- RPPS du médecin signataire :
- RPPS de garde -
- N° RPPS :
Si un qualificateur est présent, le séparateur (`:` ou `-`) devient
obligatoire pour éviter d'aspirer du narratif (faux positif type
"Le RPPS est consulté pour vérifier 12345678901 dans la base").
La lambda `_repl_rpps` reconstruit `RPPS : [RPPS]` en sortie : le
qualificateur est consommé mais perdu (pas de fuite, choix cosmétique).
Cas 005_bacterio_complete passe désormais (retiré de KNOWN_FAILURES).
La fuite `10101010101` derrière `RPPS prescripteur :` est masquée.
Cohérent avec le cadrage section 10.1 (règle cœur générique
applicable à tout établissement de santé français — pas de
spécificité locale).
Tests : 72 passed, 1 xfailed (avant : 71 passed, 2 xfailed).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Trois nouveaux patterns cœur dans `_mask_structured_line` pour des
labels génériques qui n'étaient pas couverts par le pipeline kv_value
(le split key:value laissait fuir la valeur quand le label dépassait
les patterns existants `RE_EXTRACT_NOM_NAISSANCE`, `RE_EXTRACT_PRENOM`,
`RE_EXTRACT_VILLE_RESIDENCE`).
`RE_LABEL_NOM_VARIANTES` capture :
- Nom de jeune fille / de famille / de naissance(.)
- Nom d'usage / Nom marital / Nom marié
`RE_LABEL_PRENOM` capture :
- Prénom : / Prénoms : / Prénom de naissance / utilisé(e) / usuel
- Capture jusqu'à fin de ligne pour les énumérations virgulées
(Prénoms : Sabine, Marie → tout masqué).
`RE_LABEL_VILLE` capture :
- Ville : / Ville de résidence : / Ville de naissance :
- Capture jusqu'à fin de ligne (gère "Saint-Jean-de-Luz",
"Saint-Denis (974)", composés multi-tokens).
Effets de bord positifs :
- Le bug "Saint-Jean-de-Luz → [ETABLISSEMENT]-de-Luz" est corrigé :
le matcher `RE_LABEL_VILLE` masque toute la valeur en `[VILLE]`
AVANT que le gazetteer FINESS Aho-Corasick ne grignote "Saint-Jean".
Cas 006_trackare_soignants et 008_anesthesie_complete : alignement
des expected.txt sur cette amélioration.
Choix d'architecture (cf cadrage docs/cadrage-projet-anonymisation.md
section 10.1) : ces labels sont des règles cœur génériques applicables
à tout établissement de santé français. Légitimes en hardcodé. Les
patterns layout-specific (Bordeaux suffixe, CHCB en fin de phrase,
email cassé par force_term) seront branchés via admin_rules dans
l'étape suivante.
Cas 010_fiche_admission_minimale passe désormais (retiré de
KNOWN_FAILURES). Le xfail strict aurait signalé xpass.
Tests : 9 passed, 2 xfailed (avant : 8 passed, 3 xfailed sur
test_synthetic_review).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Trois fixes regroupés issus de la session de revue couche 2 :
#6 — caractère ñ dans les patterns de noms
Étend les classes de caractères pour inclure Ñ/ñ (basque, hispanique).
Avant : `Beñat` → `[NOM]ñat` (fuite indirecte du suffixe).
Après : `Beñat` → `[NOM]` (capture complète).
Justification : usage prévu La Réunion + populations basques/
hispaniques. Si nécessaire on ajoutera Ã/ã, Õ/õ (portugais) plus
tard.
#10 — règle numéro adhérent mutuelle (nouveau)
Ajoute placeholder [ADHERENT] et `RE_NUM_ADHERENT` :
`(?:n[°o]?\s*|num[ée]ro\s+(?:d['’]\s*)?)adh[ée]rent[e]?\s*[:\-]?\s*([A-Z0-9]{6,15})`
Couvre `n°adhérent`, `n° adhérent:`, `Numéro d'adhérent :`,
`Numéro d'adhérente:`, `numero adherent`, alphanumérique 6-15.
Faux positif `Le patient est adhérent à la mutuelle.` non matché
(préfixe N°/numéro obligatoire).
Branché dans `_mask_structured_line` (pour conserver le préfixe
au moment du matching, avant le split key:value) et dans
`_mask_line_by_regex` (texte non-structuré).
#11 — NIR avant TEL pour éviter consommation prématurée
Réordonne RE_NIR avant RE_TEL dans `_mask_line_by_regex` et
`selective_rescan`. Le NIR au format espacé `2 73 04 65 100 100 88`
est testé d'abord (validation modulo 97). Si validé, masqué en
[NIR] avant que RE_TEL ne consomme les 10 chiffres centraux. Si
la clé échoue (faux positif), TEL reprend la main inchangé.
Avant : `2 73 04 65 100 100 68` → `2 73 [TEL] 68`.
Après : `2 73 04 65 100 100 68` → `[NIR]`.
Cas synthetic_review/010 corrigé : NIR de test mis à clé valide
(68 au lieu de 88), expected aligné sur [ADHERENT] et [NIR].
Le case 010 reste en xfail — fuites résiduelles ELIZONDO / Sabine
/ Bayonne (labels structurels Nom de jeune fille / Prénom / Ville
non couverts) à fixer dans le batch suivant.
Tests : 70 passed, 3 xfailed (inchangé). Pas de régression.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- RE_SCAN_FILENAME_ARTIFACT : masque le suffixe numérique des noms de
fichiers internes type EXT2-[IPP]-2300249096.TIF qui fuyaient en sortie.
- _RE_VENUE_BEFORE_IPP : variante BACTERIO observée en production où
le N° venue est rejeté plusieurs lignes après le libellé, juste
avant IPP. Détection en phase 0i.
- _RE_FINAL_VENUE_BEFORE_IPP : nettoyage final pour le résiduel du
même layout BACTERIO si le numéro a survécu jusqu'à process_pdf.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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) <noreply@anthropic.com>
Fuite détectée lors du QC batch 22 : le nom "Douar" était dans l'audit
(NOM page 6) mais restait visible dans le PDF redacted_vector. Cause :
dans get_text('words') le word était 'Douar,nécessitant' (virgule collée
sans espace). _search_whole_word faisait un == strict après strip des
ponctuations frontières, mais la virgule était au MILIEU — pas stripée.
→ aucun match → aucun rectangle → fuite.
Fix : passe 2 dans _search_whole_word avec regex word-boundary sur le
texte complet du word (pattern `(?<![A-Za-zÀ-ÿ])token(?![A-Za-zÀ-ÿ])`)
+ bbox proportionnelle au ratio chars matched / chars total du word.
Approximation exacte sur polices monospace, précision ±pixels sur
polices proportionnelles — couverte par le rectangle de redaction.
Validation bout-en-bout sur trackare-BA042686-23090597 : "Douar" masqué
(0 page résiduelle). QC strict retombe de 1 anomalie à 0.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Le pattern type utilisait [Cc]entre\s+[Hh]ospitalier : seule la 1re lettre
de chaque mot était ambidextre, la suite devait être en minuscules. "CENTRE
HOSPITALIER COTE BASQUE" (tout majuscule) échappait → compensé par regex
YAML force_mask_regex "Centre\s+Hospitalier\s+…".
Fix : utiliser (?i:…) case-insensitive localement sur les sous-motifs "type
d'établissement" et "déterminants" (de, du, la…) tout en gardant le nom
propre strict (1re lettre majuscule obligatoire). Évite les FP tout en
capturant les majuscules complètes.
Cas validés :
- "Centre Hospitalier de Bayonne" → match (inchangé)
- "CENTRE HOSPITALIER COTE BASQUE" → match (nouveau)
- "POLYCLINIQUE CÔTE BASQUE SUD" → match (nouveau)
- "CLINIQUE SAINT-JEAN" → match (nouveau)
- "examen hôpital de Bordeaux" → pas de match (exclusion préservée)
Test YAML stripped : CENTRE HOSPITALIER et COTE BASQUE sont maintenant
masqués par ETAB (regex/AC) au lieu de force_term. Après ce fix + Fix#4,
on peut retirer les regex "Centre\s+Hospitalier…" et "Polyclinique…" du YAML.
Non-régression : 122 hits sur trackare-18007562 avec YAML complet.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Trois trous de détection identifiés par l'audit de règles :
1. Énumération "Bordeaux et Bayonne" / "Bordeaux, Bayonne, Biarritz" : la règle
contextuelle _RE_GEO_BEFORE n'acceptait que des déclencheurs directs (à, de,
hôpital de, urgences de…). Dans une énumération, la 2ème ville+ échappait.
Nouvelle passe 2 : propagation mutuelle entre hits AC adjacents liés par
" et " ou ", ". Itération à point fixe pour chaînes longues. Garde-fou :
chaque hit ≥ 5 lettres pour éviter FP sur communes courtes homonymes.
2. Code postal encore en chiffres : _RE_GEO_BEFORE n'acceptait que
[CODE_POSTAL] déjà masqué. Ajout de `\b\d{5}\s+` comme déclencheur pour
couvrir l'ordre dans lequel _mask_ville_gazetteers est appelée avant le
masquage du code postal.
3. Suffixe CEDEX : "BAYONNE CEDEX" capturait BAYONNE seul. Extension automatique
de la capture pour inclure " CEDEX" et " CEDEX N" adjacents.
Cas validés :
- "travaille à Bordeaux et Bayonne" → [VILLE] et [VILLE]
- "Régions : Bordeaux, Bayonne, Biarritz" → 3× [VILLE] (chaîne sans ancre)
- "64109 BAYONNE CEDEX" → [VILLE] (capture CEDEX inclus)
- "charge", "médecin et patient" → aucun FP
Non-régression : 122 hits sur trackare-18007562.
Après ce fix, on peut retirer BAYONNE, BAYONNE CEDEX du YAML force_mask_terms.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Le matcher Aho-Corasick FINESS rejetait tous les mono-mots < 10 chars pour
éviter les faux positifs. Conséquence : EMBRUNS (7 chars), présent dans
etablissements_distinctifs.txt, était ignoré et devait être forcé en YAML
(LES EMBRUNS, REED LES EMBRUNS, EMBRUNS BIDART, regex [Ee]mbruns).
Nouveau fichier data/finess/mono_mots_distinctifs.txt contenant la whitelist
curée des mono-mots courts considérés comme distinctifs. Maintenance manuelle
(un mot par ligne, commentaires autorisés). Le matcher accepte un mono-mot
< 10 chars uniquement s'il est dans cette whitelist.
Initialisation : embruns, embrun (documents CHCB "Les Embruns").
Validation :
- _FINESS_AC matche maintenant "les embruns quelque part" et "embruns seul"
- Pas de régression sur trackare-18007562 (122 hits)
Après ce fix + futurs, on pourra retirer LES EMBRUNS / REED LES EMBRUNS /
EMBRUNS BIDART et regex [Ee]mbruns de force_mask_terms du YAML.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Deux corrections exploitant mieux les gazetteers FINESS/INSEE pour réduire la
dépendance au YAML force_mask_terms.
1. scripts/build_finess_gazetteers.py : ne lisait que col 1 (finess_et) du CSV.
Les col 2 (entjur, entité juridique) étaient ignorés. ~48k numéros
juridiques manqués, dont 640780417 (CHCB entjur) forcé en YAML à cause
de cette lacune. Fix : lecture col 1 + col 2 avec déduplication.
Régénération : 101 941 → 150 436 numéros (+48 495).
2. anonymizer_core_refactored_onnx.py :
- _FINESS_ETAB_NAMES (122k noms) chargé mais jamais consulté après le
refactoring NER-first (le matching passe par l'Aho-Corasick sur
etablissements_distinctifs.txt). Suppression → -122k entrées RAM.
- _INSEE_PRENOMS (lowercase) et _INSEE_PRENOMS_SET (uppercase sans accents)
lisaient deux fois le même fichier prenoms_france.txt. Fusion en une
seule passe disque, les deux formes dérivées en mémoire. -36k lectures.
Validation :
- 640780417 présent dans _FINESS_NUMBERS après rebuild
- 122 hits sur trackare-18007562 (non-régression)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Plantages signalés sous Windows : causes identifiées et corrigées.
1. anonymisation_onefile.spec : les fichiers data/stopwords_manuels.txt,
villes_blacklist.txt, dpi_labels_blacklist.txt, companion_blacklist.txt
n'étaient PAS inclus dans le bundle PyInstaller (seuls les sous-dossiers
data/bdpm, data/finess, data/insee l'étaient). Résultat en frozen : sets
vides, qualité dégradée, plus de faux positifs.
2. anonymizer_core_refactored_onnx.py : chargements robustifiés.
- Helper _load_txt_set avec try/except et logging WARNING si fichier absent
- Fallbacks intégrés (_DPI_LABELS_FALLBACK, _COMPANION_BLACKLIST_FALLBACK)
pour continuer à fonctionner si bundle partiel
- try/except sur stopwords_manuels.txt, villes_blacklist.txt, BDPM
3. launcher.py : UX repensée pour le chargement des modèles.
- SetupWindow (premier lancement) : auto-démarrage (plus de clic nécessaire),
progress bar avec étapes visuelles (⏳/✓/✗ par modèle), bouton relance si
échec, bouton "continuer malgré tout" pour modèles optionnels.
- Splash screen ajouté dans launch_gui() : le chargement des gazetteers
(INSEE 200k+ noms, FINESS 100k+ établissements) prend 15-30 s au démarrage
normal. Sans feedback, l'utilisateur croyait l'app plantée. Le splash
tourne pendant l'import (thread séparé, poll avec splash.after).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Suite de l'externalisation des règles. Trois listes étaient codées en dur dans
anonymizer_core_refactored_onnx.py et impossibles à modifier par les
établissements sans recompiler :
- _NEVER_MASK_AS_NAME (12 entrées) — labels DPI structurels
- _DPI_LABELS_BLACKLIST (14 entrées, doublon partiel du précédent)
- _COMPANION_BLACKLIST (~75 entrées) — spécialités, labos pharma, mots ambigus
Les deux premières fusionnées dans data/dpi_labels_blacklist.txt (11 entrées
uniques, comparaison case-insensitive). La troisième dans
data/companion_blacklist.txt (75 entrées, comparaison uppercase).
Ajout de deux clés YAML pour enrichissement par établissement :
- additional_dpi_labels (ex: "Service", "Statut")
- additional_companion_blacklist (ex: spécialités locales)
Les 3 niveaux cumulatifs habituels s'appliquent : code (vide) → fichiers data/
→ YAML config. Chargement au démarrage avec log INFO du nombre d'entrées.
Test trackare-18007562-23054899 : 122 hits, 0 régression, 0 DPI label masqué
comme NOM.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Bug majeur depuis l'externalisation : la GUI v5.4 écrivait whitelist_phrases
(clé racine), mais le core ne lisait que whitelist.sections_titres /
noms_maj_excepts (imbriqué). _apply_whitelist post-masquage était par ailleurs
désactivée (1bd3495) sans remplacement.
Correctif :
- load_dictionaries() lit whitelist_phrases et alimente deux sets globaux
(_WHITELIST_NEVER_MASK_TOKENS, _WHITELIST_NEVER_MASK_PHRASES). Mots-outils
(de, du, le...) écartés pour éviter blocages collatéraux.
- _apply_extracted_names : check whitelist en pré-masquage, prime sur les
force_names (ex: "DUPONT" reste visible même après "Dr DUPONT").
- process_pdf : filtrage final de l'audit avant redact_pdf_vector. Les hits
multi-mots dont au moins un sous-token est whitelist sont retirés.
- redact_pdf_vector : check whitelist sur les sous-mots cherchés
individuellement quand le multi-mots n'est pas trouvé sur la page.
Validé sur trackare-18007562-23054899 :
- Avec whitelist BELLEAU : 0 hit dans audit, 31 occurrences préservées dans PDF
- Sans whitelist : 0 occurrence dans PDF (non-régression OK)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
pyzbar interprétait les cellules de tableaux trackare comme des codes-barres
et les noircissait. Ajout d'un seuil minimum de surface (2000 px²) pour
filtrer les faux positifs sur les petites zones.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Whitelist post-masquage désactivée : injectait des phrases au mauvais
endroit dans le texte anonymisé (bug critique)
- Labels DPI "Date", "Note", "Heure", "Type", "Saint", "Page" ajoutés à
_NEVER_MASK_AS_NAME et _DPI_LABELS_BLACKLIST pour empêcher leur
propagation globale comme noms de personnes
- Corrige "Date d'admission → [NOM] d'admission",
"Note d'évolution → [NOM] d'évolution", etc.
Score évaluation : 99.3/100 (fuites pré-existantes Sie/GRAND inchangées)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Les noms avec bypass_stopwords=True (contexte Dr/Mme confirmé) sont
maintenant toujours acceptés par la cross-validation, même s'ils sont
dans les stop-words médicaux (ex: Dr MASSE, Dr GRAND).
Note: les fuites "Sie" (3 chars) et "GRAND" (stop-word) existaient
déjà avant le refactoring NER-first (score 99.3 identique).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
La cross-validation NER (_cross_validate_name_candidates) gère désormais
les décisions contextuelles nom/terme-médical. Les stop-words purement
médicaux sont supprimés :
- data/stopwords_manuels.txt : 1307 → 233 entrées (uniquement les mots
ambigus qui sont aussi des noms/prénoms INSEE)
- _MEDICAL_STOP_WORDS_SET hardcodé : ~400 → 80 entrées essentielles
(mots courts, formes galéniques, titres hospitaliers)
- Les enrichissements BDPM (~7300), edsnlp (~2000) et fichier externe
sont conservés tels quels
Score qualité inchangé : 100/100 (A+), 0 fuite, 0 faux positif.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Step 5: anonymise_document_regex now accepts optional NER managers,
runs NER on the original (unmasked) text, and cross-validates
regex-extracted names against NER detections + INSEE gazetteers.
NER-only detections (names found by NER but missed by regex) are
also added. Falls back to original behavior when no NER is available.
Step 6: process_pdf passes NER managers into anonymise_document_regex
for NER-first cross-validation. The existing NER safety net pass on
masked text is preserved (double-pass: original + masked text).
Quality score: 100.0/100 (A+), zero regression.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add infrastructure for NER-first name validation without changing
existing behavior. New code only, quality score remains 100/100.
Step 1: Load INSEE family names (219K) and prenoms (33K) as
module-level gazetteers (_INSEE_NOMS_FAMILLE, _INSEE_PRENOMS_SET)
normalized uppercase without accents.
Step 2: Add _run_ner_on_original_text() that runs all available NER
models (EDS-Pseudo, GLiNER, CamemBERT-bio) on unmasked text and
returns deduplicated NerDetection list.
Step 3: Add NerDetection and NameCandidate dataclasses. Modify
_extract_document_names and _extract_trackare_identity to also
return NameCandidate lists with context_strength (high/medium/low)
metadata. Callers updated for new return values.
Step 4: Add _cross_validate_name_candidates() implementing decision
matrix: high context always accepted, medium/low validated against
NER confirmations, INSEE membership, and stopword filtering.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Toutes les listes de règles sont maintenant modifiables sans toucher
au code Python :
Fichiers de données (data/) :
- stopwords_manuels.txt : 1307 termes médicaux/techniques
- villes_blacklist.txt : 117 communes à ne pas matcher
- medicaments_stopwords.txt : 7312 médicaments BDPM (existant)
- Chargés automatiquement au démarrage
Config YAML (dictionnaires.yml) :
- additional_stopwords : mots supplémentaires par établissement
- additional_villes_blacklist : villes supplémentaires
- whitelist_phrases : phrases à ne jamais anonymiser
- force_mask_terms : mots à toujours masquer
Chaîne de chargement : code dur → fichiers data/ → YAML config
Les 3 niveaux se cumulent (union).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Bug critique corrigé : les noms forcés (contexte Dr/Mme) comme "MASSE"
étaient masqués dans le texte mais pas dans le PDF raster car filtrés
par les stop-words médicaux. Nouveau kind "NOM_FORCE" qui bypass le
filtre stop-words dans les fonctions de redaction vector et raster.
GUI : remplacement des zones texte brut par des listes interactives
avec champ de saisie + bouton Ajouter + bouton Supprimer, fond coloré
(vert pour whitelist, rose pour blacklist).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- 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>
Nouveau module format_converter.py : conversion automatique vers PDF
avant anonymisation. Formats supportés :
- PDF (passthrough)
- DOCX (python-docx → texte → PDF)
- ODT (odfpy → texte → PDF)
- RTF (striprtf → texte → PDF)
- TXT (texte brut → PDF via PyMuPDF)
- HTML (BeautifulSoup → texte → PDF)
- JPEG/PNG/TIFF/BMP (image embarquée → OCR docTR en aval)
Nouvelle fonction process_document() : wrapper qui gère la conversion
puis appelle process_pdf(). GUI mise à jour pour chercher tous les
formats supportés (plus seulement *.pdf).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ProcessPoolExecutor relançait l'exe pour chaque sous-processus de
rastérisation sous PyInstaller --onefile, créant une fenêtre GUI par page.
En mode frozen, la rastérisation est maintenant séquentielle.
Aussi: remplacement du mutex Windows par un file lock (msvcrt.locking)
plus fiable pour la protection anti-multi-instance.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Bug #1 (critique) : RE_EXTRACT_STAFF_ROLE matchait à l'intérieur des mots
(IDE dans METOCLOPRAMIDE, AS dans ATORVASTATINE) → ajout \b word boundaries
et suppression du ? optionnel sur ASH (AS matchait partout)
Bug #2 : raster multi-mots utilisait page.search_for() (substring matching)
→ ajout vérification frontières de mots pour les tokens multi-mots
dans redact_pdf_raster et redact_pdf_vector
FP FINESS Aho-Corasick :
- "resistance" (Centre de la Résistance) matchait "résistance aux fluoroquinolones"
- "radiotherapie" matchait "tumorectomie, radiothérapie et hormonothérapie"
→ ajout blacklist : resistance, radiotherapie, chimiotherapie, etc.
FP villes : "COU" (commune) matchait dans "prurit (cou, décolleté, dos)"
→ ajout COU, DOS, SEIN, BRAS à _VILLE_BLACKLIST
Stop-words : ajout "totale", "partielle", "prothese", "unicompartimentale"
Score évaluation maintenu à 100.0/100 (A+)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
L'OCR docTR est maintenant déclenché page par page (< 150 chars) au lieu
d'un seuil global sur tout le document. Permet de traiter les documents
mixtes (pages texte + pages scannées) sans pénaliser le temps de traitement
sur les pages déjà riches en texte.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- RE_TEL : ajout du format +33(0)XXXXXXXXX (ex: +33(0)156125400)
- _add_tokens_force_first : tous les tokens après Dr/Mme/Mr sont maintenant
dans force_names (bypass stop-words médicaux). Corrige la fuite de noms
de médecins homonymes de termes médicaux (ex: Dr MASSE)
Score évaluation maintenu à 100.0/100 (A+)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Intégration du modèle CamemBERT-bio-deid v3 (F1=0.96, Recall=0.97, 1112 docs)
et corrections qualité issues de l'audit approfondi sur 29 fichiers.
Détection des villes en texte libre :
- Automate Aho-Corasick sur 33K communes INSEE + 11.6K villes FINESS
- Stratégie contextuelle : exige un contexte géographique (à, de, vers,
habite, urgences de, etc.) sauf pour les villes composées (Saint-Palais)
- Blacklist de ~80 communes homonymes de mots courants (charge, signes, plan...)
- Normalisation SAINT↔ST pour les variantes orthographiques
- De 18 fuites de villes à 2 cas résiduels atypiques
Masquage des initiales de prénom :
- Post-traitement regex : "Dr T. [NOM]" → "Dr [NOM] [NOM]"
- Références initiales : "Ref : JF/VA" → "Ref : [NOM]/[NOM]"
Détection texte espacé d'en-tête :
- "C E N T R E H O S P I T A L I E R" → [ETABLISSEMENT]
Autres corrections :
- Fix regex RE_EXTRACT_MME_MR (Mr?.? → Mr.?, \s+ → [ \t]+, * → {0,4})
- Stop words médicaux : lever, coucher, services hospitaliers (viscérale, etc.)
- CamemBERT NER manager : version tracking, propriété version, log F1/Recall
- Script finetune : export ONNX automatique + mise à jour VERSION.json
- Évaluateur qualité : exclusion stop words médicaux des alertes INSEE
Documentation :
- Spécifications techniques CamemBERT-bio-deid v3
- Conformité RGPD + AI Act (caviardage PDF raster)
- AIPD (Analyse d'Impact Protection des Données)
Score qualité : 97.0/100 (Grade A), Leak score 100/100
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Nouveau script build_finess_gazetteers.py : extraction noms distinctifs, villes, numéros depuis CSV open data
- Automate Aho-Corasick (pyahocorasick) pour matching multi-pattern en ~1.7ms/page
- 108K patterns indexés (noms composés >= 8 chars, mots uniques >= 10 chars)
- Blacklist mots génériques (clinique, pharmacie, etc.) et stop words médicaux
- Normalisation position-preserving (sans accents, même longueur)
- Construction lazy de l'AC (après chargement des stop words)
- Intégration dans _mask_line_by_regex et selective_rescan
- Nouveau gazetteer villes_finess.txt (11,660 villes)
- Résultats : "Girandières" → masqué, "Côte Basque" → masqué, 0 FP sur termes médicaux courants
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- camembert_ner_manager.py : inférence ONNX CPU (~10ms), predict/predict_long/validate_eds_entities
- Vote triple NER : EDS-Pseudo (confiance) + GLiNER (zero-shot) + CamemBERT-bio (fine-tuné F1=89%)
- CamemBERT-bio peut sauver un vrai nom à basse confiance EDS (camembert_confirmed=True)
- CamemBERT-bio confirme le rejet des FP médicaux (Paracétamol, Tramadol → False)
- Intégré dans process_pdf via paramètre camembert_manager
- run_batch_30_audit.py mis à jour pour charger le modèle
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Prénoms INSEE renforcent la confiance NER (prénom connu → ne pas filtrer)
- Communes INSEE disponibles pour distinction ville/nom de famille
- Export 29 fichiers silver annotations (252K tokens, 12.8K entités) pour fine-tuning
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remplace \s+ par [ \t]+ dans 11 regex d'extraction de noms (empêche capture cross-line de médicaments)
- Ajoute \b word boundaries dans RE_PERSON_CONTEXT (empêche "PDR" de matcher "DR")
- Ajoute filtrage _MEDICAL_STOP_WORDS_SET dans selective_rescan._rescan_person
- Ajoute stop words : labos pharma (MYL/VTS/ARW/PAN/MSO), dosages (FAIBLE/FORT), anatomie imagerie (CEREBRAL/ABDOMINO-PELVIEN)
- Filtre stop words dans _add_name_force et _add_tokens_force_first
- Mise à jour baseline regression_tests/ avec 29 fichiers du batch audit 30
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Déplacer _DOCTR_AVAILABLE = False dans le bon bloc except
- Était dans le bloc hospital_filter au lieu du bloc doctr
- Corrige l'erreur 'name _DOCTR_AVAILABLE is not defined'
- Affectait ~15 documents ANAPATH scannés