Commit Graph

74 Commits

Author SHA1 Message Date
ffb8006e91 fix(detect): RPPS avec qualificateur (RPPS prescripteur :, RPPS de garde :…) (#1)
É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>
2026-04-27 22:33:01 +02:00
9b431494a5 fix(detect): labels structurels Nom de jeune fille / Prénom / Ville (#7 #8 #9)
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>
2026-04-27 22:30:40 +02:00
fcf945d1f7 fix(detect): quick wins #6 #10 #11 — caractère ñ, numéro adhérent, NIR avant TEL
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>
2026-04-27 21:13:27 +02:00
7403811c62 fix(detect): masquer artefacts noms de fichiers DPI et variante BACTERIO N° venue
- 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>
2026-04-27 16:17:36 +02:00
bc24a21fea Wire admin rules into ONNX anonymizer 2026-04-21 12:10:17 +02:00
34dcf8f360 Externalize dictionaries and add anonymization review corpus 2026-04-21 10:32:57 +02:00
f1f73e11f3 fix(detect): accepter prénoms 3 chars après Dr/Mme (Ute, Eva, Léo…)
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>
2026-04-15 17:21:54 +02:00
d3eeeafb72 fix(redact): masquer tokens collés à ponctuation ("Douar,nécessitant")
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>
2026-04-15 14:10:34 +02:00
68b2aff6ac fix(regex): RE_HOPITAL_VILLE accepte les ALL-CAPS (CENTRE HOSPITALIER)
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>
2026-04-15 09:40:08 +02:00
86292b3c84 feat(ville): énumérations + CP nu + suffixe CEDEX dans règle contextuelle
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>
2026-04-15 09:37:55 +02:00
56547277c8 feat(finess): whitelist de mono-mots distinctifs courts (EMBRUNS, etc.)
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>
2026-04-15 09:35:16 +02:00
89e1a16856 fix(finess): inclure les entjur + supprimer code mort _FINESS_ETAB_NAMES
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>
2026-04-15 09:33:07 +02:00
c57b0cf350 fix(frozen): data/*.txt dans bundle, feedback UI pendant chargement modèles
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>
2026-04-15 08:50:42 +02:00
4adce9c5c4 refactor: externaliser DPI labels et companion blacklist (modifiables sans recompiler)
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>
2026-04-14 10:26:18 +02:00
d6b8249dc7 fix(whitelist): GUI whitelist_phrases enfin lue et appliquée par le core
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>
2026-04-14 10:23:09 +02:00
08bdff00ec fix: pyzbar FP sur tableaux — carrés noirs sur dates/heures dans les grilles
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>
2026-03-31 15:27:52 +02:00
1799878490 fix: DR. Ute (3 chars), SAINT-GERMES composé, SODIUM MACO/BAX pharma
- force_names bypass le seuil 4 chars (prénoms courts après Dr/Mme : Ute, Eva)
- SAINT seul = bloqué, SAINT-xxx composé = accepté comme nom
- Labos pharma ajoutés aux stop-words + companion blacklist :
  MACO, AGUETTANT, RENAUDIN, ARROW, BIOGARAN, MYLAN, TEVA, ZENTIVA
- Score : 99.8/100 (amélioration, "Sie" corrigé)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 15:17:37 +02:00
1bd3495329 fix: labels DPI masqués (Date, Note, Type, Heure) + whitelist désactivée
- 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>
2026-03-31 12:07:51 +02:00
5cce7d8ccb fix: cross-validation respecte bypass_stopwords pour les noms forcés (Dr/Mme)
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>
2026-03-31 11:07:59 +02:00
f5adf17e1a Revert "refactor: réduction stop-words manuels — NER cross-validation suffit"
This reverts commit 773d470e8e.
2026-03-31 11:04:51 +02:00
773d470e8e refactor: réduction stop-words manuels — NER cross-validation suffit
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>
2026-03-31 09:26:54 +02:00
98d2d412fe feat(ner-first): integrate NER-first flow into pipeline (steps 5-6)
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>
2026-03-31 08:38:56 +02:00
815926361f feat(ner-first): add NER-first architecture scaffolding (steps 1-4)
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>
2026-03-31 08:31:44 +02:00
7bc86406ba feat: externalisation des listes — stop-words et villes modifiables sans code
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>
2026-03-31 07:45:42 +02:00
bd7413fda4 fix: sync texte↔raster + GUI listes whitelist/blacklist améliorées
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>
2026-03-30 17:34:51 +02:00
f96704f839 feat: whitelist phrases + panneau paramètres avancés dans la GUI
- 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>
2026-03-30 15:03:08 +02:00
0678d072d3 feat: support multi-formats — DOCX, images, ODT, RTF, TXT, HTML
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>
2026-03-18 09:25:26 +01:00
f7be74334b fix: import sys manquant — crash 'name sys is not defined' en mode frozen
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 01:06:58 +01:00
c889eebc45 fix: fenêtres fantômes PyInstaller — désactiver ProcessPoolExecutor en mode frozen
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>
2026-03-18 00:51:54 +01:00
45fe4ebafd fix: retour relecteur #2 — page scannée noire, labels DPI, stop-words
- Page scannée entièrement noire (OGC 258) : les images couvrant > 70%
  de la page ne sont plus noircies (document scanné ≠ logo/signature)
- Labels DPI "Nom [■] naissance" : tokens < 3 chars ("N", "S") exclus
  du raster pour éviter les FP sur les mots courts des labels
- Stop-words enrichis : betascrub, hibiscrub, fresubin, nutrison,
  résorbable, nombreuses, internationale, capsule, alfa, prothèses
- FINESS blacklist : "internationale", "international", "intercommunal"
- "classification [ETABLISSEMENT] de l'infection" → corrigé

Score évaluation maintenu à 100.0/100 (A+)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 12:11:26 +01:00
53861b17a6 fix: FP médicaments dans raster + texte — RE_EXTRACT_STAFF_ROLE + FINESS + stop-words
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>
2026-03-17 07:11:57 +01:00
7408fb6ede feat: OCR docTR par page — plus de seuil global, traite chaque page pauvre individuellement
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>
2026-03-16 20:28:27 +01:00
7a68d85f2f fix: téléphone +33(0) non détecté + noms médecins homonymes de termes médicaux
- 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>
2026-03-16 18:33:32 +01:00
396bdca0ef fix: corrections retours relecteur — fuites adresses/établissements + FP médicaments
Fuites corrigées :
- "Le BOURG" : nouveau regex RE_LIEU_DIT_SEUL pour lieux-dits courants
- "CABINET ETXEBARNONDOA" : nouveau regex RE_EXTRACT_CABINET
- "REED LES EMBRUNS" : ajouté force_mask_terms + force_mask_regex case-insensitive
- "au [ETABLISSEMENT] nocturne" : "long cours" exclu des phrases FINESS

Faux positifs corrigés :
- "OXYGENE LUNETTES" : "lunettes" ajouté aux stop-words
- "POTASSIUM CHLORURE" : "chlorure" ajouté aux stop-words
- Phrases FINESS génériques étendues (le bourg, le val, les pins...)

Score évaluation maintenu à 100.0/100 (A+)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-16 18:04:08 +01:00
893ecd90de feat: réduction FP + gazetteers adresses FINESS + batch parallèle + corrections multi-axes
- Token min length relevé de 2-3 → 4 chars (élimine FP EPO, IRC, SIB...)
- Stop-words enrichis : acronymes médicaux 3 lettres, termes pharma, soins infirmiers
- BDPM stop-words : ~7300 noms commerciaux + DCI/substances actives
- Gazetteers adresses FINESS : 63K patterns Aho-Corasick (position-preserving normalization)
- Filtre contextuel anatomique pour FINESS établissements
- Nouvelles regex : RE_CIVILITE_COMMA_LIST, RE_EXTRACT_NOM_UTILISE, RE_EXTRACT_PRENOM,
  RE_NUM_EXAMEN_PATIENT, RE_ADRESSE_LIEU_DIT, RE_CIVILITE_INITIALE, Dr X.NOM
- URLs complètes (RE_URL) + détection multiline
- N° venue inversé (layout-aware) + EPISODE/NDA dans _CRITICAL_PII_TYPES
- HospitalFilter désactivé pour ADRESSE/TEL/VILLE/EPISODE (identifient le patient)
- Batch silver export parallélisé (multiprocessing spawn, N workers)
- Seuil sur-masquage relevé à 8%, server.py enrichi (source regex/ner)
- Blacklist villes : COURANT, PARIS ; contexte villes étendu (UHCD, spécialités)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-16 09:26:56 +01:00
cfec14482e fix: corrections retours collaborateurs — FP médicaments, N° venue, taille PDF
- Fix critique: whole-word search dans redact_pdf_raster et redact_pdf_vector
  pour éviter le substring matching (ex: "Luc" dans "FLUCONAZOLE",
  "TATIN" dans "ATORVASTATINE"). Appliqué à tous les kinds nom/NER.
- Ajout regex RE_VENUE_SEJOUR pour N° venue / N° séjour (BACTERIO, Trackare)
- DDN multiline élargi: tolère 0-3 lignes entre label DDN et date (tableaux BACTERIO)
- N° venue multiline: détection dans tableaux BACTERIO interleaved
- Réduction taille PDF raster: 150 DPI + JPEG quality 85 (était 300 DPI PNG)
  Ratio moyen: 19.5x (était 30-50x)
- Score qualité maintenu: 97.0/100 (grade A), 0 régression

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 10:38:27 +01:00
8588c0660b feat(phase3): CamemBERT v3 + détection villes + initiales + texte espacé + docs réglementaires
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>
2026-03-11 12:16:13 +01:00
044b4dc867 feat(phase2): Détection établissements par Aho-Corasick sur 108K noms FINESS
- 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>
2026-03-09 22:56:43 +01:00
22ed56ffd5 fix(phase2): Corrections audit 30 fichiers — FP stop words, villes, établissements, noms composés
- Ajout 10 stop words FP (bouffee, discontinue, respimat, lyoc, probnp, bpco, colle, gsc, masse, selle)
- Ajout 8 villes stop words (saint-palais, tarnos, hendaye, dax, orthez, oloron, pau, cambo)
- Protection "Examen Clinique" contre masquage [ETABLISSEMENT] (lookbehind négatif)
- Ajout Pharmacie et Centre Médical dans RE_HOPITAL_VILLE
- Masquage "Ville, le [date]" dans en-têtes courrier (Bayonne, le 12/03/2024)
- Noms composés avec espace (DI LULLO, LE MOIGNE) via _add_compound
- Contacts Trackare lowercase + capture 3e token (vandestock/michele)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 22:45:26 +01:00
aba8e13639 feat(phase2): Intégration CamemBERT-bio ONNX comme 3e signal NER (vote triple)
- 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>
2026-03-09 13:42:56 +01:00
2abb9afede feat(phase2): Gazetteers FINESS 102K établissements + fine-tuning CamemBERT-bio F1=89%
Gazetteers FINESS (data.gouv.fr open data):
- 102K numéros FINESS → détection par lookup exact dans _mask_admin_label + selective_rescan
- 122K noms d'établissements, 113K téléphones, 76K adresses (disponibles)
- Un nombre 9 chiffres matchant un vrai FINESS est masqué même sans label "FINESS"

Fine-tuning CamemBERT-bio (almanach/camembert-bio-base):
- Export silver annotations réécrit : alignement original↔pseudonymisé (difflib)
  → 6862 entités B- (vs 3344 avec l'ancien audit-only) sur 222K tokens
- Sliding windows (200 tokens, stride 100) pour documents longs
- WeightedNERTrainer avec class weights cappés (max 10x) + label smoothing
- Résultat: Precision=88.1%, Recall=89.8%, F1=88.9% (20 epochs, lr=1e-5)
- Modèle sauvegardé dans models/camembert-bio-deid/best (non commité)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 13:27:37 +01:00
192c4c034e feat(phase2): Gazetteers INSEE (36K prénoms + 34K communes) + silver annotations
- 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>
2026-03-09 12:03:17 +01:00
3590099b41 feat(phase2): Multi-signal NER — BDPM gazetteers, confiance EDS, safe patterns, GLiNER
Chantier 1: Intégration BDPM (5737 médicaments officiels) dans medication whitelist
Chantier 2: Safe patterns contextuels (dosages mg/mL/cpr, formes pharma, même ligne)
Chantier 3: Scores de confiance NER réels (edsnlp 0.20 ner_confidence_score)
Chantier 4: GLiNER zero-shot (urchade/gliner_multi_pii-v1) en vote croisé
Chantier 5: Scripts export silver annotations + fine-tuning CamemBERT-bio

0 fuite, 0 régression, -18 FP supplémentaires éliminés.
Sécurité: GLiNER ne peut rejeter que si confiance NER < 0.70.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 12:01:46 +01:00
bcd8013fa6 fix(phase2): Ajout stop words cliniques — 117 FP en moins (RESPI, NEPHRO, URINE, etc.)
Termes cliniques Trackare (RESPI, NEPHRO, CARDIO, PULMO, POST-OP, SPO2, etc.)
et termes médicaux (respiratoire, rénale, cardiaque, urine) ajoutés aux stop words.
Filtrés par NER EDS-Pseudo et selective_rescan. 0 fuite, 0 régression.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 09:58:58 +01:00
5972a09f9f fix(phase2): Élimination FP cross-line + word boundaries — 0 fuite, 0 FP médical
- 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>
2026-03-08 11:24:22 +01:00
58cb209e26 feat(phase2): Extraction layout-aware multi-colonnes — 322 fuites → 0, -103 FP
Phase 2 de l'amélioration qualité anonymisation :

1. Extraction multi-colonnes (PyMuPDF layout-aware) :
   - Nouvelle fonction _extract_page_layout_aware() détecte les layouts
     sidebar+corps (typiques des CRH/CRO hospitaliers)
   - Remplace pdfplumber comme extraction primaire (PyMuPDF blocks)
   - Élimine l'entrelacement de texte entre sidebar et corps médical
   - pdfplumber conservé pour les tables et comme fallback

2. Masquage FINESS multiline :
   - Détection "N° Finess\n[...]\n640000162" (label et numéro séparés)
   - Propagation globale du numéro FINESS sur toutes les pages
   - Gestion du format *640000162* (avec astérisques Trackare)

3. Masquage URLs hospitalières (www.ch-xxx.fr)

4. Nettoyage crochets doubles [[PLACEHOLDER]] → [PLACEHOLDER]

Résultats non-régression (30 fichiers audit) :
- Fuites : 322 → 0 (-100%)
- Faux positifs : 113 → 10 (-91%)
- 0 régression fonctionnelle
- OGC 1-59 : 0 fuite soignant, 0 FINESS, 0 lieu de naissance

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 18:19:08 +01:00
a356b63d68 fix: Corrections qualité Phase 1 — 261 fuites en moins, 0 régression
Audit sur 30 fichiers aléatoires (OGC 12-690) révélant un overfitting
sur les 59 premiers OGC. Corrections appliquées avec test de non-régression
à chaque étape :

- NDA pieds de page Trackare : regex Episode N. (227→0 fuites)
- ONDANSETRON : word boundary \b sur RE_NUMERO_DOSSIER (32→0)
- RPPS isolés : détection 11 chiffres dans docs Trackare (3→0)
- Stop words : retrait noms réels (ute, dogue, cambo, bains), ajout
  termes médicaux (AINS, ponction, hanche, burkitt, ORL, GDS, OAP...)
- Pattern DR. Prénom NOM : capture prénoms médecins (Ute ×19, Tam...)
- force_names : contextes structurés (DR., Signé, Note d'évolution)
  bypassent les stop words pour masquer les vrais noms de soignants
- Phase 2b : PiiHit trackare (EPISODE, RPPS) appliqués au texte .txt
- Framework de non-régression (regression_tests/) + batch audit 30 fichiers

Résultat : 322→61 fuites détectées, 113→109 faux positifs, 0 régression.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 17:32:28 +01:00
f0730b8211 Fix fuites soignants + lieux de naissance : 8/8 noms masqués, 0 lieu en clair
Corrections noms soignants (167 fuites → 0) :
- 5 patterns extraction Trackare : Note d'évolution, Signé, Signé—médicament,
  Flacon/Ampoule, timestamp HH:MM (ETCHEBARNE, ALVARADO)
- Fix tiret de troncature : "LACLAU-" masqué, "NOCENT-EJNAINI" préservé
- Décomposition noms composés : "LACLAU-LACROUTS" → LACLAU + LACROUTS individuels
- +22 stop words (FP trackare, timestamp, médicaments)

Corrections lieux de naissance (49 fuites → 0) :
- Regex élargie : accepte minuscules, codes INSEE, tout format
- Rescan sécurité : lieu de naissance + ville de résidence

Audit batch 130 fichiers : 0 fuite soignant, 0 lieu en clair, 0 régression PII.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 17:10:18 +01:00
40c34be471 chore: Avant implémentation Phase 1 corrections qualité 2026-03-02 23:34:06 +01:00
cfcf2eed4b fix: Corriger bug _DOCTR_AVAILABLE non défini
- 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
2026-03-02 21:19:48 +01:00