Commit Graph

188 Commits

Author SHA1 Message Date
34c681b791 feat(gui): GUI V6 G4 — alignement visuel sur la maquette v6 (option A)
Refonte de la couche présentation pour reprendre docs/ui_mockup_v6.html, sans
changer de techno UI ni la logique G1-G3.

- theme.py : 4 thèmes aux tokens EXACTS de la maquette (sombre #1a1a2e/#16213e/
  #e94560, clair, médical, neutre), palette complète + status_color.
- ui_kit.py (nouveau) : composants stylés (Card titrée, boutons primary/secondary/
  success/pilule, StatCard, ToggleRow) appliquant la palette.
- app.py : shell étroit, header identité + version + statut licence + liseré accent,
  barre d'onglets custom (plus de CTkTabview brut), navigation par recréation,
  changement de thème à chaud.
- tab_usage : carte Apparence (sélecteur de thème), dropzone stylée, grille formats,
  barre d'actions, progression à étapes + journal, résultats en cartes statistiques.
- tab_config : sous-navigation Réglages/Masquage/Partage/Règles ; Réglages câblé au
  ConfigState (profil, moteurs NER, dossier sortie).
- tab_about : grille d'informations + bloc licence (logique inchangée).

Logique inchangée : engine_bridge, config_state, license_client/store, runner.
Tests : +9 (theme). self-test exit 0, 55 tests gui_v6, 202 tests/unit (0 régression).
Smoke construction headless (Xvfb) : 3 onglets × 4 thèmes rendus sans erreur.
Pas de pywebview, aucun .exe.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 12:06:05 +02:00
23557d2cf9 feat(gui): GUI V6 G3 — câblage moteur, Configuration, licence UI, build-prep
G3-A câblage moteur réel (engine_bridge.py) : EngineSettings + NerManagers à
chargement paresseux (aucun manager à l'import), kwargs alignés CLI/V5
(make_vector_redaction=False, also_make_raster_burn=True, config_path, use_hf,
ner/gliner/camembert_manager, ogc_label) ; make_process_fn engine injectable ;
état managers not_loaded/loading/ready/unavailable, échecs optionnels tolérés.

G3-B Configuration (config_state.py + tabs/tab_config.py) : ConfigState →
EngineSettings, profils via profile_defaults (path injectable), options
raster/NER local/profil/sortie, état managers, sections admin-only via admin_mode.

G3-C Licence UI (machine_id.py + tab_about) : activation par clef
(LicenseClient.activate), bouton vérifier (check), affichage statut, aucun token
loggé, aucun appel réseau au démarrage (local_status seul).

Intégration : tab_usage exécute via le moteur réel selon ConfigState
(make_process_fn), anti double-lancement UI. app.py câble Config↔Usage↔licence.

G3-D build-prep : anonymisation_gui_v6_onefile.spec (entry V6, customtkinter +
modules gui_v6 en hiddenimports). Installateur Anonymisation.iss produit déjà la
cible Anonymisation-Setup.exe. Aucun artefact .exe commité ; build Windows à part.

Tests +14 (engine_bridge 8, config_state 6). self-test exit 0, 46 tests gui_v6,
193 tests/unit (0 régression). Moteur/V5/specs CLI intacts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 10:53:47 +02:00
c2c40543e5 feat(gui): add GUI V6 G2 — onglet Utilisation + runner injectable
Onglet Utilisation fonctionnel (couche présentation only) :
- processing_runner: runner testable sans display/moteur lourd, process_fn
  injectable (défaut = process_document en import paresseux), découverte
  fichier/dossier, sorties anonymise/ comme V5 (arbo préservée), progression,
  journal, résumé OK/KO, arrêt coopératif entre documents, anti double-lancement
- tabs/tab_usage: sélection fichier/dossier + nb PDF détectés, dossier sortie
  (défaut anonymise/), Lancer/Arrêter, barre de progression, statut, journal,
  résumé ; worker threadé, file d'événements drainée par after() ; aucun réseau
- app.py: onglet Utilisation câblé (placeholder G2 retiré)
- self-test: couvre processing_runner + tab_usage

Tests: +11 (runner) — discovery, sorties, échec partiel, arrêt, anti-double-run,
callbacks. self-test exit 0, 32 tests gui_v6, 179 tests/unit (0 régression).
Moteur/V5/managers/specs intacts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 18:58:10 +02:00
d265cd3269 feat(gui): add GUI V6 G1 foundation (license client/store, shell, About tab)
Socle de la refonte GUI V6 (couche présentation uniquement, aucune logique de
détection) :
- license_store: stockage licence hors dépôt (%LOCALAPPDATA%/Aivanov | XDG),
  read/write atomique/delete, ne journalise aucun token
- license_client: LicenseStatus + activate/check/local_status, session HTTP
  injectable, serveur indisponible géré sans crash, aucune clé privée
- theme: 4 thèmes + couleurs de statut licence
- app + tab_about: shell customtkinter minimal (header, bandeau licence,
  3 onglets), onglet À propos étoffé
- Pseudonymisation_Gui_V6.py: point d'entrée + --self-test (exit 0 sans fenêtre)
- requirements.txt: customtkinter==5.2.2

Tests: 20 nouveaux (store sur vrais fichiers, client sur session injectée).
Suite tests/unit: 167 passed, 0 régression. V5/moteur/managers/specs intacts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 18:50:23 +02:00
ae3e2050c1 feat(cli): add dedicated Inno Setup installer for the Windows CLI
Installateur Inno Setup séparé de la GUI (validé GO par Qwen), pour tests
internes et intégration de la brique CLI dans un autre logiciel.

- installer/Anonymisation-CLI.iss : AppId distinct de la GUI
  (B2F4A7C1-…), PrivilegesRequired=lowest, DefaultDirName
  {localappdata}\Programs\Anonymisation-CLI, source dist\Anonymisation-CLI.exe.
  Clés registre HKCU stables (InstallPath/ExePath/Version) + App Paths HKCU
  pour résolution tierce, supprimées à la désinstallation (uninsdeletekey).
  Pas de PATH système, pas de raccourci bureau. GUI .iss non modifiée.
- installer/Anonymisation-CLI-README.txt : usage, codes retour, lookup registre.
- scripts/build_windows_cli_installer_only.ps1 : build ISCC dédié,
  sortie release\Anonymisation-CLI-Setup.exe + SHA-256.
- docs/build-windows-oneclick.md : section « Installateur CLI dédié ».

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 16:44:58 +02:00
8790c64cca feat(cli): add Windows single-file anonymization entrypoint
CLI de production sans GUI pour anonymiser un fichier unique, validé GO par
Qwen (revue indépendante contrat/packaging/modèles) sur de vrais PDF.

- scripts/anonymize_cli.py (NOUVEAU) : contrat positionnel
  `Anonymisation-CLI.exe <fichier> <dossier_sortie>` (+ --out compat),
  chemins espaces/accents, codes retour 0/1/2/3/4.
  Chargement modèles fail-closed : CamemBERT-bio ONNX OBLIGATOIRE (code 3 si
  absent, aucun mode dégradé silencieux) ; EDS-Pseudo + GLiNER optionnels,
  tracés au log ; --no-ner = regex seul assumé. Résolution _MEIPASS frozen
  alignée sur launcher.py. Sortie burn raster identique GUI v5.
- anonymisation_cli_onefile.spec : entrypoint basculé vers anonymize_cli.py
  (le harnais perf D-19 anonymize_batch_cli.py reste hors build).
- docs/build-windows-oneclick.md : section « CLI Windows (sans GUI) »
  (build, usage, codes retour, modèles, limitations).

Tests Linux (vrais PDF) : --help OK, fichier manquant→2, --no-ner accents→0,
NER complet→0 (CamemBERT-bio + EDS-Pseudo chargés), modèle déplacé→3.
Build/smoke Windows à suivre (séparé). Commit CLI-only strict, distinct du P0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 14:26:11 +02:00
87f5e48d66 feat(anonymizer): add v11.5 P0 layout-aware detectors
Trois détecteurs simples « layout/context-aware » (chantier v11.5 P0),
validés par 2 revues Codex + 10 tests adversariaux Qwen, 0 régression :

- RE_ADRESSE réécrit en grammaire de tokens (_RE_VOIE_TYPE + _RE_VOIE_TOKEN) :
  capture initiales (« J. Loeb »), voies commémoratives à chiffres
  (« 8 Mai 1945 »), apostrophes ' et ’, bornage à la ligne courante,
  arrêt sur point post-mot (anti-débordement clinique).
- _mask_ville_gazetteers : retourne toujours un tuple (texte, liste) même
  sans Aho-Corasick ; masque les communes Saint/St/Sainte/Ste multi-mots à
  espaces (« St Martin de Hinx ») entièrement, sans exiger de contexte géo.
- DATE_NAISSANCE retiré de la propagation globale + DATE_NAISSANCE_GLOBAL
  ajouté aux skip vector/raster : on ne masque plus une date nue sur tout le
  document. La DDN reste masquée en contexte fort, page par page. Les dates
  cliniques identiques à la DDN hors contexte sont préservées.

tests/unit/test_p0_layout_detectors.py : 38 tests dédiés (matrice adresse
générique, anti-FP, communes Saint, propagation DDN, 10 tests adversariaux
Qwen). Suite tests/unit complète : 147 passed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 10:28:18 +02:00
0af71caffe fix(anonymizer): cover CHCB real-world staff layouts 2026-06-08 12:44:09 +02:00
41b64bf64f fix(anonymizer): handle FC14 practitioner OGC rules 2026-06-08 12:03:51 +02:00
c40441d03a fix(perf): apply MVP threading hotfix
Configure numerical library and torch threading for H1, keep raster threading/timing instrumentation, remove CONCERTATION from forced masks after real PDF FP testing, and record coordination archive state.
2026-06-08 10:41:15 +02:00
eb6e030183 docs(coordination): handoff fin de journée Dom + mise en veille Claude
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 20:55:38 +02:00
222b1d3970 docs(coordination): diagnostic perf MVP (D-19) — torch mono-thread + raster/OCR séquentiels
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 19:16:51 +02:00
7f03acb8fb docs(coordination): installateur bêta v11 + 4 sous-plans agents v11.5
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 18:15:59 +02:00
57aa0f0154 docs(coordination): plan v11.5 parallèle (4 agents) répondant à D-17
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 17:59:40 +02:00
65d3824f25 docs(coordination): rapport rebuild v11 + pack bêta (C-BETA-1..4)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 12:32:02 +02:00
080faac7ed docs(coordination): ack T-N/T-O Qwen + trace sauvegarde/repart propre build
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 12:13:05 +02:00
15f73f8ded chore(beta): C-BETA-1 hygiène finale repo
- gitignore graphify-out/ (artefacts knowledge graph générés)
- commit messages coordination 2026-06-05 (ordre de marche Dom via Codex)
- commit rapport analyse campagne GUI (synthétique, sans PII)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 10:56:02 +02:00
68ec34574c docs(coordination): rétrograde T-N (ONNX non bloquant) + T-O prioritaire
Vérif code : modèle custom embarqué dans l'EXE au build, autres modèles
téléchargés au 1er lancement. T-N → pérennité backup (priorité normale).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 09:46:02 +02:00
f1fc28ac0b docs(coordination): assigne T-N (modèle ONNX) + T-O (validation pack bêta) à Qwen + log cleanup
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 17:03:48 +02:00
bf79e445f5 docs(coordination): protocole de coordination + décisions + inbox + log + vision
- docs/coordination/ : README, decisions (no-ui, pivots MVP), inbox Claude/Qwen/Dom, archive, log, etat-projet
- docs/installation/ : procédure SmartScreen
- docs/reflexions/ : vision fonctionnelle avant prod

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 16:31:06 +02:00
2d23f6c31a build(windows): scripts build one-click + installer + doc
- build_windows_oneclick.bat / build_windows_installer_oneclick.bat : wrappers
- scripts/build_windows_oneclick.ps1 / build_windows_installer_only.ps1 / install_inno_setup_build_dep.ps1
- build_signing.example.ps1 : exemple protocole signing (sans secret)
- docs/build-windows-oneclick.md : documentation du build

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 16:31:06 +02:00
a02e89b7ba test: non-régression F5 + batch paths + masquage manuel + layouts réels
- test_f5_nom_compose_orphelin.py : 13 tests (regex F5, application, scénario Trackare EJNAINI)
- test_gui_batch_paths.py / test_manual_masking.py : couverture des modules
- test_real_world_identifier_layouts.py : non-régression layouts réels (D-15)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 16:30:56 +02:00
91a128d1aa feat: modules batch paths + masquage manuel + templates de masque
- gui_batch_paths.py : listing documents + construction chemins de sortie batch
- manual_masking.py : masquage manuel piloté par templates YAML
- config/mask_templates/ : template FC19

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 16:30:56 +02:00
4b1ab3a7ba build(deps): ajoute pyahocorasick aux requirements (C-1 partiel)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 16:30:56 +02:00
9f4fe1b110 chore(rgpd): untrack sorties PII pdf_natif + gitignore RGPD/caches/admin
- Ajoute pdf_natif/, ano/pdf_natif/pseudonymise/, .admin, .claude/, .codex-loop/, .qwen/ au .gitignore
- Untrack 48 fichiers PII (.pseudonymise.txt + .audit.jsonl) encore suivis sous pdf_natif/
- Stage 12 suppressions résiduelles sous ano/pdf_natif/pseudonymise/
- Conformité D-12 (aucune PII versionnée)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 16:30:42 +02:00
299bbee5ff 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>
2026-06-03 12:02:53 +02:00
c110de4a2e feat(T-I): validateur paranames + filtre mots-outils FR du gazetteer
Validateur scripts/validate_paranames.py exécuté sur le gazetteer réel,
révèle 2 défauts → corrigés :

- Mots-outils FR (avec/dans/voir/...) présents dans INSEE/paranames →
  risque FP au contexte 'low'. Ajout de 347 mots-outils spaCy fr (sûrs,
  filtrés des patronymes INSEE fréquents) à stopwords_manuels.txt.
  build_paranames_gazetteer.py filtre désormais aussi contre ce fichier ;
  gazetteer reconstruit (1 379 196 noms, mots-outils ≥3 chars retirés).
- Priorité sécurité respectée : allez/polygone sont de vrais patronymes
  INSEE rares → laissés MASQUABLES (pas de fuite), hors stopwords.
- OYARCABAL reclassé en warning (couvert par regex F3, absent de Wikidata).

Garde-fous vérifiés : Petit/Boucher/Berger conservés, noms étrangers
(EJNAINI/NGUYEN/...) conservés. Validateur 5/5. tests/unit 85 passed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 11:20:21 +02:00
87377a54de test(T-G): réparer corpus synthétique post-cleanup CHCB + dégel 009
- Fixtures 001/003/004/005/010 : CHCB → CHUXX (D-12)
- 009 : Biarritz désormais masqué [VILLE] (bug connu résolu par F1-F4),
  retrait de KNOWN_FAILURES + restauration de Biarritz dans must_not_contain
- test_q1_quarantine.py : tests réels B-3/D2/D3/M5/INDEX/errors.log
  (ex-squelette xfail)

Suite tests/unit : 85 passed, 0 failed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 10:31:38 +02:00
758a36200f fix(detect): exclure 'appartement' du gazetteer FINESS (générique)
L'entrée mono-mot 'appartement' de etablissements_distinctifs.txt
matchait à tort en ETAB_FINESS (ex. « 17 boulevard Thiers, appartement 3B »
→ appartement masqué [ETABLISSEMENT]). Ajout à generic_name_blacklist.txt.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 10:31:38 +02:00
54bb05ce64 docs(decision): D-14 architecture plateforme licence app.aivanov.fr
Acte la décision Dom sur l'architecture du système licence post-MVP :

## Choix clé : plateforme client centralisée (pas de licence locale isolée)

- Hébergement : infra OVH existante Dom (HDS, ISO 27001, ultra-HA)
- Domaine : app.aivanov.fr (extensible à d'autres apps Dom)
- Stack : FastAPI + PostgreSQL + HTMX/Jinja2 + fastapi-users + Brevo
- Côté programme : RSA-PSS 2048 signé, vérif locale + phone home 30j

## Modèle métier

- 1 licence = 1 poste (modèle Microsoft Office classique)
- Abonnement annuel
- Grace period expiration : 15 jours
- Mode hors-ligne max : 30 jours
- Révocation : effective au prochain check
- Paiement intégré : Phase 3 (post-août)

## Pourquoi self-hosted (vs Keygen.sh SaaS)

- Souveraineté : données en France (HDS obligatoire pour santé)
- Économie long terme (50 clients ROI < 1 an)
- Évite dépendance à un tiers américain
- Customisation totale (futures intégrations Pro Santé Connect)
- Dom dispose déjà de l'infra OVH HDS/ISO 27001

## Roadmap

- Phase 0 (bêta Réunion) : pas de licence, livraison directe
- Phase 1.1 (juin-juillet) : module license.py côté programme (~12h)
- Phase 1.2 (juin-juillet) : plateforme MVP (~50h)
- Phase 2 (août) : self-service complet (~40h)
- Phase 3 (post-août) : paiement intégré (~60h)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-02 18:36:23 +02:00
b651a26cc0 feat(admin): D-13 partial — bannière "MODE ADMIN" + doc périmètre
## Bannière mode admin

Ajout d'un suffixe "[⚙ MODE ADMIN]" dans le titre de la fenêtre principale
quand `admin_mode.is_admin()` retourne True. Signal visuel clair pour :
- Le bêta-testeur (s'il bidouille, il voit qu'il a déverrouillé quelque chose)
- L'opérateur Dom (pour vérifier d'un coup d'œil que le mode admin est actif
  pour ses propres tests)

## Périmètre D-13 partial

Documenté dans `decisions/2026-06-02_dom_d13-partial-scope.md` :

| Protection | Statut |
|---|---|
| VLM Ollama caché en non-admin |  (D-11) |
| Titre fenêtre signalé en admin |  (ce commit) |
| Stopwords personnalisés | ⏭ Reporté v11.5 |
| Profils techniques (regex_overrides, force_terms) | ⏭ Reporté v11.5 |
| Choix moteur NER | ⏭ Reporté v11.5 |
| Sauvegarde configs sensibles | ⏭ Reporté v11.5 |

## Pourquoi le report est OK pour MVP

1. Le risque RGPD critique (envoi externe à Ollama) est résolu par D-11
2. Les autres réglages, bien que visibles, ne déclenchent pas de fuite
3. La transposition customtkinter v6 (v11.5) refondra l'UI — patcher
   2874 lignes tkinter aujourd'hui = double travail à refaire en v6
4. Le bêta-testeur n'a pas accès au mode admin (pas de fichier .admin
   livré, pas d'env var par défaut)

## Activation manuelle

- Env : `ANON_ADMIN=1 python Pseudonymisation_Gui_V5.py`
- Fichier : créer `.admin` à la racine

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-02 17:04:01 +02:00
40c6f23ce0 feat(admin): D-11 Ollama VLM caché par défaut + module admin_mode
## Module admin_mode.py

Nouveau module qui détecte si l'application tourne en mode admin :
- Variable d'environnement `ANON_ADMIN=1` (ou `true`/`yes`/`on`)
- OU fichier `.admin` à la racine de l'application

Expose :
- `is_admin()` — retourne bool, caché en module
- `admin_required(feature_name)` — garde qui lève RuntimeError si pas admin

Pas de mot de passe — c'est un verrou "interdit aux distraits" pour ne
pas exposer au bêta-testeur des options sensibles (envoi à Ollama, conf
critique). Le vrai durcissement viendra avec D-13 (mode admin complet).

## GUI — VLM Ollama caché par défaut (D-11)

Dans Pseudonymisation_Gui_V5.py, après l'import classique de VlmManager,
on force VlmManager = None et VlmConfig = None **si le mode admin n'est
pas actif**.

Effet :
- Bêta-testeur lambda : VLM Ollama complètement invisible et inactif
  (économise aussi la RAM du modèle CamemBERT-bio + downloads Ollama)
- Mode admin activé : comportement actuel inchangé

Tests manuels :
- import GUI sans env : VlmManager = None 
- `ANON_ADMIN=1 python -c "import Pseudonymisation_Gui_V5"` : VlmManager
  est <class 'vlm_manager.VlmManager'> 

## Reste à faire (D-13)

- Mode admin = mot de passe / fingerprint
- Cacher dans l'UI les widgets liés au VLM (cases à cocher, etc.)
- Cacher d'autres réglages sensibles (stopwords personnalisés,
  regex_overrides, force_terms)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-02 16:48:59 +02:00
4a6f743cf8 fix(detect): add "das" to stopwords (acronyme PMSI, pas un nom)
Sur le corpus FC, "DAS" était détecté comme nom de famille INSEE en
contexte fort (suivi de "DR") et compté comme leak audit par le scoring.

En réalité, DAS est un **acronyme PMSI / T2A** :
- DP = Diagnostic Principal
- DR = Diagnostic Relié
- **DAS = Diagnostic Associé Significatif**

Contexte typique :
    DR
    DAS
    Actes
    Rappel : un code CIM de DAS suivi d'un astérisque correspond à
    une CMA exclue par le DP

Le pipeline pensait "Dr. DAS" = médecin nommé DAS. Ajout de "das" aux
stopwords pour bloquer la détection.

Risque résiduel : si un vrai patient/médecin nommé DAS existe, il ne
sera pas masqué. C'est un trade-off acceptable car le PMSI utilise DAS
partout dans les rapports T2A.

Impact attendu : score qualité FC remonte 99.3 → ~100/100 (1 leak audit
fictif éliminé).

Découverte par Qwen dans son audit du 2026-06-02 14:50.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-02 16:47:32 +02:00
099d2c32a3 feat(detect): paranames gazetteer Wikidata (1.4M noms + 502K prénoms)
Intégration de paranames (bltlab/paranames v2024.05.07.0, CC BY 4.0)
pour étendre la couverture du gazetteer aux noms étrangers en France
absents d'INSEE (basques, maghrébins, asiatiques, africains, etc.).

## Citation

Sälevä, J., & Lignos, C. (2024). ParaNames 1.0: Creating an Entity Name
Corpus for 400+ Languages using Wikidata. In Proceedings of LREC-COLING
2024. https://aclanthology.org/2024.lrec-main.1103/

## Fichiers

- scripts/build_paranames_gazetteer.py — script reproductible
- data/paranames/README.md — attribution + procédure
- data/paranames/EXTRACTION.md — workflow reproductible
- data/paranames/noms_famille_world.txt.gz — 1 379 609 noms (4.3 Mo gz, <30 Mo RAM)
- data/paranames/prenoms_world.txt.gz — 502 302 prénoms (1.4 Mo gz)

## Volume final

Réduction significative vs estimation initiale (~80 Mo) grâce à NFKD+A-Z
qui fusionne toutes les translittérations Wikidata (cyrilliques, arabes,
chinoises…) en latin de base. Résultat : 4.3 Mo gz total, ~30 Mo RAM.

## Spot-check

| Nom | Présent ? | Note |
|---|---|---|
| EJNAINI |  | Le cas de fuite résiduelle audit_30 — devrait être fixé |
| OYARZABAL |  | Variante basque |
| OYARCABAL |  | Orthographe franco-espagnole rare, absente Wikidata |
| NGUYEN, SCHMIDT, OBAMA, NAKAMURA, GARCIA, MARTIN, BERNARD |  | OK |

## Intersection INSEE

- ∩ INSEE FR : 130 340 noms (59.5 % de couverture INSEE)
- Gain net : 1 249 269 noms supplémentaires (focus diaspora / DOM-TOM)

## Risque FP identifié

Quelques mots français courants sont présents dans paranames (origine :
noms d'autres langues) : VOIR, ALLO. MIDI déjà filtré par stopwords.
Impact à mesurer sur retraitement audit_30. Si nécessaire, ajout d'un
filtre dictionnaire français à apporter ultérieurement.

## Source

- Dépôt : https://github.com/bltlab/paranames
- Mirror HF (utilisé) : https://huggingface.co/datasets/imvladikon/paranames
- License : CC BY 4.0
- Origine : Wikidata (entités publiques) — pas de PII fuitée

REJETÉ comme alternative : philipperemy/name-dataset (origine = leak
Facebook 2021, RGPD bloquant pour produit médical).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-02 16:02:54 +02:00
9d2fd4052d feat(detect): paranames loader + fallback étendu cross-validation
Préparation à l'intégration du gazetteer paranames (Wikidata CC BY 4.0,
Sälevä & Lignos LREC-COLING 2024) qui couvrira les noms étrangers en
France absents du gazetteer INSEE (basques, maghrébins, asiatiques,
africains, etc.).

## Loader

- `_PARANAMES_NOMS_SET` + `_PARANAMES_LOADED` (cache global)
- `_load_paranames_noms()` : lazy load au 1er besoin
- Fichier cible : `data/paranames/noms_famille_world.txt.gz`
- Si fichier absent : retourne set vide, log INFO, comportement actuel
  (INSEE seul) — fallback transparent
- Si erreur de lecture : log WARNING, fallback INSEE

## Intégration cross-validation

Dans `_cross_validate_name_candidates`, `is_in_insee` étendu :
    is_in_insee = (tok_upper in insee_noms or tok_upper in insee_prenoms
                   or tok_upper in _load_paranames_noms())

Effets :
- En contexte "low" + non NER : un token comme OYARCABAL (basque) ou
  EJNAINI (maghrébin) sera désormais accepté si présent dans paranames.
- Aucun changement pour noms FR (déjà dans INSEE).
- Aucune régression : si le fichier paranames n'est pas généré, le
  comportement est strictement identique.

## Génération du gazetteer

Le script de génération `scripts/build_paranames_gazetteer.py` et le
fichier `data/paranames/noms_famille_world.txt.gz` sont produits par un
agent dédié en cours d'exécution. Commit séparé à venir avec :
- Script de génération
- README + attribution CC BY 4.0
- Fichier gazetteer

## Tests

74 passed sur 75 (1 test happy path Q-1) + 10 xfailed. 5 tests
synthetic_review cassés (non liés à ce commit — issue séparée du
CHCB cleanup à fixer dans un commit dédié).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-02 15:48:54 +02:00
f66df3f5ce fix(scripts): reprocess_audit30 path local Dom (env override) (D-12 fixup)
L'agent CHCB cleanup a remplacé CHCB → CHUXX dans le path SOURCE_ROOT
mais le vrai dossier sur le disque Dom s'appelle bien
'II-1 Ctrl_T2A_2025_CHCB_DocJustificatifs (1)'. Ça a cassé toutes
les recherches PDF (29/29 MISSING).

Fix : lecture du path depuis env var ANON_AUDIT30_SOURCE avec fallback
sur le path local réel. Le nom CHCB est dans le path filesystem chez
Dom, pas une référence sémantique à anonymiser.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-02 14:47:09 +02:00
96f9691395 feat(detect): F2 capture du nom précédant le label "Nom usuel :"
Complète F3 (qui captait le nom APRÈS "Nom usuel :"). Dans certains
comptes-rendus type BACTERIO, l'identité patient sous forme
"NAME Prenom1 Prenom2" apparaît juste AVANT le label, sans label devant.

Cas typique BACTERIO 23232115 :
    10.40
    SIMONET Marie lise        ← cette ligne, pas attrapée par F3
    Nom usuel :
    14/03/1985
    OYARCABAL                 ← capturée par F3

Ajout de RE_EXTRACT_NAME_BEFORE_NOM_USUEL qui regarde la ligne
précédant directement le label "Nom usuel :" : si elle ressemble à
"MAJUSCULES Prenom Prenom" (NAME ≥4 chars + 1 à 3 tokens
en suite), on la capture en contexte "high" (champ DPI quasi-certain).

Validation sur exemple synthétique :
- F3 OYARCABAL : ['OYARCABAL'] 
- F2 SIMONET : ['SIMONET Marie lise'] 

Reste à valider sur retraitement audit_30 complet.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-02 14:44:59 +02:00
e7380ed258 chore(rgpd): replace remaining CHCB/Bayonne refs after re-verification (D-12)
Re-applique les remplacements dans anonymizer_core_refactored_onnx.py
(commentaires reverted par un linter entre les commits) et corrige
docs/coordination/inbox/for-dom/2026-06-02_qwen_owncloud-livraison-procedure.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-02 14:42:40 +02:00
6299bd1309 chore(gitignore): exclude corpus_validation + tests/ground_truth + silver_annotations (PII)
Étend .gitignore pour exclure les répertoires de travail contenant des
données patient réelles (corpus_validation/, regression_tests/baseline/,
tests/ground_truth/, tests/phase1_production_test/, data/silver_annotations/*.bio,
test_chcb_leak/, test_3ogc/, test_anonymise/, test_gui_output/).

Retire ces fichiers du suivi git (git rm --cached) sans les supprimer du
disque local. Conforme à la décision D-12.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-02 14:41:14 +02:00
c427e2a3f4 chore(rgpd): replace CHCB/Bayonne refs in docs (D-12)
Anonymise les références aux entités réelles (CHCB, villes basques,
Saint-Denis, Réunion, etc.) dans la documentation projet, les maquettes
HTML/Python, les notes de coordination et les audits.

Conserve docs/coordination/decisions/2026-06-02_dom_mvp-pivots-strategiques.md
(table de mapping de référence) et docs/coordination/inbox/for-claude/
intacts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-02 14:40:20 +02:00
1c44a26eb3 chore(rgpd): replace CHCB/Bayonne/Saint-Denis/Réunion refs in source + configs (D-12)
Anonymise toutes les références à des entités réelles (CHCB, Bayonne, Saint-Denis,
Réunion, etc.) dans le code source, les configurations YAML, les scripts/outils,
et les tests unitaires. Conserve les tests synthétiques (cases) intentionnels.

- profile key chcb_strict → chuxx_strict
- CHCB → CHUXX, Bayonne → Chicago, Saint-Denis → Springfield,
  Réunion → Province Bêta, 64100/97400 → 12345, FINESS → 999999999,
  préfixe tél 05.59.44 → 0X.XX.XX
- renomme tools/test_chcb_leak.py → tools/test_force_term_leak.py

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-02 14:39:21 +02:00
a1ef2225d5 feat(detect): F3 capture du nom après label "Nom usuel :"
Le pipeline ne reconnaissait pas le label "Nom usuel :" — utilisé dans
certains comptes-rendus type BACTERIO. Ajout d'une regex dédiée
RE_EXTRACT_NOM_USUEL qui :

1. Trouve "Nom usuel :" en début de ligne
2. Skippe les lignes qui ne commencent pas par une lettre majuscule
   (date au format DD/MM/YYYY, placeholders entre crochets, lignes vides)
3. Capture le premier token en MAJUSCULES ≥4 chars

Cas couvert : BACTERIO 23232115 contient
    SIMONET Marie lise
    Nom usuel :
    14/03/1985
    OYARCABAL

OYARCABAL est ainsi extrait avec contexte "high" (champ DPI structuré
quasi-certain) et masqué.

Test unitaire rapide validé sur l'exemple ci-dessus.

Reste à faire : F2 (SIMONET — pattern NAME+PRENOM+PRENOM sans label) — non
trivial sans label, à implémenter avec heuristique contextuelle (top du doc,
etc.). Reporté.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-02 14:35:33 +02:00
c8ac2e356a chore(scripts): add reprocess_audit30.py for quality regression testing
Petit utilitaire pour re-traiter le corpus audit_30 avec le code courant
et générer un dossier de sortie horodaté.

Usage:
    python scripts/reprocess_audit30.py [--out /tmp/.../foo] [--no-ner]

Lit la liste des 29 docs depuis evaluation/baseline_scores.json, retrouve
chaque PDF source dans /home/dom/Téléchargements/.../CHCB_DocJustificatifs,
appelle process_pdf() pour chacun, sortie dans /tmp/reprocess_audit30/
(ou --out).

Permet ensuite de mesurer la qualité avec :
    python scripts/evaluate_quality.py --dir <output> --compare

Validé sur audit_30 — 29 docs en ~4 min avec NER ONNX.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-02 14:26:02 +02:00
af3fb53772 feat(detect): F1 décomposition noms à trait d'union + F4 filet INSEE opt-in
## F1 — Décomposition noms composés (corrige GRAND, EJNAINI)

Quand le NER détecte un nom à trait d'union (ex "Romain BILLON-GRAND",
"Cécilia NOCENT-EJNAINI"), le regex `\bBILLON-GRAND\b` ne traverse pas le
saut de ligne du formatage Trackare en colonnes étroites ("BILLON-\nGRAND").

Solution dans `_apply_extracted_names` : pour chaque nom validé contenant un
`-` (et ≥5 chars), ajouter aussi les sous-tokens (≥4 chars) à `safe_names`.
Les sous-tokens héritent du `bypass_stopwords` du composé (cas Dr/Mme).

Validation sur audit_30 :
- GRAND : 17 → 0 occurrences 
- Score global : 97.9 → 98.3 (+0.4)
- leak_audit : 3 → 1

## F4 — Filet rescan résiduel élargi noms INSEE (OPT-IN)

Le rescan post-anonymisation ne couvrait que NIR/EMAIL/IBAN/TEL. Ajout
d'un check sur les tokens uppercase ≥4 chars présents dans le gazetteer
INSEE (`_INSEE_NOMS_FAMILLE`), hors stopwords médicaux, hors placeholders,
hors whitelist utilisateur.

**Désactivé par défaut** (`cfg["rescan"]["check_insee_names"] = False`).

Raison : INSEE contient beaucoup de mots français courants (VOIR, ALLO,
POLYGONE, MIDI, FAURE, …) qui produisent un sur-masquage massif. Sur le
corpus audit_30, F4 activé met 29/29 docs en quarantaine. Inutilisable
en l'état mais utile pour un futur profil "paranoid" avec filtre par
fréquence INSEE rare + dictionnaire français en exclusion.

À activer via :
    cfg["rescan"]["check_insee_names"] = True

## Restant

- F2 (SIMONET) : pattern NAME+PRENOM+PRENOM → medium (à implémenter)
- F3 (OYARCABAL) : label "Nom usuel :" → high sur ligne suivante (à implémenter)
- EJNAINI : mystère — fix F1 devrait suffire mais ne suffit pas, à investiguer

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-02 14:25:52 +02:00
b3c935f30a chore(archives): move 6 legacy GUI/pipeline files to archives/legacy_gui/
## Fichiers déplacés (git mv, historique préservé)

- Pseudonymisation_Gui_Models_V4.py (V4 obsolète)
- pseudonymisation_pipeline_gui_v3.py (V3 obsolète)
- Pseudonymisation_Pipeline_Robuste_Patch.py (oct 2025, abandonné)
- pseudonymisation_pipeline_robuste.py (oct 2025, abandonné)
- test_gui_error.py (test orphelin V4)
- test_gui_fixed.py (test orphelin V4)

## Pourquoi

Pour éviter toute confusion avec la GUI active (Pseudonymisation_Gui_V5.py)
maintenant que le stash WIP 2026-04-27 (profils + masques + build windows)
a été appliqué et que Dom va y faire des modifications avant le MVP.

## README ajouté

archives/legacy_gui/README.md documente le contenu, les raisons d'archivage,
les fichiers actifs en production, et la procédure de restauration.

## Restauration

Réversible via : git mv archives/legacy_gui/<file> .

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-02 11:22:26 +02:00
380e520013 feat(gui): apply WIP profils+masques+build-windows from stash (2026-04-27)
Application du stash@{0} resté en WIP depuis le 27/04 :
  "On main: wip-gui-profils-masque-manuel-build-windows-2026-04-27"

## Apport

- Pseudonymisation_Gui_V5.py (+1208 lignes) : profils, panneau paramètres
  avancés, éditeur de masques intégré, gestion whitelist/blacklist
- launcher.py (+315) : splash natif PyInstaller, single-instance,
  téléchargement modèles
- anonymisation_onefile.spec : config PyInstaller mise à jour
- pdf_mask_designer.py (+114) : éditeur de masques amélioré
- config_defaults.py (+23) : constantes nouvelles
- tests/unit/test_config_externalization.py (+12) : tests config
- .gitignore (+5)

## Pourquoi

La version courante de la GUI sur la branche feature manquait :
- L'éditeur de masques
- Les profils
- Le panneau paramètres avancés
- Le splash natif au démarrage

Aucun conflit avec mes 10 commits Q-1 (pas de chevauchement de fichiers).

## Validation

75 passed, 10 xfailed sur pytest tests/unit/.

## Note

Le stash reste disponible dans `git stash list` jusqu'à drop explicite.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-02 11:09:46 +02:00
5d89eaf8dc feat(q1): G - B-1 métadonnées sortie (audit.jsonl + XMP PDF)
Implémentation de la traçabilité B-1 sur les sorties d'anonymisation.

## .audit.jsonl — entrée metadata en 1ère ligne

Chaque .audit.jsonl commence maintenant par une entrée :
  {"type": "metadata",
   "app_version": "0.11.0-mvp",
   "build_date": "...",
   "build_commit": "...",
   "build_branch": "...",
   "processed_at": "<iso>",
   "document_name": "...",
   "ocr_used": bool,
   "extracted_chars": int,
   "quarantine_flags": []}

Permet de prouver a posteriori avec quelle config un document a été
anonymisé (audit DPO / CNIL).

## XMP PDF — _apply_pseudo_xmp_metadata()

Helper appelé avant doc.save() dans redact_pdf_vector et redact_pdf_raster :

1. doc.set_metadata({}) — efface TOUTES les métadonnées source
   (CRITIQUE : les PDF source peuvent contenir le nom patient dans
   /Author, /Title, /Keywords)
2. Pose nos métadonnées : creator/producer "Pseudonymisation v...",
   title="Document anonymise", author vide, keywords avec commit+ts
3. Garde-fou : log + overwrite si une métadonnée source survit
   (defense in depth)

## Constantes module-level

- APP_VERSION = "0.11.0-mvp" (à incrémenter avant chaque rebuild release)
- BUILD_DATE/BUILD_COMMIT/BUILD_BRANCH chargés depuis build_info.py
  (regénéré à chaque rebuild EXE). Fallback "dev/unknown" en dev.

## Tests

74 passed, 10 xfailed — pas de régression.

Ref: docs/coordination/inbox/for-dom/2026-05-29_consolide_pseudocode-Q1-v2.md §7

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-02 10:59:58 +02:00
c4883291d3 test(q1): add test_q1_quarantine.py — 11 tests (1 actif, 10 xfail strict)
Squelette de tests TDD pour Q-1 quarantaine différentielle.

État au commit :
- test_happy_path_no_quarantine_created_if_no_failure  actif (passe)
- 10 tests en xfail strict, à dégeler au fur et à mesure :
  * B-3 préflight (2 tests)
  * Q-1 quarantine flow (3 tests)
  * B-1 metadata (2 tests)
  * B-2 logs (2 tests)
  * INDEX.md (1 test)

Validation : 74 passed, 10 xfailed sur tests/unit/.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-02 10:45:00 +02:00
cf78bea910 feat(q1): F+sécurité — rescan inconditionnel + hardening quarantine
Suite des étapes Q-1 (F = rescan résiduel) + apport sécurité par Qwen
review Codex gpt-5.5 5 rounds (verdict READY FOR MERGE).

## anonymizer_core_refactored_onnx.py

- M5 Rescan résiduel inconditionnel : NIR/EMAIL/IBAN/TEL recherchés après
  TOUT nettoyage. Fail-closed — aucun output livré si > seuil
  (SEUIL_RESCAN_RESIDUEL = 0)
- M3 Return structuré : process_pdf retourne maintenant
  {"status": "quarantined", "reason": ..., "text": "", "audit": ""} au lieu
  de {} sur quarantaine — callers compatibles avec outputs["text"]/"audit"
- C3+M2 fallback préflight : si quarantine_mgr absent ET préflight rate,
  copie du PDF source dans out_dir/_preflight_failed/ avec chmod 0o700
  (le document n'est jamais perdu silencieusement)
- S5 guard double raster : "pdf_raster" not in outputs avant fallback
- Retrait import DocLogger (mort, jamais branché)

## quarantine.py

- _sanitize_doc_name() — anti path-traversal sur le nom de doc
- _escape_markdown_table_cell() — anti injection markdown dans INDEX.md
- _secure_quarantine_dir() — mkdir + chmod(0o700) systématique
- _append_errors_log() durci :
  os.open(O_CREAT|O_APPEND|O_WRONLY|O_NOFOLLOW, 0o600)
  + fcntl.flock(LOCK_EX) + os.fchmod
- Retrait DocLogger (code mort identifié en review)
- Retrait REASON_CODES (jamais utilisé)

## Limites connues

- QuarantineManager pas encore wired dans GUI/server.py — les callers
  actuels marchent en fallback (quarantine_mgr=None)
- finalize() + ProcessPoolExecutor : entries worker-local ne mergent pas
  automatiquement (à documenter)

## Validation

- 73 tests unit existants : OK (non-régression)
- 1 test Q-1 happy path : passe (dégelé dans commit suivant)
- Codex gpt-5.5 5 rounds review : READY FOR MERGE

Co-Authored-By: Qwen Code <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-02 10:44:52 +02:00
5216a1518e feat(q1): E - B-3 preflight text too short, quarantine direct
Étape E du sprint Q-1 — B-3 pré-flight.

Si extract_text_with_fallback_ocr retourne moins de SEUIL_TEXTE_MINI
(=100) caractères :
- log.warning systématique
- Si quarantine_mgr fourni : flag preflight_text_too_short (severity=full),
  copie du PDF original dans quarantine_dir/ pour ré-essai manuel
- Return {} (pas de sortie texte/audit/PDF pour ce doc)

Couvre les cas : scan non-OCRisé, PDF vide, OCR raté.

Évite le pire scénario : un opérateur qui croit que son document est
anonymisé alors qu'aucune PII n'a même été détectée parce qu'il n'y
avait pas de texte à traiter.

Rétro-compat préservée : sans quarantine_mgr, le comportement reste
"return {}" + log au lieu du silence (toujours strictement meilleur).

Risque appelants : un caller qui suppose la présence des clés "text"/
"audit" dans le retour doit gérer le cas dict vide. À voir au runtime.

Ref: docs/coordination/inbox/for-dom/2026-05-29_consolide_pseudocode-Q1-v2.md §8

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-29 21:39:47 +02:00