Files
anonymisation/docs/coordination/audits/2026-05-28_qwen_audit-complet.md
Domi31tls d21e01a2c2 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

18 KiB
Raw Blame History

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

  1. Synthèse globale
  2. Risques critiques
  3. Risques importants
  4. Appréciations positives
  5. Recommandations prioritaires
  6. Métriques
  7. 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_vectorapply_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, gouvernance
  • AIPD-anonymisation.md — analyse d'impact sur la protection des données
  • conformite-rgpd-ia-act.md — conformité RGPD et IA Act
  • annotation_guide.md — guide d'annotation
  • protocole-validation-humaine.md — protocole de validation humaine
  • spec-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_vector et redact_pdf_raster — structure quasi-identique (~200 lignes chacune), même pattern by_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.ini ou pyproject.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-cov non 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 pytest sur PR/push)
  • Pas de linting (pas de ruff, flake8, mypy)
  • Pas de vérification de qualité (pas de evaluate_quality.py dans 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.py avec 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: pass sur 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 :

  1. Sécuriser les chemins critiques : logguer les exceptions au lieu de les ignorer sur la rédaction PDF et le rescan
  2. Nettoyer le code mort : supprimer les GUI abandonnées et le core incomplet
  3. 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.