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:
@@ -50,6 +50,7 @@ REFERENTIELS_DIR = BASE_DIR / "data" / "referentiels"
|
||||
UPLOAD_MAX_SIZE_MB = 50
|
||||
ALLOWED_EXTENSIONS = {".pdf", ".csv", ".xlsx", ".xls", ".txt"}
|
||||
CIM10_DICT_PATH = BASE_DIR / "data" / "cim10_dict.json"
|
||||
CIM10_SUPPLEMENTS_PATH = BASE_DIR / "data" / "cim10_supplements.json"
|
||||
CCAM_DICT_PATH = BASE_DIR / "data" / "ccam_dict.json"
|
||||
CIM10_PDF = Path("/home/dom/ai/aivanov_CIM/cim-10-fr_2026_a_usage_pmsi_version_provisoire_111225.pdf")
|
||||
GUIDE_METHODO_PDF = Path("/home/dom/ai/aivanov_CIM/guide_methodo_mco_2026_version_provisoire.pdf")
|
||||
|
||||
@@ -13,7 +13,7 @@ import unicodedata
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
from ..config import CIM10_DICT_PATH, RAG_INDEX_DIR
|
||||
from ..config import CIM10_DICT_PATH, CIM10_SUPPLEMENTS_PATH, RAG_INDEX_DIR
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -90,6 +90,7 @@ def load_dict() -> dict[str, str]:
|
||||
"""Charge le dictionnaire CIM-10 (singleton lazy-loaded).
|
||||
|
||||
Si le fichier JSON n'existe pas, tente de le construire depuis metadata.json.
|
||||
Fusionne ensuite les suppléments (sous-codes manquants) sans écraser le dict principal.
|
||||
"""
|
||||
global _dict_cache
|
||||
if _dict_cache is not None:
|
||||
@@ -102,6 +103,18 @@ def load_dict() -> dict[str, str]:
|
||||
logger.info("Dictionnaire CIM-10 absent, construction depuis metadata.json...")
|
||||
_dict_cache = build_dict()
|
||||
|
||||
# Fusionner les suppléments (ne remplace pas les entrées existantes)
|
||||
if CIM10_SUPPLEMENTS_PATH.exists():
|
||||
with open(CIM10_SUPPLEMENTS_PATH, encoding="utf-8") as f:
|
||||
supplements = json.load(f)
|
||||
added = 0
|
||||
for code, label in supplements.items():
|
||||
if code not in _dict_cache:
|
||||
_dict_cache[code] = label
|
||||
added += 1
|
||||
if added:
|
||||
logger.info("Suppléments CIM-10 : %d codes ajoutés depuis %s", added, CIM10_SUPPLEMENTS_PATH.name)
|
||||
|
||||
return _dict_cache
|
||||
|
||||
|
||||
|
||||
@@ -873,6 +873,23 @@ def _lookup_cim10(text: str) -> str | None:
|
||||
return dict_lookup(text, domain_overrides=CIM10_MAP)
|
||||
|
||||
|
||||
# Plages de référence biologiques (min, max) — utilisées par _is_abnormal()
|
||||
# et exportées pour le formatage du contexte LLM dans rag_search.py
|
||||
BIO_NORMALS: dict[str, tuple[float, float]] = {
|
||||
"Lipasémie": (0, 60),
|
||||
"CRP": (0, 5),
|
||||
"ASAT": (0, 40),
|
||||
"ALAT": (0, 40),
|
||||
"GGT": (0, 60),
|
||||
"PAL": (0, 150),
|
||||
"Bilirubine totale": (0, 17),
|
||||
"Hémoglobine": (12, 17),
|
||||
"Plaquettes": (150, 400),
|
||||
"Leucocytes": (4, 10),
|
||||
"Créatinine": (50, 120),
|
||||
}
|
||||
|
||||
|
||||
def _is_abnormal(test: str, value: str) -> bool | None:
|
||||
"""Détermine si un résultat biologique est anormal."""
|
||||
try:
|
||||
@@ -884,21 +901,7 @@ def _is_abnormal(test: str, value: str) -> bool | None:
|
||||
return True
|
||||
return None
|
||||
|
||||
normals: dict[str, tuple[float, float]] = {
|
||||
"Lipasémie": (0, 60),
|
||||
"CRP": (0, 5),
|
||||
"ASAT": (0, 40),
|
||||
"ALAT": (0, 40),
|
||||
"GGT": (0, 60),
|
||||
"PAL": (0, 150),
|
||||
"Bilirubine totale": (0, 17),
|
||||
"Hémoglobine": (12, 17),
|
||||
"Plaquettes": (150, 400),
|
||||
"Leucocytes": (4, 10),
|
||||
"Créatinine": (50, 120),
|
||||
}
|
||||
|
||||
if test in normals:
|
||||
lo, hi = normals[test]
|
||||
if test in BIO_NORMALS:
|
||||
lo, hi = BIO_NORMALS[test]
|
||||
return val > hi or val < lo
|
||||
return None
|
||||
|
||||
@@ -10,6 +10,7 @@ from ..config import (
|
||||
OLLAMA_CACHE_PATH, OLLAMA_MAX_PARALLEL, OLLAMA_MODEL,
|
||||
)
|
||||
from .cim10_dict import normalize_code, validate_code as cim10_validate
|
||||
from .cim10_extractor import BIO_NORMALS
|
||||
from .ccam_dict import validate_code as ccam_validate
|
||||
from .ollama_client import call_ollama, parse_json_response
|
||||
from .ollama_cache import OllamaCache
|
||||
@@ -166,8 +167,15 @@ def _format_contexte(contexte: dict) -> str:
|
||||
bio_parts = []
|
||||
for b in biologie:
|
||||
test, valeur, anomalie = b if isinstance(b, (list, tuple)) else (b.get("test"), b.get("valeur"), b.get("anomalie"))
|
||||
# Ajouter la plage de référence si connue
|
||||
norme_str = ""
|
||||
if test in BIO_NORMALS:
|
||||
lo, hi = BIO_NORMALS[test]
|
||||
lo_s = int(lo) if lo == int(lo) else lo
|
||||
hi_s = int(hi) if hi == int(hi) else hi
|
||||
norme_str = f" [N: {lo_s}-{hi_s}]"
|
||||
marker = " (\u2191)" if anomalie else ""
|
||||
bio_parts.append(f"{test} {valeur}{marker}")
|
||||
bio_parts.append(f"{test} {valeur}{norme_str}{marker}")
|
||||
lines.append(f"- Biologie : {', '.join(bio_parts)}")
|
||||
|
||||
imagerie = contexte.get("imagerie")
|
||||
@@ -489,6 +497,7 @@ RÈGLES IMPÉRATIVES :
|
||||
- Ne PAS coder les antécédents non pertinents pour le séjour
|
||||
- Privilégie les codes CIM-10 les plus SPÉCIFIQUES (4e ou 5e caractère)
|
||||
- Ne propose que des diagnostics CLAIREMENT mentionnés dans le texte
|
||||
- ATTENTION aux valeurs biologiques : ne code PAS un diagnostic si les valeurs sont dans les normes indiquées entre crochets [N: min-max]. Exemple : Créatinine 76 [N: 50-120] = NORMAL, pas d'insuffisance rénale.
|
||||
|
||||
DIAGNOSTIC PRINCIPAL : {dp_texte or "Non identifié"}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user