Files
t2a_v2/tests/test_extraction.py
dom 07c267539c tests: CRH sections + DP diag bonus + case 74 regression + fusion propagation
- test_extraction: +21 tests (sections diag_sortie/diag_principal/synthese,
  variantes titres, terminaisons, faux positifs mid-sentence, biosynthèse)
- test_dp_selector: +55 tests (flags, candidates, scoring, hardening DIM,
  bonus +4/+2, evidence excerpt, cas 74 D50→I25.1 corrigé)
- test_fusion: +39 tests (propagation dp_selection evidence/reason/verdict,
  source 2e dossier, pas de crash si aucun DP)
- fixtures: case_74_min.json + 3 fixtures DP existantes

Aucun mock utilisé — données synthétiques uniquement.
Le test cas 74 passe : I25.1 gagne sur D50 grâce au bonus diag_sortie +4.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 13:28:54 +01:00

406 lines
14 KiB
Python

"""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("") == ""