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>
18 KiB
Audit complet — Projet Anonymisation de documents médicaux
Date : 28 mai 2026
Audit réalisé par : Qwen Code
Répertoire analysé : /home/dom/ai/anonymisation/
Sommaire
- Synthèse globale
- Risques critiques
- Risques importants
- Appréciations positives
- Recommandations prioritaires
- Métriques
- Rapport détaillé par domaine
1. Synthèse globale
| Domaine | Appréciation | Risque |
|---|---|---|
| Architecture | ⚠️ Moyen | Élevé |
Core (anonymizer_core_refactored_onnx.py) |
⚠️ Fonctionnel mais fragile | Élevé |
| Qualité du code | ⚠️ Moyen | Moyen |
| Tests | ✅ Bon | Moyen |
| Documentation | ✅ Très bon | Faible |
| Sécurité / Conformité | ✅ Bon sur le papier, ⚠️ dans le code | Moyen |
| Build / CI/CD | ⚠️ Moyen | Moyen |
| Gestion du code mort | ❌ Problématique | Moyen |
2. Risques critiques
2.1 Fuites PII silencieuses (except Exception: pass)
Le core de 4 770 lignes contient ~47 clauses except Exception, dont ~20 sont des silences purs (pass). Les plus dangereux :
| Localisation | Ligne(s) | Problème |
|---|---|---|
extract_text_with_fallback_ocr |
~1118-1156 | Chaque passe d'extraction PDF (PyMuPDF, pdfplumber, pdfminer, docTR) capture l'exception sans log. Si PyMuPDF échoue silencieusement, on ne sait jamais pourquoi. |
redact_pdf_vector → apply_redactions() |
~3938 | Si la rédaction PDF échoue, le PDF de sortie peut être non anonymisé sans aucun avertissement. |
_rasterize_page (police DejaVu) |
~3991 | Fallback de police silencieux. |
process_pdf (VLM et NER) |
~4137, 4202 | Dégradation gracieuse acceptable, mais aucun log même en debug. |
Rédaction vectorielle dans process_pdf |
~4655 | Tout le bloc de rédaction est dans un try/except: pass. Le PDF peut ne pas être généré. |
Impact : Un document contenant des données de santé personnelles (PHI) pourrait être délivré non anonymisé. Dans le contexte médical, c'est un risque réglementaire majeur (RGPD, hébergement HDS).
Recommandation : Remplacer systématiquement except Exception: pass par except Exception as e: log.warning("...", exc_info=e) sur les chemins critiques. Minimum : logguer l'erreur.
2.2 Chemin absolu hardcodé dans le .spec
# anonymisation_onefile.spec
app_dir = 'C:\\Users\\dom\\ai\\anonymisation'
Le build PyInstaller ne fonctionne que sur la machine de dom. Tout rebuild sur une autre machine échouera ou produira un binaire cassé.
Recommandation : Utiliser Path(__file__).parent ou une variable d'environnement.
2.3 Regex recompilées à chaque ligne
Des regex sont compilées inline dans _mask_line_by_content, appelée pour chaque ligne de chaque page :
_re_ville_date = re.compile(r"...", re.MULTILINE)
_re_lieu = re.compile(r"(...)")
_re_ville_res = re.compile(r"(...)")
_stop_rx = re.compile(_MEDICAL_STOP_WORDS, re.IGNORECASE)
Sur un document de 50 pages / 2 000 lignes → 2 000 recompilations inutiles. Dégradation estimée : 3-5x sur gros documents.
Recommandation : Compiler ces regex au niveau module (une seule fois) en variables globales.
3. Risques importants
3.1 Deux build systems parallèles incohérents
| Système | Point d'entrée | Fonctionnalités |
|---|---|---|
PyInstaller (.spec) |
launcher.py |
Splash, single-instance, téléchargement modèles |
Nuitka (build_windows.bat) |
Pseudonymisation_Gui_V5.py |
Direct GUI, sans setup |
Les deux produisent des expériences utilisateur différentes et ciblent des points d'entrée différents.
3.2 Core double
| Fichier | Lignes | Statut |
|---|---|---|
anonymizer_core_refactored.py |
388 | Version incomplète, sans NER ONNX |
anonymizer_core_refactored_onnx.py |
4 770 | Version active |
Un développeur pourrait importer le mauvais fichier par erreur.
3.3 ~2 000 lignes de code mort
| Fichier | Lignes | Statut |
|---|---|---|
pseudonymisation_pipeline_gui_v3.py |
439 | GUI V3 abandonnée |
Pseudonymisation_Gui_Models_V4.py |
390 | GUI V4 abandonnée |
pseudonymisation_pipeline_robuste.py |
627 | RobustEngine non utilisé dans le pipeline principal |
Pseudonymisation_Pipeline_Robuste_Patch.py |
167 | Patch probablement obsolète |
anonymizer_core_refactored.py |
388 | Core incomplet |
3.4 _search_whole_word — complexité N²
page.get_text("words") est appelé une fois par token à chercher dans PyMuPDF :
for w in page.get_text("words"):
wt = w[4].strip(".,;:!?()[]{}\"'«»-–—/\\")
if wt.lower() == p_lower:
rects.append(fitz.Rect(...))
Pour 500 noms × 30 pages = 15 000 appels à get_text("words").
3.5 Injection via regex utilisateur
Les regex_overrides du YAML ne sont pas validés. Un pattern comme (.*) avec DOTALL pourrait capturer tout le document. Aucune sandboxing n'est appliquée.
3.6 Données sensibles en mémoire
Le fichier garde toutes les PII en mémoire (anon.audit) avec les valeurs originales non masquées. Pas de del ou de nettoyage explicite après usage. En cas de crash ou de dump mémoire, les données non anonymisées sont exposées.
3.7 Données de test exposées
Les répertoires test_*/ et corpus_validation/ contiennent des fichiers .audit.jsonl et .pseudonymise.txt qui sont des sorties réelles d'anonymisation (potentiellement avec des données sensibles résiduelles). Ils ne devraient pas être versionnés.
4. Appréciations positives
4.1 Documentation riche et structurante
cadrage-projet-anonymisation.md— document de cadrage complet avec priorités, gates de release, gouvernanceAIPD-anonymisation.md— analyse d'impact sur la protection des donnéesconformite-rgpd-ia-act.md— conformité RGPD et IA Actannotation_guide.md— guide d'annotationprotocole-validation-humaine.md— protocole de validation humainespec-regles-administration.md— spécifications des règles d'administration
4.2 Corpus de test solide
- 27 documents réels annotés manuellement dans
tests/ground_truth/ - 4 couches de tests : unitaires, regression synthétique, corpus réel, validation humaine
- 13 tests unitaires pytest dans
tests/unit/ - Score de référence : 97.0/100 [Grade A]
- Baseline enregistrée : 0 fuite audit, 0 fuite regex, 0 fuite INSEE haute
4.3 Architecture de configuration saine
- Séparation
dictionnaires.default.yml/dictionnaires.yml(overlay runtime) - 5 profils utilisateur dans
profiles.default.yml(standard_local, chuxx_strict, partage_recherche, dossier_audit, demo) - Règles admin avec cycle de vie (draft/candidate/active) dans
admin_rules.default.yml - Schéma JSON de validation dans
schemas/admin_rules.schema.json
4.4 Pipeline d'anonymisation bien conçu
- 5 passes d'extraction avec fallback (pdfplumber → pdfminer → PyMuPDF → docTR OCR → tesseract)
- Propagation globale des PII sur toutes les pages
- Rescan de sécurité post-anonymisation
- Gazetteers Aho-Corasick pour FINESS, villes, noms de famille
- NER multi-modèles : EDS-Pseudo (F1=0.97), CamemBERT-bio ONNX, GLiNER, VLM Ollama
4.5 Évaluation structurée
- 5 axes : LEAK_AUDIT, LEAK_REGEX, LEAK_INSEE, FP_DENSITY, FP_MEDICAL
- Scoring pondéré avec notation A+ → F
- Comparaison automatique avec baseline
- Export JSON des résultats
4.6 Absence de dépendances circulaires
Le graphe d'import est propre et acyclique. Les managers (eds_pseudo_manager, gliner_manager, camembert_ner_manager, vlm_manager, ner_manager_onnx) sont des feuilles du graphe (n'importent rien de local).
4.7 Build Windows mature
- Signature Authenticode optionnelle
- Inno Setup pour l'installateur
- PyInstaller (onefile/onedir) + Nuitka
- Workflows GitHub Actions pour le build automatique
5. Recommandations priorisées
Priorité 1 — Sécurité (à faire immédiatement)
| # | Action | Effort | Impact |
|---|---|---|---|
| 1.1 | Remplacer except Exception: pass par except Exception as e: log.warning(...) sur les chemins critiques (rédaction PDF, rescan) |
2h | 🔴 Élimine le risque de fuite silencieuse |
| 1.2 | Corriger le chemin absolu dans .spec (utiliser Path(__file__).parent) |
15min | 🔴 Build reproductible |
| 1.3 | Ajouter un mécanisme de wipe des PII en mémoire après la rédaction PDF (del anon.audit) |
30min | 🟡 Conformité RGPD — dump mémoire |
Priorité 2 — Nettoyage du code mort
| # | Action | Effort | Impact |
|---|---|---|---|
| 2.1 | Supprimer ou archiver les 3 GUI mortes (V3, V4, Robuste) | 30min | 🟡 Réduit la confusion |
| 2.2 | Supprimer anonymizer_core_refactored.py ou le renommer anonymizer_core_refactored_legacy.py |
15min | 🟡 Évite l'import accidentel |
| 2.3 | Déplacer les test_*/ de la racine vers tests/data/ |
30min | 🟡 Repository propre |
| 2.4 | Supprimer ano.zip, *.log de la racine |
15min | 🟡 Hygiène |
| 2.5 | Nettoyer les répertoires vides (test_doctr_fix/) |
10min | 🟡 Hygiène |
Priorité 3 — Performance
| # | Action | Effort | Impact |
|---|---|---|---|
| 3.1 | Compiler les regex de _mask_line_by_content au niveau module (une seule fois) |
1h | 🟢 3-5x plus rapide sur gros documents |
| 3.2 | Factoriser _search_whole_word pour appeler get_text("words") une seule fois par page |
2h | 🟢 Réduction significative du temps de redaction |
| 3.3 | Extraire _collect_rects_for_hits() commune à vector/raster |
3h | 🟡 Réduit la duplication (~400 lignes → ~250) |
Priorité 4 — Qualité et maintenance
| # | Action | Effort | Impact |
|---|---|---|---|
| 4.1 | Ajouter pytest.ini ou pyproject.toml avec config pytest |
30min | 🟡 Tests exécutables proprement |
| 4.2 | Ajouter un workflow GitHub Actions pour les tests (pas juste le build) | 2h | 🟡 Non-régression automatique |
| 4.3 | Ajouter ruff ou flake8 dans la CI |
1h | 🟡 Qualité syntaxique |
| 4.4 | Regrouper les ~40 magic numbers dans un bloc de constantes | 2h | 🟡 Configurable sans lire le code |
| 4.5 | Unifier le nommage (tout en snake_case) |
4h | 🟡 Cohérence du projet |
| 4.6 | Factoriser les 3 fonctions Aho-Corasick en une classe générique | 3h | 🟡 ~150 lignes de duplication éliminées |
Priorité 5 — Alignement Linux/Windows
| # | Action | Effort | Impact |
|---|---|---|---|
| 5.1 | Faire pointer install.sh vers launcher.py au lieu de la GUI directement |
30min | 🟡 Expérience Linux identique à Windows |
| 5.2 | Tester le pipeline complet sur Linux (pas juste Windows) | 4h | 🟡 Portabilité |
6. Métriques
| Métrique | Valeur |
|---|---|
| Lignes de code Python (hors venv) | ~14 270 |
| Fichiers Python à la racine | 33 |
| Fichiers Python morts estimés | ~6 (~2 000 lignes) |
| Tests unitaires | 13 fichiers |
| Documents ground truth | 27 |
| Score qualité baseline | 97.0/100 [Grade A] |
except Exception: pass dans le core |
~20 |
| Magic numbers dans le core | ~25 |
| Versions de GUI coexistantes | 4 (1 active) |
| Dépendances circulaires | 0 |
Fichiers tools/ (scripts divers) |
~42 |
| Workflows GitHub Actions | 2 (build uniquement) |
7. Rapport détaillé par domaine
7.1 Core (anonymizer_core_refactored_onnx.py)
Fonctions trop longues :
| Fonction | Lignes estimées | Complexité |
|---|---|---|
process_pdf |
~200 | Très élevée : orchestration, regex, NER, rescan, nettoyage, whitelist, PDF |
anonymise_document_regex |
~180 | Très élevée : 10+ phases, logique NER-first, noms, tables |
_extract_trackare_identity |
~250 | Extrêmement élevée : 20+ patterns regex, nested functions |
_mask_ville_gazetteers |
~150 | Élevée : Aho-Corasick + énumérations + contexte géo + point fixe |
redact_pdf_raster |
~170 | Élevée : search, OCR, images, barcode, parallélisation |
Code dupliqué :
redact_pdf_vectoretredact_pdf_raster— structure quasi-identique (~200 lignes chacune), même patternby_page, même déduplication, même fallback_search_whole_word- Regex multiline répétées (phase 0a à 0h-bis) — 8+ blocs identiques de pattern scanning
_mask_finess_establishments,_mask_finess_addresses,_mask_ville_gazetteers— 3 fonctions Aho-Corasick avec la même structure
Types et annotations : Aucune fonction n'a de type hints sur les paramètres ou le retour, à l'exception des dataclasses. Le type cfg: Dict[str, Any] est utilisé partout — un TypedDict ou dataclass rendrait le code auto-documenté.
Imports inline : Des import re as _re, import numpy as np, from pyzbar.pyzbar import decode, from PIL import ImageFont sont exécutés à l'intérieur de fonctions, parfois dans des boucles.
7.2 Architecture
Graphe de dépendances :
launcher.py (point d'entrée Windows)
|
+-> anonymizer_core_refactored_onnx.py (CORE PRINCIPAL, 4 770 lignes)
| +-> config_defaults.py
| +-> admin_rules.py -> config_defaults.py
| +-> detectors/hospital_filter.py
| +-> ner_manager_onnx.py
| +-> camembert_ner_manager.py
| +-> eds_pseudo_manager.py
| +-> gliner_manager.py
| +-> vlm_manager.py
|
+-> Pseudonymisation_Gui_V5.py (GUI ACTIVE, 1 804 lignes)
+-> anonymizer_core_refactored_onnx.py (déjà chargé)
+-> ner_manager_onnx.py
+-> eds_pseudo_manager.py
+-> vlm_manager.py
+-> config_defaults.py
server.py (API FastAPI, point d'entrée microservice)
+-> anonymizer_core_refactored_onnx.py
+-> config_defaults.py
+-> tous les managers (try/except import)
Incohérences de nommage :
| Convention | Fichiers concernés |
|---|---|
snake_case.py |
Majorité : launcher.py, server.py, config_defaults.py, etc. |
PascalCase.py |
Pseudonymisation_Gui_V5.py, Pseudonymisation_Gui_Models_V4.py, Pseudonymisation_Pipeline_Robuste_Patch.py |
Le répertoire Pseudonymiseur/ (majuscule, nom français) coexiste avec ano/ (abréviation anglaise).
7.3 Tests
Points forts :
- Architecture de test à 4 couches bien documentée
- 27 documents réels annotés manuellement — corpus sérieux
- Manifest de regression synthétique avec critères
must_contain/must_not_contain - Baseline de qualité enregistrée : score global 97.0/100
Points faibles :
- Pas de configuration pytest formelle (pas de
pytest.inioupyproject.toml) - 3 fichiers de test flottent à la racine du projet au lieu d'être dans
tests/ - 42 fichiers dans
tools/mêlant tests, analyses et utilitaires — pas de séparation claire - Pas de mesure de couverture de code (
pytest-covnon configuré) - Pas de workflow CI pour les tests automatiques
7.4 Configuration
Architecture saine :
| Fichier | Rôle |
|---|---|
dictionnaires.default.yml |
Template versionné — whitelist, blacklist, regex_overrides, phrases préservées |
dictionnaires.yml |
Surcharge locale — actuellement vide ({}) |
profiles.default.yml |
5 profils : standard_local, chuxx_strict, partage_recherche, dossier_audit, demo |
profiles.yml |
Surcharge locale — 2 profils créés depuis la GUI |
admin_rules.default.yml |
Règles administrables avec cycle de vie (draft/candidate/active) |
admin_rules.yml |
Surcharge locale — vide (rules: []) |
Point d'attention : admin_rules n'est pas encore branché au pipeline principal — le fichier est un "contrat cible" pour un futur moteur.
7.5 CI/CD
Workflows existants :
| Workflow | Déclencheur | Environnement | Méthode |
|---|---|---|---|
build-windows.yml |
Tag v* ou manuel |
windows-latest, Python 3.12 |
Nuitka (standalone folder) |
build-portable.yml |
Tag v* ou manuel |
windows-latest, Python 3.12 |
Python embarqué embeddable zip |
Ce qui manque :
- Pas de workflow de test (pas de
pytestsur PR/push) - Pas de linting (pas de ruff, flake8, mypy)
- Pas de vérification de qualité (pas de
evaluate_quality.pydans la CI) - Pas de build Linux (uniquement Windows)
- Pas de vérification de sécurité (dependabot, Trivy, etc.)
7.6 Régression
Suite de regression :
- 29 fichiers baseline dans
regression_tests/baseline/ - Script
check_regression.pyavec 7 types de fuites connues et 5 types de faux positifs identifiés
Problème : Le script utilise un chemin absolu en dur vers les sorties :
/home/dom/Téléchargements/II-1 Ctrl_T2A_2025_CHUXX_DocJustificatifs (1)/anonymise_audit_30
Ce chemin n'est pas portable.
7.7 detectors/
Un seul fichier : detectors/hospital_filter.py
La méthode should_filter() retourne **toujours False``. Les coordonnées hospitalières ont été validées comme devant être masquées car elles identifient indirectement le patient (contrôle humain du 2026-03-12). Le filtre est donc essentiellement inactivé, sauf pour les épisodes Trackare via filter_detections()`.
Ce répertoire semble être un emplacement prévu pour de futurs detecteurs mais qui n'a pas été étoffé.
8. Résumé exécutif
Le projet dispose d'une bonne base conceptuelle : pipeline d'anonymisation bien pensé, documentation de qualité professionnelle, corpus de test sérieux avec score de 97/100. L'architecture de configuration (default/overlay) et le graphe de dépendances (acyclique) sont propres.
Cependant, l'évolution organique a accumulé :
- Des risques de sécurité silencieux (
except: passsur les chemins critiques de rédaction PDF) - ~2 000 lignes de code mort (GUI V3/V4, core incomplet, patch obsolète)
- Des incohérences de nommage et de build (PyInstaller vs Nuitka, chemins absolus)
- Des problèmes de performance évitables (regex recompilées, appels N² à PyMuPDF)
Les 3 actions prioritaires qui apportent le plus de valeur immédiatement :
- Sécuriser les chemins critiques : logguer les exceptions au lieu de les ignorer sur la rédaction PDF et le rescan
- Nettoyer le code mort : supprimer les GUI abandonnées et le core incomplet
- Rendre le build reproductible : corriger le chemin absolu dans le
.spec
Ces 3 actions combinées représentent moins de 3h de travail et éliminent les risques les plus sérieux.