#!/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())