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

11 KiB

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) :

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 :

@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 :

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 :

@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 :

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

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 :

@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

# 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

ollama pull qwen2.5vl:7b
# ou
ollama pull qwen3-vl:8b

Vérification

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

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.