#!/usr/bin/env python """ Script pour charger et indexer tous les référentiels médicaux. Ce script : 1. Charge et indexe CIM-10 2026 depuis le PDF 2. Convertit/vérifie CCAM V81 → 2025 3. Extrait le Guide Méthodologique MCO 2026 4. Utilise le GPU pour l'indexation FAISS Usage: python scripts/load_referentiels.py """ import json import logging import sys from pathlib import Path from typing import List, Dict # Ajouter le répertoire parent au path sys.path.insert(0, str(Path(__file__).parent.parent)) try: import pypdf PDF_SUPPORT = True except ImportError: PDF_SUPPORT = False print("⚠️ pypdf non installé. Installez-le avec: pip install pypdf") sys.exit(1) try: import pandas as pd EXCEL_SUPPORT = True except ImportError: EXCEL_SUPPORT = False print("⚠️ pandas non installé. Installez-le avec: pip install pandas openpyxl") sys.exit(1) from pipeline_mco_pmsi.rag.referentiels_manager import ReferentielsManager # Configuration du logging logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s' ) logger = logging.getLogger(__name__) def extract_text_from_pdf(pdf_path: Path) -> str: """Extrait le texte d'un fichier PDF.""" logger.info(f"📄 Extraction du PDF: {pdf_path.name}") text_parts = [] try: with open(pdf_path, 'rb') as f: reader = pypdf.PdfReader(f) if reader.is_encrypted: try: reader.decrypt('') except: raise RuntimeError(f"PDF protégé par mot de passe: {pdf_path.name}") total_pages = len(reader.pages) logger.info(f" {total_pages} pages à traiter...") for i, page in enumerate(reader.pages, 1): if i % 50 == 0: logger.info(f" Progression: {i}/{total_pages} pages") text = page.extract_text() if text: text_parts.append(text) full_text = '\n\n'.join(text_parts) logger.info(f"✅ Extraction terminée: {len(full_text)} caractères") return full_text except Exception as e: logger.error(f"❌ Erreur extraction PDF: {e}") raise def load_cim10(data_dir: Path, manager: ReferentielsManager) -> bool: """Charge et indexe le référentiel CIM-10 2026.""" logger.info("\n" + "="*60) logger.info("🏥 CHARGEMENT CIM-10 2026") logger.info("="*60) pdf_path = Path("cim-10-fr_2026_a_usage_pmsi_version_provisoire_111225.pdf") if not pdf_path.exists(): logger.error(f"❌ Fichier CIM-10 introuvable: {pdf_path}") return False try: # Extraire le texte du PDF text = extract_text_from_pdf(pdf_path) # Sauvegarder le texte brut text_file = data_dir / "cim10_2026_text.txt" logger.info(f"💾 Sauvegarde du texte: {text_file}") with open(text_file, 'w', encoding='utf-8') as f: f.write(text) # Découper en chunks et indexer logger.info("🔪 Découpage en chunks...") chunks = manager.chunk_cim10(text, "2026") logger.info(f" {len(chunks)} chunks créés") # Construire l'index FAISS logger.info("🔍 Construction de l'index FAISS...") index = manager.build_index(chunks) logger.info(f" Index créé avec {index.dimension} dimensions") # Sauvegarder les chunks chunks_file = data_dir / "cim10_2026_chunks.json" logger.info(f"💾 Sauvegarde des chunks: {chunks_file}") chunks_data = [chunk.model_dump() for chunk in chunks] with open(chunks_file, 'w', encoding='utf-8') as f: json.dump(chunks_data, f, ensure_ascii=False, indent=2) logger.info("✅ CIM-10 2026 chargé et indexé avec succès!") return True except Exception as e: logger.error(f"❌ Erreur chargement CIM-10: {e}") import traceback traceback.print_exc() return False def load_ccam(data_dir: Path, manager: ReferentielsManager) -> bool: """Charge et indexe le référentiel CCAM 2025 (V81).""" logger.info("\n" + "="*60) logger.info("🔧 CHARGEMENT CCAM 2025 (V81)") logger.info("="*60) # Vérifier si les fichiers V81 existent v81_chunks = data_dir / "ccam_V81_chunks.json" v81_text = data_dir / "ccam_V81_text.txt" if v81_chunks.exists() and v81_text.exists(): logger.info("📦 Fichiers CCAM V81 trouvés, conversion en version 2025...") try: # Charger les chunks V81 with open(v81_chunks, 'r', encoding='utf-8') as f: chunks_data = json.load(f) logger.info(f" {len(chunks_data)} chunks trouvés") # Convertir en version 2025 logger.info("🔄 Conversion V81 → 2025...") for chunk in chunks_data: chunk['referentiel_version'] = '2025' chunk['chunk_id'] = chunk['chunk_id'].replace('V81', '2025') # Sauvegarder avec le nouveau nom chunks_2025 = data_dir / "ccam_2025_chunks.json" with open(chunks_2025, 'w', encoding='utf-8') as f: json.dump(chunks_data, f, ensure_ascii=False, indent=2) # Copier le fichier texte text_2025 = data_dir / "ccam_2025_text.txt" with open(v81_text, 'r', encoding='utf-8') as f: text_content = f.read() with open(text_2025, 'w', encoding='utf-8') as f: f.write(text_content) # Réindexer avec FAISS logger.info("🔍 Construction de l'index FAISS...") # Recréer les objets Chunk from pipeline_mco_pmsi.rag.referentiels_manager import Chunk chunks_objects = [Chunk(**chunk) for chunk in chunks_data] # Construire l'index index = manager.build_index(chunks_objects) logger.info(f" Index créé avec {index.dimension} dimensions") logger.info("✅ CCAM 2025 converti et indexé avec succès!") return True except Exception as e: logger.error(f"❌ Erreur conversion CCAM: {e}") logger.info(" Tentative de rechargement depuis le fichier Excel...") # Si conversion échoue ou fichiers absents, charger depuis Excel excel_path = Path("CCAM_V81.xls") if not excel_path.exists(): excel_path = data_dir / "CCAM_V81.xls" if not excel_path.exists(): logger.error(f"❌ Fichier CCAM introuvable: CCAM_V81.xls") return False try: logger.info(f"📊 Lecture du fichier Excel: {excel_path.name}") # Lire le fichier Excel df = pd.read_excel(excel_path) logger.info(f" {len(df)} lignes trouvées") # Extraire le texte (adapter selon la structure du fichier) text_parts = [] for _, row in df.iterrows(): # Adapter selon les colonnes du fichier CCAM row_text = ' '.join(str(val) for val in row.values if pd.notna(val)) text_parts.append(row_text) text = '\n\n'.join(text_parts) # Sauvegarder le texte text_file = data_dir / "ccam_2025_text.txt" logger.info(f"💾 Sauvegarde du texte: {text_file}") with open(text_file, 'w', encoding='utf-8') as f: f.write(text) # Découper et indexer logger.info("🔪 Découpage en chunks...") chunks = manager.chunk_ccam(text, "2025") logger.info(f" {len(chunks)} chunks créés") # Construire l'index FAISS logger.info("🔍 Construction de l'index FAISS...") index = manager.build_index(chunks) logger.info(f" Index créé avec {index.dimension} dimensions") # Sauvegarder les chunks chunks_file = data_dir / "ccam_2025_chunks.json" logger.info(f"💾 Sauvegarde des chunks: {chunks_file}") chunks_data = [chunk.model_dump() for chunk in chunks] with open(chunks_file, 'w', encoding='utf-8') as f: json.dump(chunks_data, f, ensure_ascii=False, indent=2) logger.info("✅ CCAM 2025 chargé et indexé avec succès!") return True except Exception as e: logger.error(f"❌ Erreur chargement CCAM: {e}") import traceback traceback.print_exc() return False def load_guide_mco(data_dir: Path, manager: ReferentielsManager) -> bool: """Charge et indexe le Guide Méthodologique MCO 2026.""" logger.info("\n" + "="*60) logger.info("📚 CHARGEMENT GUIDE MÉTHODOLOGIQUE MCO 2026") logger.info("="*60) pdf_path = Path("guide_methodo_mco_2026_version_provisoire.pdf") if not pdf_path.exists(): logger.warning(f"⚠️ Fichier Guide MCO introuvable: {pdf_path}") logger.info(" Le système fonctionnera sans le guide (optionnel)") return False try: # Extraire le texte du PDF text = extract_text_from_pdf(pdf_path) # Sauvegarder le texte brut text_file = data_dir / "guide_mco_2026_text.txt" logger.info(f"💾 Sauvegarde du texte: {text_file}") with open(text_file, 'w', encoding='utf-8') as f: f.write(text) # Découper en chunks et indexer logger.info("🔪 Découpage en chunks...") chunks = manager.chunk_guide_mco(text, "2026") logger.info(f" {len(chunks)} chunks créés") # Construire l'index FAISS logger.info("🔍 Construction de l'index FAISS...") index = manager.build_index(chunks) logger.info(f" Index créé avec {index.dimension} dimensions") # Sauvegarder les chunks chunks_file = data_dir / "guide_mco_2026_chunks.json" logger.info(f"💾 Sauvegarde des chunks: {chunks_file}") chunks_data = [chunk.model_dump() for chunk in chunks] with open(chunks_file, 'w', encoding='utf-8') as f: json.dump(chunks_data, f, ensure_ascii=False, indent=2) logger.info("✅ Guide MCO 2026 chargé et indexé avec succès!") return True except Exception as e: logger.error(f"❌ Erreur chargement Guide MCO: {e}") import traceback traceback.print_exc() return False def main(): """Point d'entrée principal.""" logger.info("\n" + "🚀 "*30) logger.info("CHARGEMENT DES RÉFÉRENTIELS MÉDICAUX") logger.info("🚀 "*30 + "\n") # Créer le répertoire de données si nécessaire data_dir = Path("data/referentiels") data_dir.mkdir(parents=True, exist_ok=True) # Initialiser le ReferentielsManager logger.info(f"📁 Répertoire de données: {data_dir.absolute()}") manager = ReferentielsManager(data_dir=data_dir) # Charger les référentiels results = { "CIM-10 2026": load_cim10(data_dir, manager), "CCAM 2025": load_ccam(data_dir, manager), "Guide MCO 2026": load_guide_mco(data_dir, manager), } # Résumé logger.info("\n" + "="*60) logger.info("📊 RÉSUMÉ DU CHARGEMENT") logger.info("="*60) for name, success in results.items(): status = "✅ OK" if success else "❌ ÉCHEC" logger.info(f" {name}: {status}") # Vérifier les fichiers créés logger.info("\n📦 Fichiers créés:") for file in sorted(data_dir.glob("*_2025_*")) + sorted(data_dir.glob("*_2026_*")): size_mb = file.stat().st_size / (1024 * 1024) logger.info(f" {file.name} ({size_mb:.1f} MB)") # Statut final all_success = all(results.values()) if all_success: logger.info("\n🎉 Tous les référentiels ont été chargés avec succès!") logger.info(" Le système est prêt à traiter des séjours.") else: logger.warning("\n⚠️ Certains référentiels n'ont pas pu être chargés.") logger.info(" Le système fonctionnera avec les référentiels disponibles.") return 0 if all_success else 1 if __name__ == "__main__": sys.exit(main())