"""Tests pour le module d'extraction.""" import pytest from src.extraction.document_classifier import classify from src.extraction.crh_parser import parse_crh from src.extraction.trackare_parser import parse_trackare, _clean_person_name class TestDocumentClassifier: def test_classify_trackare(self): text = """CENTRE HOSPITALIER COTE BASQUE Dossier Patient Détails des patients Nom de naissance: CLIER IPP: 01306172 Détails épisode Episode No: 23042753 Signes Vitaux""" assert classify(text) == "trackare" def test_classify_crh(self): text = """N° Finess CENTRE HOSPITALIER COTE BASQUE Pôle Spécialités Médicales Service de Gastro-Entérologie Mon cher confrère, Votre patiente a été hospitalisée""" assert classify(text) == "crh" def test_classify_trackare_by_ipp(self): text = "IPP: 12345678 Episode No: 87654321" assert classify(text) == "trackare" class TestCRHParser: def test_parse_patient_info(self): text = """MME NARBAIS AUDREY MAISON IRREXELAIA 64430 ST ETIENNE DE BAIGORRY Mon cher confrère, Votre patiente NARBAIS Audrey née le 23/02/1980 a été hospitalisée du 25/02/2023 au 03/03/2023 pour le motif suivant: Pancréatite aiguë lithiasique""" result = parse_crh(text) assert result["patient"]["nom_complet"] == "NARBAIS AUDREY" assert result["patient"]["sexe"] == "F" assert result["patient"]["date_naissance"] == "23/02/1980" def test_parse_sejour(self): text = """Votre patiente née le 23/02/1980 a été hospitalisée du 25/02/2023 au 03/03/2023 pour le motif suivant: Pancréatite aiguë""" result = parse_crh(text) assert result["sejour"]["date_entree"] == "25/02/2023" assert result["sejour"]["date_sortie"] == "03/03/2023" def test_parse_medecins(self): text = "Dr PUJOS. Dr F. AUDEMAR. Docteur DUTREY Sarah." result = parse_crh(text) assert any("PUJOS" in m for m in result["medecins"]) assert any("AUDEMAR" in m for m in result["medecins"]) class TestCRHSectionsDiagnostic: """Tests Patch 0 — nouvelles sections CRH à fort signal DP.""" def test_parse_diag_sortie(self): """'Diagnostic de sortie :' capturé dans sections.""" text = """Au total : Syndrome coronarien aigu traité par angioplastie. Diagnostic de sortie : SCA ST+ antérieur I25.1 Traitement de sortie : Aspirine 100mg""" result = parse_crh(text) assert "diag_sortie" in result["sections"] assert "SCA" in result["sections"]["diag_sortie"] def test_parse_diagnostics_retenus(self): """'Diagnostics retenus :' capturé comme diag_sortie.""" text = """Au total : Bilan. Diagnostics retenus : - Embolie pulmonaire I26.9 - Thrombose veineuse profonde I80.2 Devenir : Retour à domicile""" result = parse_crh(text) assert "diag_sortie" in result["sections"] assert "Embolie pulmonaire" in result["sections"]["diag_sortie"] def test_parse_diagnostics_retenus_a_la_sortie(self): """'Diagnostics retenus à la sortie :' capturé.""" text = """Conclusion : Amélioration. Diagnostics retenus à la sortie : Pneumopathie J18.9 TTT de sortie : Amoxicilline""" result = parse_crh(text) assert "diag_sortie" in result["sections"] assert "Pneumopathie" in result["sections"]["diag_sortie"] def test_parse_diag_principal(self): """'Diagnostic principal :' capturé.""" text = """Examen clinique : Patient fébrile. Diagnostic principal : Pancréatite aiguë biliaire K85.1 Diagnostics associés : Lithiase vésiculaire K80.2""" result = parse_crh(text) assert "diag_principal" in result["sections"] assert "Pancréatite" in result["sections"]["diag_principal"] def test_parse_probleme_principal(self): """'Problème principal :' capturé comme diag_principal.""" text = """Antécédents : HTA, diabète. Problème principal : Insuffisance cardiaque décompensée I50.0 Devenir : Hospitalisation prolongée""" result = parse_crh(text) assert "diag_principal" in result["sections"] assert "Insuffisance cardiaque" in result["sections"]["diag_principal"] def test_parse_synthese(self): """'Synthèse :' capturé.""" text = """Au total : Bilan complet. Synthèse : Patient de 65 ans admis pour SCA. Angioplastie réalisée. Traitement de sortie : Brilique""" result = parse_crh(text) assert "synthese" in result["sections"] assert "SCA" in result["sections"]["synthese"] def test_existing_sections_preserved(self): """Les 7 sections existantes sont toujours captées (non-régression).""" text = """pour le motif suivant: Douleur abdominale Antécédents : HTA traitée Histoire de la maladie : Douleur depuis 3 jours Examen clinique : Abdomen souple Au total : Pancréatite aiguë biliaire TTT de sortie : Paracétamol Devenir : Retour à domicile""" result = parse_crh(text) assert "motif_hospitalisation" in result["sections"] assert "antecedents" in result["sections"] assert "histoire_maladie" in result["sections"] assert "examen_clinique" in result["sections"] assert "conclusion" in result["sections"] assert "traitement_sortie" in result["sections"] assert "devenir" in result["sections"] def test_conclusion_does_not_overflow_into_diag_sortie(self): """La section conclusion s'arrête avant 'Diagnostic de sortie'.""" text = """Au total : Bilan complet réalisé. Évolution favorable. Diagnostic de sortie : SCA ST+ antérieur""" result = parse_crh(text) assert "conclusion" in result["sections"] assert "diag_sortie" in result["sections"] # La conclusion ne doit PAS contenir le texte du diagnostic de sortie assert "SCA" not in result["sections"]["conclusion"] assert "SCA" in result["sections"]["diag_sortie"] class TestCRHSectionsRobustness: """Tests robustesse Patch 0 — variantes de titres, terminaisons, cas pièges.""" # --- A1 : Variantes de titres --- def test_diagnostics_de_sortie_plural(self): """'Diagnostics de sortie' (pluriel) capturé.""" text = "Diagnostics de sortie :\nSCA I25.1\n\nDevenir :\nRAD" result = parse_crh(text) assert "diag_sortie" in result["sections"] assert "SCA" in result["sections"]["diag_sortie"] def test_diagnostics_retenus_en_sortie(self): """'Diagnostics retenus en sortie' capturé.""" text = "Diagnostics retenus en sortie :\nPneumopathie J18.9\n\nDevenir :\nRAD" result = parse_crh(text) assert "diag_sortie" in result["sections"] assert "Pneumopathie" in result["sections"]["diag_sortie"] def test_en_resume(self): """'En résumé' capturé comme synthese.""" text = "En résumé :\nPatient opéré avec succès.\n\nTraitement de sortie :\nParacétamol" result = parse_crh(text) assert "synthese" in result["sections"] assert "opéré" in result["sections"]["synthese"] def test_en_synthese(self): """'En synthèse' capturé comme synthese.""" text = "En synthèse :\nAmélioration clinique rapide.\n\nDevenir :\nRAD" result = parse_crh(text) assert "synthese" in result["sections"] assert "Amélioration" in result["sections"]["synthese"] def test_titre_avec_deux_points_colles(self): """'Diagnostic principal:' (sans espace avant ':') fonctionne.""" text = "Diagnostic principal:\nPancréatite aiguë K85.1\n\nDevenir :\nRAD" result = parse_crh(text) assert "diag_principal" in result["sections"] assert "Pancréatite" in result["sections"]["diag_principal"] def test_titre_synthese_sans_deux_points(self): """'Synthèse' suivi d'un saut de ligne (sans ':') fonctionne.""" text = "Synthèse\nBilan favorable, retour à domicile.\n\nDevenir :\nRAD" result = parse_crh(text) assert "synthese" in result["sections"] assert "favorable" in result["sections"]["synthese"] # --- A2 : Terminaisons correctes --- def test_antecedents_stop_before_diag_principal(self): """Antécédents s'arrêtent avant 'Diagnostic principal'.""" text = """Antécédents : HTA traitée depuis 10 ans. Diabète type 2. Diagnostic principal : Embolie pulmonaire I26.9""" result = parse_crh(text) assert "antecedents" in result["sections"] assert "diag_principal" in result["sections"] assert "Embolie" not in result["sections"]["antecedents"] assert "Embolie" in result["sections"]["diag_principal"] def test_histoire_maladie_stop_before_synthese(self): """Histoire de la maladie s'arrête avant 'Synthèse'.""" text = """Histoire de la maladie : Douleur abdominale depuis 3 jours. Synthèse : Pancréatite aiguë biliaire confirmée.""" result = parse_crh(text) assert "histoire_maladie" in result["sections"] assert "synthese" in result["sections"] assert "confirmée" not in result["sections"]["histoire_maladie"] def test_examen_clinique_stop_before_diag_sortie(self): """Examen clinique s'arrête avant 'Diagnostic de sortie'.""" text = """Examen clinique : Abdomen souple, pas de défense. Diagnostic de sortie : Cholécystite aiguë K81.0""" result = parse_crh(text) assert "examen_clinique" in result["sections"] assert "diag_sortie" in result["sections"] assert "Cholécystite" not in result["sections"]["examen_clinique"] def test_diag_sortie_multiline_not_truncated(self): """Section diag_sortie multi-lignes : contenu complet capturé.""" text = """Diagnostic de sortie : - SCA ST+ antérieur I25.1 - Anémie ferriprive D50 - HTA essentielle I10 Traitement de sortie : Aspirine 100mg""" result = parse_crh(text) assert "diag_sortie" in result["sections"] content = result["sections"]["diag_sortie"] assert "I25.1" in content assert "D50" in content assert "I10" in content assert len(content) > 0 def test_diag_principal_stop_before_diag_associes(self): """'Diagnostic principal' s'arrête avant 'Diagnostics associés'.""" text = """Diagnostic principal : Pancréatite aiguë biliaire K85.1 Diagnostics associés : Lithiase vésiculaire K80.2""" result = parse_crh(text) assert "diag_principal" in result["sections"] content = result["sections"]["diag_principal"] assert "Pancréatite" in content assert "Lithiase" not in content # --- A3 : Cas pièges (faux positifs) --- def test_diagnostic_in_phrase_not_captured(self): """'diagnostic' dans une phrase courante ne déclenche PAS une section.""" text = """Au total : Pas de diagnostic retenu pour l'instant. Bilan complémentaire en cours. Devenir : Consultation dans 3 semaines""" result = parse_crh(text) # "diag_sortie" et "diag_principal" ne doivent PAS apparaître assert "diag_sortie" not in result["sections"] assert "diag_principal" not in result["sections"] # Mais conclusion doit capturer le texte assert "conclusion" in result["sections"] def test_synthese_in_word_not_captured(self): """'synthèse' dans un mot composé ('biosynthèse') ne déclenche PAS la section.""" text = """Au total : Déficit de biosynthèse hépatique probable. Devenir : Surveillance""" result = parse_crh(text) assert "synthese" not in result["sections"] assert "conclusion" in result["sections"] class TestTrackareParser: def test_parse_patient_info(self): text = """Nom de naissance: CLIER IPP: 01306172 Nom et Prénom: NARBAIS AUDREY Date de naissance: 23/02/1980 Sexe: Féminin Lieu de naissance: CHAMPIGNY SUR MARNE Adresse: MAISON IRREXELAIA QUARTIER AUZO TTIPI Ville de résidence: ST ETIENNE DE BAIGORRY Code Postal: 64430 Episode No: 23042753 Date d'admission: 25/02/2023 Heure d'admission: 03:07 Date de sortie: 03/03/2023 Taille: 162 cm - Poids: 90.2 kg - IMC: 34.370""" result = parse_trackare(text) assert result["patient"]["nom_naissance"] == "CLIER" assert result["patient"]["nom_prenom"] == "NARBAIS AUDREY" assert result["patient"]["ipp"] == "01306172" assert result["patient"]["sexe"] == "F" assert result["patient"]["date_naissance"] == "23/02/1980" assert result["patient"]["imc"] == 34.370 assert result["sejour"]["episode"] == "23042753" assert result["sejour"]["date_entree"] == "25/02/2023" def test_parse_diagnostics(self): text = """Diagnostic aux urgences Type Etat Code Date Principal actif K80.5 Calcul des canaux biliaires (sans angiocholite ni cholécystite) [CMA2] 25/02/2023 05:27""" result = parse_trackare(text) assert len(result["diagnostics"]) >= 1 assert result["diagnostics"][0]["code_cim10"] == "K80.5" assert result["diagnostics"][0]["type"] == "Principal" def test_parse_vitals(self): text = """Poids/Taille Taille [cm] 162,00 Poids [kg] 90,20 Indice de masse 34.370""" result = parse_trackare(text) assert result["signes_vitaux"]["taille_cm"] == 162.0 assert result["signes_vitaux"]["poids_kg"] >= 90.0 assert result["signes_vitaux"]["imc"] == 34.370 class TestCleanPersonName: def test_clean_simple(self): assert _clean_person_name("Sarah DUTREY") == "Sarah DUTREY" def test_clean_with_noise(self): assert _clean_person_name("Sarah DUTREY une complication") == "Sarah DUTREY" def test_clean_multiline(self): assert _clean_person_name("Sarah\nDUTREY") == "Sarah DUTREY" def test_clean_medical_term(self): assert _clean_person_name("Bilirubine") == "" def test_clean_empty(self): assert _clean_person_name("") == ""