# 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](#1-synthèse-globale)
2. [Risques critiques](#2-risques-critiques)
3. [Risques importants](#3-risques-importants)
4. [Appréciations positives](#4-appréciations-positives)
5. [Recommandations prioritaires](#5-recommandations-priorisées)
6. [Métriques](#6-métriques)
7. [Rapport détaillé par domaine](#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_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`

```python
# 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** :

```python
_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 :

```python
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.
