# 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.