201 lines
7.7 KiB
Python
Executable File
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())
|