Files
Domi31tls 0067738df6 spec: Architecture complète avec VLM (5 couches détection)
- Ajout documentation VLM (Ollama qwen2.5vl:7b)
- Pipeline complet: Regex → VLM → EDS-Pseudo → CamemBERT → Contextuel
- Nouvelles exigences REQ-013/REQ-014 pour optimisation VLM
- Tâches Phase 2.5: amélioration prompt, validation croisée, perf
- Document ARCHITECTURE_REELLE.md avec détails complets
- Matériel: AMD Ryzen 9 9950X, 128GB RAM, RTX 5070 12GB
- Objectifs: Rappel ≥99.5%, Précision ≥97%, F1 ≥0.98
2026-03-02 09:52:49 +01:00

474 lines
11 KiB
Markdown

# Architecture Réelle du Système d'Anonymisation
## Vue d'Ensemble
Le système d'anonymisation combine **5 couches de détection** pour garantir une couverture maximale des PII dans les documents médicaux PDF.
## 1. Extraction de Texte (5 passes)
### Méthodes d'extraction (ordre de priorité)
1. **pdfplumber** : Extraction textuelle native (PDF textuels)
2. **pdfminer** : Extraction alternative avec LAParams
3. **PyMuPDF** : Fallback si artefacts `(cid:xx)` détectés
4. **docTR OCR** : OCR deep learning pour PDF scannés (parallèle avec tesseract)
5. **tesseract OCR** : OCR complémentaire
### OCR Word Map
Pour les PDF scannés, génération d'une carte de mots avec coordonnées normalisées (0→1) :
```python
OcrWordMap = Dict[int, List[Tuple[str, float, float, float, float]]]
# {page_idx: [(word_text, x0_norm, y0_norm, x1_norm, y1_norm), ...]}
```
Utilisée pour :
- Matching flou des identifiants numériques manuscrits (VLM)
- Redaction raster précise
---
## 2. Détection PII (5 couches)
### 2.1 Regex (Couche 1)
**Fichier** : `anonymizer_core_refactored_onnx.py`
**PII détectés** :
- EMAIL, TEL (formats fragmentés), IBAN, NIR (avec validation clé modulo 97)
- IPP, FINESS, RPPS, OGC
- Dates (multiples formats), dates de naissance
- Adresses postales, codes postaux, BP
- Âges, numéros de dossier, épisodes
- Établissements de santé, services hospitaliers
**Patterns contextuels** :
- `Dr. NOM`, `Patient: NOM`, `Rédigé par NOM`
- Listes virgulées : `Dr. X, Y, DUPONT`
- Champs structurés Trackare
**Configuration** : `config/dictionnaires.yml`
- Listes blanches (sections_titres, noms_maj_excepts)
- Force-mask (termes/regex à masquer systématiquement)
- Regex overrides personnalisées
### 2.2 VLM - Vision Language Model (Couche 2)
**Fichier** : `vlm_manager.py`
**Modèle** : Ollama avec qwen2.5vl:7b (ou qwen3-vl:8b)
**Fonctionnement** :
1. Conversion de chaque page PDF en image (150 DPI)
2. Redimensionnement si > 2048px (côté le plus long)
3. Envoi à Ollama avec prompt structuré
4. Parsing de la réponse JSON (avec réparation si tronquée)
**20+ catégories détectées** :
- Identité : NOM, PRENOM, DATE_NAISSANCE, NIR, AGE
- Contact : ADRESSE, TELEPHONE, EMAIL, CODE_POSTAL, VILLE
- Identifiants médicaux : IPP, NDA, RPPS, NUMERO_PATIENT, NUMERO_LOT, NUMERO_ORDONNANCE, NUMERO_SEJOUR
- Contexte : ETABLISSEMENT, SERVICE, DATE
**Gestion des pages manuscrites** :
- Si < 100 mots OCR détectés → masquage total de la page
- Évite les hallucinations du VLM sur pages complexes
**Matching flou** :
- Pour identifiants numériques manuscrits
- Compare séquences de chiffres (ratio ≥ 0.7)
- Utilise l'OCR word map pour localisation
**Configuration** :
```python
@dataclass
class VlmConfig:
base_url: str = "http://localhost:11434"
model: str = "qwen2.5vl:7b"
timeout: int = 180
max_image_size: int = 2048
temperature: float = 0.1
num_predict: int = 8192
min_confidence: float = 0.5
```
**Prompt système** :
```
Tu identifies les données personnelles et identifiants traçables dans les documents
médicaux français. Réponds uniquement en JSON.
```
**Dégradation gracieuse** : Si Ollama indisponible, le pipeline continue sans VLM.
### 2.3 NER - Named Entity Recognition (Couche 3)
#### Option A : EDS-Pseudo (recommandé)
**Fichier** : `eds_pseudo_manager.py`
**Modèle** : AP-HP/eds-pseudo-public via edsnlp
**Performance** : F1=0.97 sur données cliniques AP-HP (11M documents)
**13 labels détectés** :
- NOM, PRENOM, MAIL, TEL, SECU (NIR)
- ADRESSE, ZIP, VILLE, HOPITAL
- DATE, DATE_NAISSANCE, IPP, NDA
**Mapping vers PLACEHOLDERS** :
```python
EDS_LABEL_MAP = {
"NOM": "NOM",
"PRENOM": "NOM",
"MAIL": "EMAIL",
"TEL": "TEL",
"SECU": "NIR",
# ...
}
```
#### Option B : CamemBERT-NER (fallback)
**Fichier** : `ner_manager_onnx.py`
**Modèles** :
- `cmarkea/distilcamembert-base-ner` (rapide, quantifié)
- `Jean-Baptiste/camembert-ner` (robuste)
**Runtime** : ONNX avec optimum.onnxruntime
**Tags supportés** : PER, LOC, ORG, DATE
**Seuils de confiance** :
```python
@dataclass
class NerThresholds:
per: float = 0.90
org: float = 0.90
loc: float = 0.90
date: float = 0.85
```
### 2.4 Extraction Trackare (Couche 4)
**Fonction** : `_extract_trackare_identity()`
**Champs structurés extraits** :
- Nom de naissance, Nom et Prénom
- Lieu de naissance, Ville de résidence
- Contacts (Conjoint, Parent, Époux, etc.)
- Personnel médical (Rédigé par, Aide:, IDE:, etc.)
- Pattern multi-lignes : `Prenom\nNOM`
**Regex spécialisées** :
- `RE_EXTRACT_PATIENT`, `RE_EXTRACT_NOM_NAISSANCE`
- `RE_EXTRACT_CONTACT`, `RE_EXTRACT_STAFF_ROLE`
- `RE_EXTRACT_DR_DEST`, `RE_EXTRACT_PR`
### 2.5 Détection Contextuelle (Couche 5)
**Patterns** :
- Noms après titres : `Dr. DUPONT`, `Pr. MARTIN`
- Noms en MAJUSCULES (hors stopwords médicaux)
- Listes virgulées : `le Dr X, Y, LAZARO`
**Stopwords médicaux** : ~4000 termes
- Médicaments (edsnlp drugs.json)
- Termes médicaux, anatomie, pathologies
- Abréviations, services hospitaliers
- Mots courants français
---
## 3. Consolidation Globale
**Fonction** : `_apply_global_pii_tokens()`
### Propagation des PII
Chaque PII détecté est propagé sur toutes les pages du document :
**Types propagés globalement** :
- `NOM_GLOBAL` : tous les tokens de noms
- `TEL_GLOBAL`, `EMAIL_GLOBAL`, `ADRESSE_GLOBAL`
- `CODE_POSTAL_GLOBAL`, `VILLE_GLOBAL`, `ETAB_GLOBAL`
- `VLM_SERVICE`, `VLM_ETAB`, `DATE_NAISSANCE`
### Noms compagnons
Détection de mots en MAJUSCULES adjacents à un nom connu :
```python
# Si "DUPONT" est détecté, et "JEAN-PIERRE DUPONT" apparaît
# → "JEAN-PIERRE" est ajouté comme nom compagnon
```
### Noms composés
Traitement en bloc : `JEAN-PIERRE`, `CAZELLES-BOUDIER`
### Rescan sélectif
Rescan des PII critiques ayant pu échapper au premier passage :
- EMAIL, TEL, IBAN, NIR
- Téléphones fragmentés sur plusieurs lignes
- Codes postaux orphelins
---
## 4. Redaction PDF
### 4.1 Vector Redaction (PDF textuels)
**Méthode** : `page.search_for()` (PyMuPDF)
1. Pour chaque PII dans l'audit
2. Recherche du texte exact dans la page
3. Si non trouvé et PII avec espaces → recherche mot par mot
4. Génération de rectangles de redaction
5. Application des rectangles (texte remplacé par noir)
### 4.2 Raster Redaction (PDF scannés)
**Méthode** : Conversion en image + redaction pixel
1. Conversion de chaque page en pixmap (DPI configurable)
2. Matching des PII via OCR word map
3. **Matching flou pour VLM** : identifiants numériques manuscrits
4. Dessin de rectangles noirs sur les zones PII
5. Reconstruction du PDF depuis les images
**Matching flou VLM** :
```python
def _search_ocr_words_fuzzy_digits(ocr_words, token, page_rect, min_ratio=0.7):
"""Compare les séquences de chiffres entre le token VLM et les mots OCR.
Accepte une correspondance si ≥ min_ratio des chiffres matchent."""
```
Appliqué aux catégories :
- `VLM_NUM_PATIENT`, `VLM_NUM_LOT`, `VLM_NUM_ORD`
- `VLM_NDA`, `VLM_NIR`, `VLM_IPP`, `VLM_RPPS`
---
## 5. Audit Trail
**Format** : `.audit.jsonl` (JSON Lines)
**Structure** :
```python
@dataclass
class PiiHit:
page: int
kind: str # Type de PII (EMAIL, NOM, VLM_TEL, etc.)
original: str # Texte original détecté
placeholder: str # Placeholder de remplacement
bbox_hint: Optional[Tuple[float, float, float, float]] # Coordonnées (optionnel)
```
**Traçabilité** :
- Chaque PII détecté est enregistré avec sa méthode de détection
- Permet l'analyse post-traitement
- Utilisé pour la validation post-anonymisation
---
## 6. Matériel et Performance
### Configuration actuelle
- **CPU** : AMD Ryzen 9 9950X (16 cœurs / 32 threads)
- **RAM** : 128 GB
- **GPU** : NVIDIA RTX 5070 (12 GB VRAM)
- **CUDA** : PyTorch 2.10.0 avec support CUDA
### Temps de traitement
**PDF textuels** :
- Extraction : < 1s
- Détection (Regex + NER GPU) : 2-5s
- Redaction : 1-2s
- **Total** : < 10s par PDF
**PDF scannés** :
- Extraction + OCR : 5-15s
- VLM (Ollama) : 10-30s par page (dépend du modèle)
- Détection (Regex + NER GPU) : 2-5s
- Redaction raster : 3-10s
- **Total** : 20-60s par PDF (selon nombre de pages)
### Optimisations possibles
1. **VLM** : Vérifier support GPU Ollama (CUDA)
2. **NER** : Batch processing optimisé (12 GB VRAM)
3. **Parallélisation** : 8-12 workers sur 16 cœurs
4. **Cache** : Résultats VLM par hash d'image
---
## 7. Points d'Attention
### Hallucinations VLM
Le VLM peut détecter des PII inexistants, surtout sur :
- Pages manuscrites complexes
- Documents mal orientés
- Tableaux denses
**Mitigation actuelle** :
- Masquage total si < 100 mots OCR
- Seuil de confiance (0.5)
**Amélioration proposée** :
- Validation croisée VLM ↔ NER
- Prompt amélioré avec exemples négatifs
- Rejet des détections non confirmées
### Faux Positifs NER
EDS-Pseudo peut détecter comme NOM :
- Médicaments (ex: "Eliquis", "Trulicity")
- Termes médicaux (ex: "Diabétique", "Cutanée")
- Mots courants (ex: "Toilette", "Repas")
**Mitigation** : Stopwords médicaux (~4000 termes)
### Performance VLM
Ollama en local peut être lent (10-30s par page).
**Solutions** :
- Vérifier support GPU
- Réduire `max_image_size` (trade-off qualité/vitesse)
- Cache des résultats
- Traitement parallèle (attention : charge GPU)
---
## 8. Dépendances Complètes
```
# Core
python>=3.12
pyyaml
# PDF
pymupdf
pdfplumber
pdfminer.six
pillow
# OCR
doctr[torch]
pytesseract
# NER
edsnlp[ml]>=0.12.0 # EDS-Pseudo
transformers
optimum
onnxruntime # ou onnxruntime-gpu
sentencepiece
# VLM
# Aucune dépendance Python (utilise urllib)
# Nécessite Ollama installé localement : https://ollama.ai/
# Tests & Qualité (nouveaux)
pytest
pytest-cov
pydantic
structlog
jinja2
matplotlib
```
---
## 9. Configuration Ollama
### Installation
```bash
# Linux
curl -fsSL https://ollama.ai/install.sh | sh
# macOS
brew install ollama
# Windows
# Télécharger depuis https://ollama.ai/download
```
### Téléchargement du modèle
```bash
ollama pull qwen2.5vl:7b
# ou
ollama pull qwen3-vl:8b
```
### Vérification
```bash
# Lister les modèles disponibles
ollama list
# Tester le modèle
ollama run qwen2.5vl:7b
```
### Configuration GPU (optionnel)
Ollama détecte automatiquement le GPU CUDA si disponible.
Vérifier dans les logs :
```bash
ollama serve
# Chercher : "GPU detected: NVIDIA GeForce RTX 5070"
```
---
## 10. Workflow Complet
```
1. Chargement PDF
2. Extraction texte (5 passes)
3. Détection Regex
4. VLM (si PDF scanné)
5. NER (EDS-Pseudo ou CamemBERT)
6. Extraction Trackare
7. Détection contextuelle
8. Consolidation globale
9. Rescan sélectif
10. Redaction PDF (vector ou raster)
11. Génération audit.jsonl
12. Validation post-anonymisation (à implémenter)
13. Certificat de conformité (à implémenter)
```
---
## Conclusion
Le système actuel est **très sophistiqué** avec 5 couches de détection complémentaires. Les principaux axes d'amélioration sont :
1. **Métriques de qualité** : Créer un dataset annoté et mesurer Précision/Rappel/F1
2. **Validation VLM** : Réduire les hallucinations via validation croisée
3. **Optimisation GPU** : Accélérer NER et vérifier support GPU Ollama
4. **Tests de régression** : Suite automatique pour éviter les régressions
5. **Validation post-anonymisation** : Scanner de fuite automatique
La spécification mise à jour reflète maintenant cette architecture réelle.