From c78f9f415dcc136366073eb36f02741205712be5 Mon Sep 17 00:00:00 2001 From: Domi31tls Date: Mon, 2 Mar 2026 10:14:56 +0100 Subject: [PATCH] =?UTF-8?q?demo:=20Ajout=20script=20de=20d=C3=A9monstratio?= =?UTF-8?q?n=20et=20correction=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Script demo_evaluation.py montrant tous les outils - Correction test flottant dans test_quality_evaluator.py - Installation pytest/pytest-cov - Tous les tests passent (16/16) --- demo_evaluation.py | 314 +++++++++++++++++++++++++++ tests/unit/test_quality_evaluator.py | 2 +- 2 files changed, 315 insertions(+), 1 deletion(-) create mode 100755 demo_evaluation.py diff --git a/demo_evaluation.py b/demo_evaluation.py new file mode 100755 index 0000000..9be4825 --- /dev/null +++ b/demo_evaluation.py @@ -0,0 +1,314 @@ +#!/usr/bin/env python3 +""" +Démonstration du système d'évaluation de la qualité d'anonymisation. + +Ce script montre comment utiliser les 3 composants principaux : +1. QualityEvaluator - Évaluation de la qualité +2. LeakScanner - Détection de fuites +3. Benchmark - Mesure de performance +""" +import json +from pathlib import Path +from evaluation import QualityEvaluator, LeakScanner, Benchmark + +def demo_annotation_tool(): + """Démo de l'outil d'annotation.""" + print("\n" + "="*80) + print("DÉMO 1 : OUTIL D'ANNOTATION") + print("="*80) + + print("\nL'outil d'annotation permet d'annoter manuellement les documents PDF.") + print("\nCommandes disponibles:") + print(" python tools/annotation_tool.py --list") + print(" → Liste les 27 documents disponibles") + print("\n python tools/annotation_tool.py --resume") + print(" → Reprend l'annotation au prochain document non annoté") + print("\n python tools/annotation_tool.py tests/ground_truth/pdfs/001_simple_unknown_BACTERIO_23018396.pdf") + print(" → Annote un document spécifique") + + # Afficher les documents disponibles + pdfs_dir = Path("tests/ground_truth/pdfs") + pdfs = sorted(pdfs_dir.glob("*.pdf")) + + print(f"\n📁 {len(pdfs)} documents disponibles dans {pdfs_dir}") + print("\nExemples:") + for pdf in pdfs[:5]: + annotation_file = pdf.parent / f"{pdf.stem}.annotations.json" + status = "✓ Annoté" if annotation_file.exists() else "○ À annoter" + print(f" {status} {pdf.name}") + print(f" ... et {len(pdfs) - 5} autres") + + +def demo_quality_evaluator(): + """Démo de l'évaluateur de qualité.""" + print("\n" + "="*80) + print("DÉMO 2 : ÉVALUATEUR DE QUALITÉ") + print("="*80) + + print("\nL'évaluateur compare les annotations manuelles avec les détections automatiques.") + print("Il calcule : Précision, Rappel, F1-Score") + + # Créer des données de test fictives + print("\n📝 Création d'annotations de test...") + + test_pdf = Path("tests/ground_truth/pdfs/001_simple_unknown_BACTERIO_23018396.pdf") + test_annotation = test_pdf.parent / f"{test_pdf.stem}.annotations.json" + test_audit = test_pdf.parent / f"{test_pdf.stem}.audit.jsonl" + + # Créer une annotation fictive + annotation_data = { + "pdf_path": str(test_pdf), + "metadata": { + "annotator": "demo", + "annotation_date": "2024-01-15T10:00:00", + "document_type": "bacterio", + "page_count": 1, + "difficulty": "simple" + }, + "annotations": [ + { + "id": "ann_001", + "page": 0, + "type": "NOM", + "text": "DUPONT", + "bbox": None, + "context": "Dr. DUPONT a examiné", + "mandatory": True, + "difficulty": "easy", + "detection_method_expected": ["regex", "ner"] + }, + { + "id": "ann_002", + "page": 0, + "type": "TEL", + "text": "01 23 45 67 89", + "bbox": None, + "context": "Tel: 01 23 45 67 89", + "mandatory": True, + "difficulty": "easy", + "detection_method_expected": ["regex"] + }, + { + "id": "ann_003", + "page": 0, + "type": "EMAIL", + "text": "jean.dupont@chu-bordeaux.fr", + "bbox": None, + "context": "Email: jean.dupont@chu-bordeaux.fr", + "mandatory": True, + "difficulty": "easy", + "detection_method_expected": ["regex"] + } + ], + "medical_terms_to_preserve": ["Médecin DIM", "Service de bactériologie"], + "statistics": { + "total_pii": 3, + "by_type": {"NOM": 1, "TEL": 1, "EMAIL": 1} + } + } + + with open(test_annotation, 'w', encoding='utf-8') as f: + json.dump(annotation_data, f, indent=2, ensure_ascii=False) + + print(f"✓ Annotation créée: {test_annotation.name}") + print(f" - 3 PII annotés: 1 NOM, 1 TEL, 1 EMAIL") + + # Créer un audit fictif (détections) + audit_data = [ + {"page": 0, "kind": "NOM", "original": "DUPONT", "placeholder": "[NOM]"}, + {"page": 0, "kind": "TEL", "original": "01 23 45 67 89", "placeholder": "[TEL]"}, + {"page": 0, "kind": "EMAIL", "original": "jean.dupont@chu-bordeaux.fr", "placeholder": "[EMAIL]"}, + {"page": 0, "kind": "NOM", "original": "MARTIN", "placeholder": "[NOM]"} # Faux positif + ] + + with open(test_audit, 'w', encoding='utf-8') as f: + for item in audit_data: + f.write(json.dumps(item) + '\n') + + print(f"✓ Audit créé: {test_audit.name}") + print(f" - 4 PII détectés: 2 NOM, 1 TEL, 1 EMAIL") + + # Évaluer + print("\n🔍 Évaluation en cours...") + evaluator = QualityEvaluator(Path("tests/ground_truth/pdfs")) + result = evaluator.evaluate(test_pdf, test_audit) + + if result: + print("\n📊 RÉSULTATS:") + print(f" True Positives: {result.true_positives}") + print(f" False Positives: {result.false_positives}") + print(f" False Negatives: {result.false_negatives}") + print(f"\n Précision: {result.precision:.4f} ({result.precision*100:.2f}%)") + print(f" Rappel: {result.recall:.4f} ({result.recall*100:.2f}%)") + print(f" F1-Score: {result.f1_score:.4f}") + + if result.false_positives > 0: + print(f"\n ⚠ Faux positifs détectés:") + for fp in result.false_detections: + print(f" - {fp['type']}: {fp['text']}") + + # Générer un rapport + report = evaluator.generate_report([result]) + print("\n" + "─"*80) + print(report) + + # Nettoyer + test_annotation.unlink() + test_audit.unlink() + print("\n✓ Fichiers de test nettoyés") + + +def demo_leak_scanner(): + """Démo du scanner de fuite.""" + print("\n" + "="*80) + print("DÉMO 3 : SCANNER DE FUITE") + print("="*80) + + print("\nLe scanner vérifie qu'aucun PII ne subsiste dans les documents anonymisés.") + print("Il détecte:") + print(" - PII originaux encore présents (CRITIQUE)") + print(" - Nouveaux PII non masqués (HAUTE)") + print(" - Métadonnées suspectes (MOYENNE)") + + # Créer des données de test + print("\n📝 Création de données de test...") + + test_pdf = Path("tests/ground_truth/pdfs/001_simple_unknown_BACTERIO_23018396.pdf") + test_audit = test_pdf.parent / f"{test_pdf.stem}.audit.jsonl" + + # Créer un audit fictif + audit_data = [ + {"page": 0, "kind": "NOM", "original": "DUPONT", "placeholder": "[NOM]"}, + {"page": 0, "kind": "TEL", "original": "01 23 45 67 89", "placeholder": "[TEL]"} + ] + + with open(test_audit, 'w', encoding='utf-8') as f: + for item in audit_data: + f.write(json.dumps(item) + '\n') + + print(f"✓ Audit créé: {test_audit.name}") + + # Scanner (simulation - le PDF n'est pas vraiment anonymisé) + print("\n🔍 Scan en cours...") + scanner = LeakScanner() + + # Simuler un scan avec du texte + print("\n📄 Simulation de scan de texte:") + + # Cas 1 : Document sûr + safe_text = "Le patient [NOM] a été examiné le [DATE]. Contact: [TEL]" + original_pii = [ + {"kind": "NOM", "original": "DUPONT"}, + {"kind": "TEL", "original": "01 23 45 67 89"} + ] + + leaks = scanner.scan_text(safe_text, original_pii) + print(f"\n Texte: {safe_text}") + print(f" Résultat: {'✓ Aucune fuite' if len(leaks) == 0 else f'✗ {len(leaks)} fuite(s)'}") + + # Cas 2 : Document avec fuite + unsafe_text = "Le patient DUPONT a été examiné. Tel: 01 23 45 67 89" + leaks = scanner.scan_text(unsafe_text, original_pii) + print(f"\n Texte: {unsafe_text}") + print(f" Résultat: {'✓ Aucune fuite' if len(leaks) == 0 else f'✗ {len(leaks)} fuite(s)'}") + + if leaks: + for leak in leaks: + print(f" - {leak['severity']}: {leak['message']}") + + # Cas 3 : Nouveau PII détecté + new_pii_text = "Contact: marie.martin@example.com" + leaks = scanner.scan_text(new_pii_text, original_pii) + print(f"\n Texte: {new_pii_text}") + print(f" Résultat: {'✓ Aucune fuite' if len(leaks) == 0 else f'✗ {len(leaks)} fuite(s)'}") + + if leaks: + for leak in leaks: + print(f" - {leak['severity']}: {leak['message']}") + + # Nettoyer + test_audit.unlink() + print("\n✓ Fichiers de test nettoyés") + + +def demo_benchmark(): + """Démo du benchmark.""" + print("\n" + "="*80) + print("DÉMO 4 : BENCHMARK DE PERFORMANCE") + print("="*80) + + print("\nLe benchmark mesure les performances du système d'anonymisation:") + print(" - Temps de traitement (total, par page)") + print(" - Utilisation CPU (%)") + print(" - Utilisation RAM (MB)") + print(" - Nombre de PII détectés") + + # Afficher les informations système + print("\n💻 Informations système:") + benchmark = Benchmark(Path("tests/ground_truth/pdfs")) + system_info = benchmark.get_system_info() + + for key, value in system_info.items(): + print(f" {key}: {value}") + + print("\n📊 Exemple de résultats de benchmark:") + print("\n Document: 001_simple_unknown_BACTERIO_23018396.pdf") + print(" Temps: 3.45s") + print(" Temps/page: 3.45s") + print(" CPU: 45.2%") + print(" RAM: 512.3 MB") + print(" PII détectés: 12") + + print("\n Document: 023_complexe_compte_rendu_CRH_23102610.pdf") + print(" Temps: 25.67s") + print(" Temps/page: 2.85s (9 pages)") + print(" CPU: 67.8%") + print(" RAM: 1024.5 MB") + print(" PII détectés: 45") + + print("\n📈 Résumé (27 documents):") + print(" Temps moyen: 8.5s") + print(" Temps min/max: 2.1s / 25.7s") + print(" CPU moyen: 52.3%") + print(" RAM moyenne: 768.2 MB") + print(" PII détectés: 245 (moy: 9.1)") + + +def main(): + """Fonction principale.""" + print("\n" + "="*80) + print("DÉMONSTRATION DU SYSTÈME D'ÉVALUATION DE LA QUALITÉ D'ANONYMISATION") + print("="*80) + + print("\nCe système permet de:") + print(" 1. Annoter manuellement des documents PDF") + print(" 2. Évaluer la qualité des détections (Précision, Rappel, F1)") + print(" 3. Scanner les fuites de PII dans les documents anonymisés") + print(" 4. Benchmarker les performances du système") + + # Lancer les démos + demo_annotation_tool() + demo_quality_evaluator() + demo_leak_scanner() + demo_benchmark() + + print("\n" + "="*80) + print("FIN DE LA DÉMONSTRATION") + print("="*80) + + print("\n📚 Pour en savoir plus:") + print(" - Guide d'annotation: docs/annotation_guide.md") + print(" - Documentation du module: evaluation/README.md") + print(" - Tests unitaires: tests/unit/") + + print("\n🚀 Prochaines étapes:") + print(" 1. Annoter les 27 documents: python tools/annotation_tool.py --resume") + print(" 2. Anonymiser les documents avec le système actuel") + print(" 3. Évaluer la qualité: python -c 'from evaluation import QualityEvaluator; ...'") + print(" 4. Mesurer la baseline et identifier les améliorations") + + print("\n✨ Bon travail !") + + +if __name__ == "__main__": + main() diff --git a/tests/unit/test_quality_evaluator.py b/tests/unit/test_quality_evaluator.py index 6aedd07..504e038 100644 --- a/tests/unit/test_quality_evaluator.py +++ b/tests/unit/test_quality_evaluator.py @@ -50,7 +50,7 @@ class TestQualityEvaluator: precision, recall, f1 = evaluator.calculate_metrics(8, 2, 2) assert precision == 0.8 # 8 / (8 + 2) assert recall == 0.8 # 8 / (8 + 2) - assert f1 == 0.8 + assert abs(f1 - 0.8) < 0.0001 # Tolérance pour les flottants # Cas zéro precision, recall, f1 = evaluator.calculate_metrics(0, 0, 0)