Files
anonymisation/tools/extract_medical_stopwords.py

201 lines
7.7 KiB
Python
Executable File

#!/usr/bin/env python3
"""
Extrait les termes médicaux des documents pour enrichir les stopwords.
Analyse les détections NOM_EXTRACTED pour identifier les faux positifs
récurrents (termes médicaux, anatomiques, etc.) à ajouter aux stopwords.
"""
import sys
import json
from pathlib import Path
from collections import Counter
import re
def extract_medical_terms():
"""Extrait les termes médicaux des détections."""
baseline_dir = Path("tests/ground_truth/pdfs/baseline_anonymized")
audit_files = sorted(baseline_dir.glob("*.audit.jsonl"))
if not audit_files:
print(f"✗ Aucun fichier audit trouvé dans {baseline_dir}")
return 1
print("="*80)
print("EXTRACTION DES TERMES MÉDICAUX")
print("="*80)
print(f"\n📁 Analyse de {len(audit_files)} fichiers audit...")
# Collecter tous les NOM_EXTRACTED
nom_extracted = []
for audit_file in audit_files:
with open(audit_file, 'r', encoding='utf-8') as f:
for line in f:
if line.strip():
det = json.loads(line)
if det.get('kind') == 'NOM_EXTRACTED':
text = det.get('original', '').strip()
if text:
nom_extracted.append(text.lower())
print(f"\n📊 {len(nom_extracted)} détections NOM_EXTRACTED trouvées")
# Compter les occurrences
counter = Counter(nom_extracted)
print(f"📊 {len(counter)} termes uniques")
# Identifier les termes médicaux potentiels
# Critères: termes fréquents (>= 3 occurrences) et patterns médicaux
medical_patterns = [
r'^(dr|docteur|professeur|prof)$',
r'^(service|unite|unité|departement|département)$',
r'^(hopital|hôpital|clinique|centre|chu|ch)$',
r'^(medecin|médecin|infirmier|infirmière|aide.soignant)$',
r'^(consultation|hospitalisation|urgence|urgences)$',
r'^(chirurgie|cardiologie|pneumologie|neurologie|oncologie)$',
r'^(radiologie|imagerie|scanner|irm|echographie|échographie)$',
r'^(biologie|laboratoire|analyse|prelevement|prélèvement)$',
r'^(traitement|medicament|médicament|ordonnance|prescription)$',
r'^(diagnostic|pathologie|maladie|syndrome|infection)$',
r'^(patient|malade|sujet|cas)$',
r'^(examen|bilan|controle|contrôle|suivi)$',
r'^(resultat|résultat|valeur|taux|dosage)$',
r'^(antecedent|antécédent|allergie|risque|facteur)$',
r'^(gauche|droit|droite|superieur|supérieur|inferieur|inférieur)$',
r'^(anterieur|antérieur|posterieur|postérieur|lateral|latéral)$',
r'^(proximal|distal|medial|médial)$',
r'(ologie|ique|aire|ose|ite|ome)$', # Suffixes médicaux
]
medical_terms = set()
frequent_terms = []
for term, count in counter.most_common():
# Termes fréquents (>= 3 occurrences)
if count >= 3:
frequent_terms.append((term, count))
# Vérifier si c'est un terme médical
is_medical = any(re.search(pattern, term, re.IGNORECASE) for pattern in medical_patterns)
if is_medical:
medical_terms.add(term)
print(f"\n📊 {len(frequent_terms)} termes fréquents (≥3 occurrences)")
print(f"📊 {len(medical_terms)} termes médicaux identifiés automatiquement")
# Afficher les top 50 termes fréquents
print(f"\n🔝 Top 50 termes les plus fréquents:")
for i, (term, count) in enumerate(frequent_terms[:50], 1):
is_medical = term in medical_terms
marker = "🏥" if is_medical else " "
print(f" {i:2d}. {marker} {term:30s} ({count:3d} occurrences)")
# Catégoriser les termes
categories = {
"Titres/Fonctions": [],
"Services/Départements": [],
"Établissements": [],
"Examens/Procédures": [],
"Anatomie": [],
"Pathologies": [],
"Médicaments/Traitements": [],
"Termes généraux": []
}
# Patterns par catégorie
category_patterns = {
"Titres/Fonctions": [r'(dr|docteur|prof|medecin|médecin|infirmier)'],
"Services/Départements": [r'(service|unite|unité|departement|département|ologie)'],
"Établissements": [r'(hopital|hôpital|clinique|centre|chu|ch)'],
"Examens/Procédures": [r'(examen|bilan|scanner|irm|echo|radio|analyse)'],
"Anatomie": [r'(gauche|droit|superieur|inferieur|anterieur|posterieur|lateral|proximal|distal)'],
"Pathologies": [r'(ite|ose|ome|pathologie|maladie|syndrome|infection)'],
"Médicaments/Traitements": [r'(traitement|medicament|médicament|ordonnance|prescription)'],
}
for term in medical_terms:
categorized = False
for category, patterns in category_patterns.items():
if any(re.search(p, term, re.IGNORECASE) for p in patterns):
categories[category].append(term)
categorized = True
break
if not categorized:
categories["Termes généraux"].append(term)
# Afficher par catégorie
print(f"\n" + "="*80)
print("TERMES MÉDICAUX PAR CATÉGORIE")
print("="*80)
for category, terms in categories.items():
if terms:
print(f"\n{category} ({len(terms)} termes):")
for term in sorted(terms)[:20]: # Limiter à 20 par catégorie
count = counter[term]
print(f" - {term} ({count} occ.)")
if len(terms) > 20:
print(f" ... et {len(terms) - 20} autres")
# Sauvegarder les résultats
output_dir = Path("tests/ground_truth/analysis")
output_dir.mkdir(exist_ok=True)
output_data = {
"extraction_date": "2026-03-02",
"total_detections": len(nom_extracted),
"unique_terms": len(counter),
"frequent_terms_count": len(frequent_terms),
"medical_terms_count": len(medical_terms),
"top_50_frequent": [
{"term": term, "count": count, "is_medical": term in medical_terms}
for term, count in frequent_terms[:50]
],
"medical_terms_by_category": {
category: sorted(terms)
for category, terms in categories.items()
if terms
},
"all_medical_terms": sorted(medical_terms)
}
output_file = output_dir / "medical_stopwords_candidates.json"
with open(output_file, 'w', encoding='utf-8') as f:
json.dump(output_data, f, indent=2, ensure_ascii=False)
print(f"\n📊 Résultats sauvegardés: {output_file}")
# Générer un fichier Python avec les nouveaux stopwords
stopwords_file = output_dir / "new_medical_stopwords.py"
with open(stopwords_file, 'w', encoding='utf-8') as f:
f.write("# Nouveaux stopwords médicaux extraits automatiquement\n")
f.write("# À ajouter à _MEDICAL_STOP_WORDS_SET dans anonymizer_core_refactored_onnx.py\n\n")
f.write("NEW_MEDICAL_STOPWORDS = {\n")
for term in sorted(medical_terms):
f.write(f' "{term}",\n')
f.write("}\n")
print(f"📊 Stopwords Python générés: {stopwords_file}")
print(f"\n" + "="*80)
print("RECOMMANDATIONS")
print("="*80)
print(f"""
1. Réviser manuellement les termes dans: {output_file}
2. Ajouter les termes validés à _MEDICAL_STOP_WORDS_SET
3. Re-exécuter l'anonymisation et l'évaluation
4. Vérifier la réduction des FP NOM_EXTRACTED
Gain estimé: Réduction de ~{len(medical_terms)} termes récurrents
Impact: Amélioration de la précision de plusieurs points
""")
return 0
if __name__ == "__main__":
sys.exit(extract_medical_terms())