Files
anonymisation/.kiro/specs/anonymization-quality-optimization/PHASE1_IMPLEMENTATION.md

8.1 KiB

Phase 1 - Implémentation des Corrections Critiques

Date: 2 mars 2026
Statut: COMPLÉTÉ


🎯 Objectif

Corriger les 3 problèmes critiques identifiés pour réduire les faux positifs de 34% (PII/doc 38 → 25).


Étape 1: Analyse des Dates (COMPLÉTÉ)

Résultats de l'Analyse

Problème identifié: 41 masques [DATE] dans les textes alors que RE_DATE est désactivé !

Cause racine: EDS-Pseudo détecte TOUTES les dates (consultations, examens, etc.) et les mappe vers "DATE".

Preuve:

# eds_pseudo_manager.py, ligne 35
EDS_LABEL_MAP: Dict[str, str] = {
    ...
    "DATE": "DATE",  # ← Problème ici !
    "DATE_NAISSANCE": "DATE_NAISSANCE",
    ...
}

Statistiques:

  • 7 dates de naissance détectées dans les audits
  • 10 masques DATE_NAISSANCE dans les textes (correct)
  • 41 masques [DATE] dans les textes (problème !)
  • Ratio: 5.9x plus de [DATE] que de DATE_NAISSANCE

Impact:

  • Perte de contexte temporel médical
  • Dates de consultation, d'examen, de traitement masquées
  • Lisibilité dégradée

Étape 2: Correction du Masquage des Dates (COMPLÉTÉ)

Solution

Désactiver le mapping "DATE" dans EDS-Pseudo pour ne garder que "DATE_NAISSANCE".

Implémentation

Fichier: eds_pseudo_manager.py

Modification:

# AVANT (ligne 35)
EDS_LABEL_MAP: Dict[str, str] = {
    ...
    "DATE": "DATE",  # ← Masque toutes les dates
    "DATE_NAISSANCE": "DATE_NAISSANCE",
    ...
}

# APRÈS
EDS_LABEL_MAP: Dict[str, str] = {
    ...
    # "DATE": "DATE",  # ← DÉSACTIVÉ: ne masquer que les dates de naissance
    "DATE_NAISSANCE": "DATE_NAISSANCE",
    ...
}

Résultat Attendu

  • [DATE]: 41 → 0 (-100%)
  • Lisibilité temporelle: Médiocre → Bonne

Statut: IMPLÉMENTÉ


Étape 3: Correction du Masquage des Médicaments (COMPLÉTÉ)

Problème

La fonction _load_edsnlp_drug_names() existe mais n'est PAS utilisée dans le pipeline !

Solution

Activer la whitelist médicaments dans le masquage NER.

Implémentation

Fichier: anonymizer_core_refactored_onnx.py

Étape 3.1: Charger la whitelist au démarrage

# Ligne ~100 (après les imports)
_MEDICATION_WHITELIST = _load_edsnlp_drug_names()
# Ajout de médicaments supplémentaires
_MEDICATION_WHITELIST.update({"idacio", "salazopyrine", "infliximab", ...})

Étape 3.2: Filtrer les détections NER

# Ligne ~1450 (dans _mask_with_eds_pseudo)
# CORRECTION 1.2: Filtrer les médicaments détectés comme NOM/PRENOM
if label in ("NOM", "PRENOM"):
    # Vérifier si c'est un médicament connu
    if w.lower() in _MEDICATION_WHITELIST:
        continue

Résultat Attendu

  • Médicaments masqués: 1+ → 0 (-100%)
  • Lisibilité thérapeutique: Médiocre → Bonne

Statut: IMPLÉMENTÉ


Étape 4: Correction du Sur-Masquage des Termes Médicaux (COMPLÉTÉ)

Problème

Les regex RE_SERVICE et RE_ETABLISSEMENT capturent des termes médicaux légitimes.

Exemples:

  • "Chef de service" → "Chef de [MASK]" (27x)
  • "Chef de Clinique" → "Chef de [ETABLISSEMENT]" (12x)

Solution

Créer une whitelist de termes médicaux structurels et modifier les regex.

Implémentation

Étape 4.1: Créer la whitelist

Fichier: config/medical_terms_whitelist.yml

# Whitelist des termes médicaux structurels à ne PAS masquer
medical_structural_terms:
  # Fonctions médicales
  - "Chef de service"
  - "Chef de Clinique"
  - "Chef de clinique"
  - "Ancien Chef de Clinique"
  - "Ancien Chef de clinique"
  - "Ancien Assistant"
  - "Praticien hospitalier"
  - "Praticien Hospitalier"
  - "Praticien hospitalier contractuel"
  - "Assistant spécialiste"
  - "Médecin coordonnateur"
  
  # Structures hospitalières (contexte)
  - "service de"
  - "unité de"
  - "pôle de"
  - "département de"

Étape 4.2: Charger la whitelist

Fichier: anonymizer_core_refactored_onnx.py

# Ligne ~104
def load_medical_whitelists():
    """Charge les whitelists médicales (termes structurels + médicaments)."""
    global _MEDICAL_STRUCTURAL_TERMS, _MEDICATION_WHITELIST
    
    # 1. Charger les termes médicaux structurels
    config_path = Path("config/medical_terms_whitelist.yml")
    if config_path.exists() and yaml:
        try:
            with open(config_path, 'r', encoding='utf-8') as f:
                data = yaml.safe_load(f)
                terms = data.get('medical_structural_terms', [])
                _MEDICAL_STRUCTURAL_TERMS = {t.lower() for t in terms}
                log.info(f"Whitelist termes médicaux chargée: {len(_MEDICAL_STRUCTURAL_TERMS)} termes")
        except Exception as e:
            log.warning(f"Erreur chargement whitelist médicale: {e}")
    
    # 2. Charger la whitelist des médicaments
    _MEDICATION_WHITELIST = _load_edsnlp_drug_names()
    # Ajouter médicaments manquants
    additional_meds = {
        "idacio", "salazopyrine", "infliximab", "apranax",
        "ketoprofene", "prevenar", "pneumovax", "bétadine"
    }
    _MEDICATION_WHITELIST.update(additional_meds)
    log.info(f"Whitelist médicaments chargée: {len(_MEDICATION_WHITELIST)} médicaments")

# Charger les whitelists au démarrage du module
load_medical_whitelists()

Étape 4.3: Filtrer avant masquage

Fichier: anonymizer_core_refactored_onnx.py

# Ligne ~920 (dans _mask_line_by_regex, avant RE_SERVICE)

# Services hospitaliers (service de Cardiologie, unité de soins palliatifs, etc.)
def _repl_service(m: re.Match) -> str:
    full_match = m.group(0)
    # Vérifier si c'est un terme structurel à préserver
    if full_match.lower() in _MEDICAL_STRUCTURAL_TERMS:
        return full_match
    # Vérifier le contexte avant (Chef de, Praticien, etc.)
    start_pos = m.start()
    context_before = line[max(0, start_pos-25):start_pos].lower()
    # Patterns à préserver
    preserve_patterns = ['chef de', 'praticien', 'ancien', 'assistant', 'médecin', 'interne']
    if any(pattern in context_before for pattern in preserve_patterns):
        return full_match
    audit.append(PiiHit(page_idx, "ETAB", full_match, PLACEHOLDERS["MASK"]))
    return PLACEHOLDERS["MASK"]
line = RE_SERVICE.sub(_repl_service, line)

Résultat Attendu

  • ETAB faux positifs: 26 → ~6 (-77%)
  • Lisibilité médicale: Médiocre → Bonne

Statut: IMPLÉMENTÉ


🧪 Étape 5: Tests et Validation

Test 1: Script de validation automatique

Fichier créé: tools/test_phase1_corrections.py

Ce script teste automatiquement les 3 corrections sur un échantillon de documents:

  1. Vérification que les termes médicaux structurels sont préservés
  2. Vérification que les médicaments sont préservés
  3. Vérification que [DATE] = 0 (seules les dates de naissance sont masquées)

Commande:

python3 tools/test_phase1_corrections.py

Test 2: Comparer avant/après

Métrique Avant Après (Attendu) Amélioration
PII/doc 38.0 ~25.0 -34%
[DATE] 41 0 -100%
Médicaments masqués 1+ 0 -100%
ETAB FP 26 ~6 -77%
Lisibilité Médiocre Bonne ++

Test 3: Vérifier les fuites

python3 tools/validate_anonymization.py

Vérifier:

  • 0 fuite de date de naissance
  • 0 fuite de CHCB
  • 0 fuite de NIR, IPP, etc.

📊 Résultat Final Attendu

Métriques

  • PII/doc: 38.0 → ~25.0 (-34%)
  • [DATE]: 41 → 0 (-100%)
  • Médicaments masqués: 1+ → 0 (-100%)
  • ETAB FP: 26 → ~6 (-77%)
  • Lisibilité: Médiocre → Bonne

Impact

  • Contexte temporel préservé (dates de consultation)
  • Information thérapeutique préservée (médicaments)
  • Contexte médical préservé (fonctions médicales)
  • Sécurité maintenue (0 fuite)

🚀 Prochaines Étapes

Après validation de la Phase 1:

  1. Phase 2: Enrichir stopwords médicaux + dédoplication (2-3 jours)
  2. Phase 3: Optimiser OCR + raffiner villes (3-5 jours)

Dernière mise à jour: 2 mars 2026
Auteur: Kiro AI Assistant
Statut: COMPLÉTÉ - Prêt pour validation