feat: Phase 1 - Système d'évaluation de la qualité
- Sélection et copie de 27 documents représentatifs (10 simples, 12 moyens, 5 complexes) - Outil d'annotation CLI complet (tools/annotation_tool.py) - Guide d'annotation détaillé (docs/annotation_guide.md) - Évaluateur de qualité (evaluation/quality_evaluator.py) * Calcul Précision, Rappel, F1-Score * Identification faux positifs/négatifs * Métriques par type de PII * Export JSON et rapports texte - Scanner de fuite (evaluation/leak_scanner.py) * Détection PII résiduels (CRITIQUE) * Détection nouveaux PII (HAUTE) * Scan métadonnées PDF (MOYENNE) - Benchmark de performance (evaluation/benchmark.py) * Mesure temps de traitement * Mesure CPU/RAM * Export JSON/CSV - Tests unitaires complets pour tous les composants - Documentation complète du module d'évaluation Tâches complétées: - 1.1.1 Sélection de 27 documents (au lieu de 30) - 1.1.2 Outil d'annotation CLI - 1.2.1 Évaluateur de qualité - 1.2.2 Scanner de fuite - 1.2.3 Benchmark de performance Prochaines étapes: - 1.1.3 Annotation des 27 documents (manuel) - 1.1.4 Enrichissement stopwords médicaux - 1.3 Mesure de la baseline
This commit is contained in:
194
tools/analyze_corpus.py
Executable file
194
tools/analyze_corpus.py
Executable file
@@ -0,0 +1,194 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Analyse du corpus OGC pour sélection de documents représentatifs.
|
||||
"""
|
||||
import sys
|
||||
from pathlib import Path
|
||||
import json
|
||||
import random
|
||||
|
||||
try:
|
||||
import fitz # PyMuPDF
|
||||
except ImportError:
|
||||
print("PyMuPDF non disponible, analyse limitée")
|
||||
fitz = None
|
||||
|
||||
|
||||
def analyze_pdf(pdf_path: Path) -> dict:
|
||||
"""Analyse un PDF : nombre de pages, taille, type."""
|
||||
stats = {
|
||||
"path": str(pdf_path),
|
||||
"folder": pdf_path.parent.name,
|
||||
"filename": pdf_path.name,
|
||||
"size_mb": round(pdf_path.stat().st_size / (1024 * 1024), 2),
|
||||
"pages": 0,
|
||||
"type": "unknown",
|
||||
}
|
||||
|
||||
# Déterminer le type de document
|
||||
name_lower = pdf_path.name.lower()
|
||||
if "trackare" in name_lower:
|
||||
stats["type"] = "trackare"
|
||||
elif "crh" in name_lower or "cr" in name_lower:
|
||||
stats["type"] = "compte_rendu"
|
||||
elif "anapath" in name_lower:
|
||||
stats["type"] = "anapath"
|
||||
elif "lettre" in name_lower or "sortie" in name_lower:
|
||||
stats["type"] = "lettre_sortie"
|
||||
elif "cro" in name_lower:
|
||||
stats["type"] = "cro"
|
||||
|
||||
# Compter les pages si PyMuPDF disponible
|
||||
if fitz:
|
||||
try:
|
||||
doc = fitz.open(str(pdf_path))
|
||||
stats["pages"] = len(doc)
|
||||
doc.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return stats
|
||||
|
||||
|
||||
def classify_complexity(stats: dict) -> str:
|
||||
"""Classifie la complexité d'un document."""
|
||||
pages = stats["pages"]
|
||||
size_mb = stats["size_mb"]
|
||||
|
||||
if pages <= 2 and size_mb < 0.3:
|
||||
return "simple"
|
||||
elif pages >= 6 or size_mb > 1.0:
|
||||
return "complexe"
|
||||
else:
|
||||
return "moyen"
|
||||
|
||||
|
||||
def main():
|
||||
corpus_dir = Path("/home/dom/Téléchargements/II-1 Ctrl_T2A_2025_CHCB_DocJustificatifs (1)/")
|
||||
|
||||
if not corpus_dir.exists():
|
||||
print(f"Erreur : {corpus_dir} n'existe pas")
|
||||
return 1
|
||||
|
||||
print("Analyse du corpus OGC...")
|
||||
print(f"Répertoire : {corpus_dir}")
|
||||
|
||||
# Collecter tous les PDFs
|
||||
all_pdfs = list(corpus_dir.glob("*/*.pdf"))
|
||||
print(f"Total PDFs trouvés : {len(all_pdfs)}")
|
||||
|
||||
# Analyser un échantillon pour estimation
|
||||
sample_size = min(100, len(all_pdfs))
|
||||
sample = random.sample(all_pdfs, sample_size)
|
||||
|
||||
print(f"\nAnalyse d'un échantillon de {sample_size} documents...")
|
||||
|
||||
analyzed = []
|
||||
for i, pdf_path in enumerate(sample, 1):
|
||||
if i % 20 == 0:
|
||||
print(f" Analysé {i}/{sample_size}...")
|
||||
stats = analyze_pdf(pdf_path)
|
||||
stats["complexity"] = classify_complexity(stats)
|
||||
analyzed.append(stats)
|
||||
|
||||
# Statistiques globales
|
||||
print("\n" + "="*60)
|
||||
print("STATISTIQUES GLOBALES")
|
||||
print("="*60)
|
||||
|
||||
# Par type
|
||||
types_count = {}
|
||||
for s in analyzed:
|
||||
types_count[s["type"]] = types_count.get(s["type"], 0) + 1
|
||||
|
||||
print("\nRépartition par type :")
|
||||
for doc_type, count in sorted(types_count.items(), key=lambda x: -x[1]):
|
||||
pct = (count / len(analyzed)) * 100
|
||||
print(f" {doc_type:20s} : {count:3d} ({pct:5.1f}%)")
|
||||
|
||||
# Par complexité
|
||||
complexity_count = {}
|
||||
for s in analyzed:
|
||||
complexity_count[s["complexity"]] = complexity_count.get(s["complexity"], 0) + 1
|
||||
|
||||
print("\nRépartition par complexité :")
|
||||
for complexity, count in sorted(complexity_count.items()):
|
||||
pct = (count / len(analyzed)) * 100
|
||||
print(f" {complexity:20s} : {count:3d} ({pct:5.1f}%)")
|
||||
|
||||
# Statistiques pages
|
||||
pages_list = [s["pages"] for s in analyzed if s["pages"] > 0]
|
||||
if pages_list:
|
||||
print(f"\nNombre de pages :")
|
||||
print(f" Min : {min(pages_list)}")
|
||||
print(f" Max : {max(pages_list)}")
|
||||
print(f" Moy : {sum(pages_list) / len(pages_list):.1f}")
|
||||
|
||||
# Statistiques taille
|
||||
sizes_list = [s["size_mb"] for s in analyzed]
|
||||
print(f"\nTaille (MB) :")
|
||||
print(f" Min : {min(sizes_list):.2f}")
|
||||
print(f" Max : {max(sizes_list):.2f}")
|
||||
print(f" Moy : {sum(sizes_list) / len(sizes_list):.2f}")
|
||||
|
||||
# Sélection de 30 documents représentatifs
|
||||
print("\n" + "="*60)
|
||||
print("SÉLECTION DE 30 DOCUMENTS REPRÉSENTATIFS")
|
||||
print("="*60)
|
||||
|
||||
# Stratégie : 10 simples, 15 moyens, 5 complexes
|
||||
# Varier les types de documents
|
||||
|
||||
simples = [s for s in analyzed if s["complexity"] == "simple"]
|
||||
moyens = [s for s in analyzed if s["complexity"] == "moyen"]
|
||||
complexes = [s for s in analyzed if s["complexity"] == "complexe"]
|
||||
|
||||
print(f"\nDisponibles : {len(simples)} simples, {len(moyens)} moyens, {len(complexes)} complexes")
|
||||
|
||||
selected = []
|
||||
|
||||
# Sélectionner 10 simples
|
||||
if len(simples) >= 10:
|
||||
selected.extend(random.sample(simples, 10))
|
||||
else:
|
||||
selected.extend(simples)
|
||||
print(f"⚠️ Seulement {len(simples)} documents simples disponibles")
|
||||
|
||||
# Sélectionner 15 moyens
|
||||
if len(moyens) >= 15:
|
||||
selected.extend(random.sample(moyens, 15))
|
||||
else:
|
||||
selected.extend(moyens)
|
||||
print(f"⚠️ Seulement {len(moyens)} documents moyens disponibles")
|
||||
|
||||
# Sélectionner 5 complexes
|
||||
if len(complexes) >= 5:
|
||||
selected.extend(random.sample(complexes, 5))
|
||||
else:
|
||||
selected.extend(complexes)
|
||||
print(f"⚠️ Seulement {len(complexes)} documents complexes disponibles")
|
||||
|
||||
print(f"\nTotal sélectionnés : {len(selected)}")
|
||||
|
||||
# Sauvegarder la sélection
|
||||
output_file = Path("tests/ground_truth/selected_documents.json")
|
||||
output_file.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
with open(output_file, "w", encoding="utf-8") as f:
|
||||
json.dump(selected, f, indent=2, ensure_ascii=False)
|
||||
|
||||
print(f"\nSélection sauvegardée dans : {output_file}")
|
||||
|
||||
# Afficher la liste
|
||||
print("\nDocuments sélectionnés :")
|
||||
print("-" * 80)
|
||||
for i, doc in enumerate(selected, 1):
|
||||
print(f"{i:2d}. [{doc['complexity']:8s}] {doc['folder']}/{doc['filename']}")
|
||||
print(f" {doc['pages']} pages, {doc['size_mb']} MB, type: {doc['type']}")
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user