#!/usr/bin/env python3 """ Extrait les exemples de faux positifs en comparant annotations et détections. """ import json from pathlib import Path from collections import defaultdict def load_annotations(pdf_name): """Charge les annotations pour un PDF.""" # Essayer différents formats de noms possible_names = [ pdf_name, pdf_name.replace('.redacted_raster', ''), pdf_name.split('.')[0] ] for name in possible_names: annotation_file = Path(f"tests/ground_truth/annotations/{name}.json") if annotation_file.exists(): with open(annotation_file, 'r', encoding='utf-8') as f: return json.load(f) return None def load_detections(pdf_name): """Charge les détections pour un PDF.""" audit_file = Path(f"tests/ground_truth/pdfs/baseline_anonymized/{pdf_name}.audit.jsonl") if not audit_file.exists(): return [] detections = [] with open(audit_file, 'r', encoding='utf-8') as f: for line in f: detections.append(json.loads(line)) return detections def normalize_text(text): """Normalise le texte pour la comparaison.""" return text.lower().strip() def is_match(detection, annotation, tolerance=5): """Vérifie si une détection correspond à une annotation.""" # Même page if detection.get('page') != annotation.get('page'): return False # Même type (ou compatible) det_type = detection.get('type', '') ann_type = annotation.get('type', '') # Normaliser les types type_mapping = { 'NOM': ['NOM', 'PRENOM'], 'PRENOM': ['NOM', 'PRENOM'], } det_types = type_mapping.get(det_type, [det_type]) ann_types = type_mapping.get(ann_type, [ann_type]) if not any(dt in ann_types for dt in det_types): return False # Texte similaire det_text = normalize_text(detection.get('text', '')) ann_text = normalize_text(annotation.get('text', '')) return det_text == ann_text or det_text in ann_text or ann_text in det_text def extract_false_positives(): """Extrait les faux positifs de chaque document.""" eval_file = Path("tests/ground_truth/quality_evaluation/baseline_quality_evaluation.json") with open(eval_file, 'r', encoding='utf-8') as f: eval_data = json.load(f) false_positives = defaultdict(list) # Parcourir chaque document for doc_result in eval_data['per_document']: pdf_name = doc_result['pdf'] # Charger annotations et détections annotations = load_annotations(pdf_name) detections = load_detections(pdf_name) if not annotations or not detections: continue # Identifier les faux positifs for detection in detections: # Vérifier si cette détection correspond à une annotation is_true_positive = False for annotation in annotations.get('pii', []): if is_match(detection, annotation): is_true_positive = True break # Si pas de correspondance, c'est un faux positif if not is_true_positive: pii_type = detection.get('type', 'UNKNOWN') false_positives[pii_type].append({ 'text': detection.get('text', ''), 'page': detection.get('page', 0), 'file': pdf_name, 'method': detection.get('method', 'unknown') }) # Afficher les résultats print("=" * 80) print("EXEMPLES DE FAUX POSITIFS") print("=" * 80) print() problematic_types = ['EPISODE', 'VILLE', 'CODE_POSTAL', 'ADRESSE', 'TEL'] for pii_type in problematic_types: fps = false_positives.get(pii_type, []) if not fps: continue print(f"\n{'=' * 80}") print(f"Type: {pii_type} ({len(fps)} faux positifs)") print(f"{'=' * 80}") # Grouper par texte pour voir les patterns text_counts = defaultdict(int) for fp in fps: text_counts[fp['text']] += 1 # Afficher les plus fréquents sorted_texts = sorted(text_counts.items(), key=lambda x: x[1], reverse=True) print(f"\nTextes les plus fréquents:") for text, count in sorted_texts[:20]: print(f" {count:3d}x '{text}'") # Afficher quelques exemples avec contexte print(f"\nExemples avec contexte:") for i, fp in enumerate(fps[:10], 1): print(f" {i:2d}. '{fp['text']}' (page {fp['page']}, méthode: {fp['method']})") print(f" Fichier: {fp['file']}") # Sauvegarder les résultats output_file = Path("tests/ground_truth/analysis/false_positives_examples.json") output_file.parent.mkdir(parents=True, exist_ok=True) with open(output_file, 'w', encoding='utf-8') as f: json.dump(dict(false_positives), f, indent=2, ensure_ascii=False) print(f"\n✅ Résultats sauvegardés dans: {output_file}") if __name__ == "__main__": extract_false_positives()