feat: résumé clinique enrichi + preuves cliniques + validation QC batch

Améliore la qualité du codage CIM-10 sur 3 axes :
- Contexte clinique enrichi (interprétations bio, traitements indicatifs, marqueurs sévérité)
- Preuves cliniques structurées par diagnostic (evidence linking dans le prompt LLM)
- Validation batch post-codage (1 appel LLM/dossier, ajustement confiance, alertes QC)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
dom
2026-02-17 21:47:27 +01:00
parent dbc5bdbaf4
commit 94fa4e5f3b
7 changed files with 988 additions and 16 deletions

View File

@@ -0,0 +1,264 @@
"""Tests pour le module d'enrichissement du contexte clinique."""
import pytest
from src.medical.clinical_context import (
BIO_INTERPRETATIONS,
TREATMENT_INDICATORS,
interpret_bio_value,
detect_treatment_indicators,
detect_severity_markers,
build_enriched_context,
format_enriched_context,
)
from src.config import (
BiologieCle,
Diagnostic,
DossierMedical,
Imagerie,
Sejour,
Traitement,
)
# --- interpret_bio_value ---
class TestInterpretBioValue:
def test_crp_major(self):
assert interpret_bio_value("CRP", "180", True) == "syndrome inflammatoire majeur"
def test_crp_moderate(self):
assert interpret_bio_value("CRP", "45", True) == "syndrome inflammatoire modéré"
def test_crp_minor(self):
assert interpret_bio_value("CRP", "8", True) == "syndrome inflammatoire mineur"
def test_crp_normal(self):
assert interpret_bio_value("CRP", "3", False) is None
def test_lipase_pancreatite(self):
assert interpret_bio_value("Lipasémie", "450", True) == "pancréatite biologique (>3N)"
def test_lipase_moderee(self):
assert interpret_bio_value("Lipasémie", "90", True) == "élévation modérée de la lipase"
def test_hemoglobine_severe(self):
assert interpret_bio_value("Hémoglobine", "6.5", True) == "anémie sévère (transfusion probable)"
def test_hemoglobine_moderee(self):
assert interpret_bio_value("Hémoglobine", "9", True) == "anémie modérée"
def test_plaquettes_severe(self):
assert interpret_bio_value("Plaquettes", "30", True) == "thrombopénie sévère"
def test_leucocytes_high(self):
assert interpret_bio_value("Leucocytes", "25", True) == "hyperleucocytose majeure (infection, inflammation)"
def test_leucocytes_low(self):
assert interpret_bio_value("Leucocytes", "1.5", True) == "leucopénie sévère (aplasie, immunodépression)"
def test_creatinine_severe(self):
assert interpret_bio_value("Créatinine", "350", True) == "insuffisance rénale sévère"
def test_unknown_test(self):
assert interpret_bio_value("Glycémie", "2.5", True) is None
def test_invalid_value(self):
assert interpret_bio_value("CRP", "positive", True) is None
def test_comma_separator(self):
assert interpret_bio_value("Hémoglobine", "6,5", True) == "anémie sévère (transfusion probable)"
def test_bilirubine_ictere(self):
assert interpret_bio_value("Bilirubine totale", "55", True) == "ictère franc"
def test_asat_cytolyse_majeure(self):
assert interpret_bio_value("ASAT", "250", True) == "cytolyse hépatique majeure (>5N)"
def test_asat_cytolyse_moderee(self):
assert interpret_bio_value("ASAT", "100", True) == "cytolyse hépatique modérée (>2N)"
# --- detect_treatment_indicators ---
class TestDetectTreatmentIndicators:
def test_insuline(self):
traitements = [Traitement(medicament="INSULINE LANTUS 20UI")]
result = detect_treatment_indicators(traitements)
assert len(result) == 1
assert result[0]["condition"] == "diabète insulino-traité"
def test_antibiotique_iv(self):
traitements = [Traitement(medicament="CEFTRIAXONE 1g IV")]
result = detect_treatment_indicators(traitements)
assert len(result) == 1
assert result[0]["condition"] == "antibiothérapie IV"
def test_multiple(self):
traitements = [
Traitement(medicament="Metformine 1000mg"),
Traitement(medicament="Enoxaparine 4000UI"),
Traitement(medicament="Paracétamol 1g"),
]
result = detect_treatment_indicators(traitements)
assert len(result) == 2
conditions = {r["condition"] for r in result}
assert "diabète type 2" in conditions
assert "anticoagulation (HBPM)" in conditions
def test_no_match(self):
traitements = [Traitement(medicament="Paracétamol 1g")]
result = detect_treatment_indicators(traitements)
assert result == []
def test_dedup_conditions(self):
traitements = [
Traitement(medicament="Enoxaparine 4000UI"),
Traitement(medicament="Lovenox 4000UI"),
]
result = detect_treatment_indicators(traitements)
# Les deux sont HBPM, mais une seule condition
assert len(result) == 1
def test_dict_input(self):
traitements = [{"medicament": "morphine 10mg"}]
result = detect_treatment_indicators(traitements)
assert len(result) == 1
assert result[0]["condition"] == "analgésie palier 3 (douleur sévère)"
# --- detect_severity_markers ---
class TestDetectSeverityMarkers:
def test_sejour_prolonge(self):
dossier = DossierMedical(sejour=Sejour(duree_sejour=20))
markers = detect_severity_markers(dossier)
assert any("séjour prolongé" in m for m in markers)
def test_sejour_gt7(self):
dossier = DossierMedical(sejour=Sejour(duree_sejour=10))
markers = detect_severity_markers(dossier)
assert any("séjour >7 jours" in m for m in markers)
def test_patient_tres_age(self):
dossier = DossierMedical(sejour=Sejour(age=85))
markers = detect_severity_markers(dossier)
assert any("très âgé" in m for m in markers)
def test_patient_age(self):
dossier = DossierMedical(sejour=Sejour(age=72))
markers = detect_severity_markers(dossier)
assert any("patient âgé" in m for m in markers)
def test_obesite_morbide(self):
dossier = DossierMedical(sejour=Sejour(imc=42.0))
markers = detect_severity_markers(dossier)
assert any("obésité morbide" in m for m in markers)
def test_complications(self):
dossier = DossierMedical(complications=["Fièvre", "Hématome"])
markers = detect_severity_markers(dossier)
assert any("2 complication(s)" in m for m in markers)
def test_no_markers(self):
dossier = DossierMedical(sejour=Sejour(age=45, duree_sejour=3))
markers = detect_severity_markers(dossier)
assert markers == []
# --- build_enriched_context ---
class TestBuildEnrichedContext:
def test_basic_context(self):
dossier = DossierMedical(
sejour=Sejour(sexe="M", age=65, duree_sejour=5),
biologie_cle=[
BiologieCle(test="CRP", valeur="150", anomalie=True),
],
traitements_sortie=[
Traitement(medicament="Ceftriaxone 1g"),
],
)
ctx = build_enriched_context(dossier)
assert ctx["sexe"] == "M"
assert ctx["age"] == 65
assert len(ctx["interpretations_bio"]) == 1
assert ctx["interpretations_bio"][0]["interpretation"] == "syndrome inflammatoire majeur"
assert len(ctx["conditions_traitements"]) == 1
assert ctx["conditions_traitements"][0]["condition"] == "antibiothérapie IV"
def test_no_abnormal_bio(self):
dossier = DossierMedical(
biologie_cle=[
BiologieCle(test="CRP", valeur="3", anomalie=False),
],
)
ctx = build_enriched_context(dossier)
assert ctx["interpretations_bio"] == []
# --- format_enriched_context ---
class TestFormatEnrichedContext:
def test_with_interpretations(self):
ctx = {
"sexe": "F",
"age": 70,
"imc": None,
"duree_sejour": 10,
"antecedents": ["HTA", "Diabète"],
"biologie_cle": [("CRP", "180", True)],
"imagerie": [],
"complications": [],
"dp_texte": None,
"das_codes_existants": None,
"interpretations_bio": [
{"test": "CRP", "valeur": "180 mg/L", "interpretation": "syndrome inflammatoire majeur"},
],
"conditions_traitements": [
{"medicament": "Insuline", "condition": "diabète insulino-traité"},
],
"marqueurs_severite": ["séjour >7 jours (10 jours)"],
}
result = format_enriched_context(ctx)
assert "Patient : F, 70 ans" in result
assert "Durée séjour : 10 jours" in result
assert "HTA" in result
assert "CRP 180" in result
assert "INTERPRÉTATION CLINIQUE" in result
assert "syndrome inflammatoire majeur" in result
assert "diabète insulino-traité" in result
assert "séjour >7 jours" in result
def test_empty_context(self):
ctx = {
"sexe": None, "age": None, "imc": None,
"duree_sejour": None, "antecedents": [],
"biologie_cle": [], "imagerie": [],
"complications": [], "dp_texte": None,
"das_codes_existants": None,
"interpretations_bio": [],
"conditions_traitements": [],
"marqueurs_severite": [],
}
result = format_enriched_context(ctx)
assert result == "Non précisé"
def test_backward_compat_bio_format(self):
"""Le format bio tuple (test, valeur, anomalie) doit rester compatible."""
ctx = {
"sexe": None, "age": None, "imc": None,
"duree_sejour": None, "antecedents": [],
"biologie_cle": [("CRP", "180", True)],
"imagerie": [],
"complications": [],
"dp_texte": None,
"das_codes_existants": None,
"interpretations_bio": [],
"conditions_traitements": [],
"marqueurs_severite": [],
}
result = format_enriched_context(ctx)
assert "CRP 180" in result
assert "(\u2191)" in result