feat: enrichissement CIM-10 sous-codes + normes biologiques dans prompt DAS
Piste 1 : ajout de cim10_supplements.json (40 sous-codes E10/E11/E13/F10) fusionné au chargement par load_dict() — E11.9 et autres ne sont plus rejetés. Piste 2 : export BIO_NORMALS depuis cim10_extractor, inclusion des plages de référence [N: min-max] dans le contexte LLM et règle explicite dans le prompt DAS pour éviter les hallucinations sur valeurs biologiques normales. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
75
tests/test_cim10_supplements.py
Normal file
75
tests/test_cim10_supplements.py
Normal file
@@ -0,0 +1,75 @@
|
||||
"""Tests pour la fusion des suppléments CIM-10 dans cim10_dict."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from src.medical.cim10_dict import load_dict, validate_code, reset_cache
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _reset():
|
||||
"""Reset le cache singleton avant/après chaque test."""
|
||||
reset_cache()
|
||||
yield
|
||||
reset_cache()
|
||||
|
||||
|
||||
class TestCim10Supplements:
|
||||
"""Tests pour la fusion des sous-codes supplémentaires."""
|
||||
|
||||
def test_e11_9_valid(self):
|
||||
"""E11.9 (diabète type 2 sans complication) est reconnu."""
|
||||
is_valid, label = validate_code("E11.9")
|
||||
assert is_valid
|
||||
assert "diabète" in label.lower() or "type 2" in label.lower()
|
||||
|
||||
def test_e10_9_valid(self):
|
||||
"""E10.9 (diabète type 1 sans complication) est reconnu."""
|
||||
is_valid, label = validate_code("E10.9")
|
||||
assert is_valid
|
||||
assert "type 1" in label.lower()
|
||||
|
||||
def test_f10_2_valid(self):
|
||||
"""F10.2 (alcool, syndrome de dépendance) est reconnu."""
|
||||
is_valid, label = validate_code("F10.2")
|
||||
assert is_valid
|
||||
assert "alcool" in label.lower()
|
||||
|
||||
def test_e13_9_valid(self):
|
||||
"""E13.9 (autres diabètes sans complication) est reconnu."""
|
||||
is_valid, label = validate_code("E13.9")
|
||||
assert is_valid
|
||||
|
||||
def test_main_dict_not_overwritten(self):
|
||||
"""Les entrées du dict principal ne sont PAS écrasées par les suppléments."""
|
||||
d = load_dict()
|
||||
# E11 existe dans le dict principal avec un label issu de FAISS
|
||||
assert "E11" in d
|
||||
# Le label doit venir du dict principal, pas des suppléments
|
||||
# (les suppléments ne contiennent que E11.0-E11.9, pas E11)
|
||||
|
||||
def test_supplements_file_missing(self, tmp_path):
|
||||
"""load_dict() fonctionne normalement si le fichier suppléments n'existe pas."""
|
||||
fake_supplements = tmp_path / "nonexistent.json"
|
||||
with patch("src.medical.cim10_dict.CIM10_SUPPLEMENTS_PATH", fake_supplements):
|
||||
d = load_dict()
|
||||
assert len(d) > 0 # Le dict principal est chargé
|
||||
|
||||
def test_supplement_codes_count(self):
|
||||
"""Les suppléments ajoutent les sous-codes attendus."""
|
||||
d = load_dict()
|
||||
# Vérifier que E11.0 à E11.9 existent tous
|
||||
for i in range(10):
|
||||
code = f"E11.{i}"
|
||||
assert code in d, f"{code} manquant du dictionnaire"
|
||||
|
||||
def test_validate_code_normalized(self):
|
||||
"""validate_code normalise le format (e119 → E11.9)."""
|
||||
is_valid, label = validate_code("e119")
|
||||
assert is_valid
|
||||
assert label # Label non vide
|
||||
@@ -126,6 +126,65 @@ class TestExtractDasLlm:
|
||||
assert "Patient hypertendu diabétique" in prompt
|
||||
|
||||
|
||||
class TestBioNormesInContext:
|
||||
"""Tests pour l'inclusion des normes biologiques dans le contexte LLM."""
|
||||
|
||||
def test_format_contexte_includes_normes(self):
|
||||
"""_format_contexte() affiche les normes [N: min-max] pour chaque résultat bio."""
|
||||
from src.medical.rag_search import _format_contexte
|
||||
|
||||
contexte = {
|
||||
"biologie_cle": [
|
||||
("Créatinine", "76", False),
|
||||
("CRP", "250", True),
|
||||
("Lipasémie", "1200", True),
|
||||
],
|
||||
}
|
||||
result = _format_contexte(contexte)
|
||||
assert "[N: 50-120]" in result
|
||||
assert "[N: 0-5]" in result
|
||||
assert "[N: 0-60]" in result
|
||||
# Créatinine normale → pas de marqueur ↑
|
||||
assert "Créatinine 76 [N: 50-120]" in result
|
||||
# CRP anormale → marqueur ↑
|
||||
assert "CRP 250 [N: 0-5] (↑)" in result
|
||||
|
||||
def test_format_contexte_no_norme_for_unknown_test(self):
|
||||
"""Les tests sans norme connue n'affichent pas de [N: ...]."""
|
||||
from src.medical.rag_search import _format_contexte
|
||||
|
||||
contexte = {
|
||||
"biologie_cle": [
|
||||
("Test inconnu", "42", None),
|
||||
],
|
||||
}
|
||||
result = _format_contexte(contexte)
|
||||
assert "Test inconnu 42" in result
|
||||
assert "[N:" not in result
|
||||
|
||||
def test_prompt_das_includes_bio_norms_rule(self):
|
||||
"""Le prompt DAS contient la règle sur les normes biologiques."""
|
||||
from src.medical.rag_search import _build_prompt_das_extraction
|
||||
|
||||
prompt = _build_prompt_das_extraction(
|
||||
text="Patient avec créatinine normale",
|
||||
contexte={"biologie_cle": [("Créatinine", "76", False)]},
|
||||
existing_das=[],
|
||||
dp_texte="Pancréatite aiguë",
|
||||
)
|
||||
assert "ATTENTION aux valeurs biologiques" in prompt
|
||||
assert "[N: min-max]" in prompt
|
||||
|
||||
def test_bio_normals_exported(self):
|
||||
"""BIO_NORMALS est bien exporté depuis cim10_extractor."""
|
||||
from src.medical.cim10_extractor import BIO_NORMALS
|
||||
|
||||
assert "Créatinine" in BIO_NORMALS
|
||||
assert BIO_NORMALS["Créatinine"] == (50, 120)
|
||||
assert "CRP" in BIO_NORMALS
|
||||
assert BIO_NORMALS["CRP"] == (0, 5)
|
||||
|
||||
|
||||
class TestExtractDasLlmIntegration:
|
||||
"""Tests d'intégration pour le pass LLM DAS dans cim10_extractor.py."""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user