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

@@ -150,6 +150,10 @@ def extract_medical_info(
# Post-processing : retirer DAS dont le code est identique au DP
_remove_das_equal_dp(dossier)
# Post-processing : validation justifications (QC batch)
if use_rag:
_validate_justifications(dossier)
# Post-processing : traçabilité source (page + extrait)
if page_tracker:
_apply_source_tracking(dossier, page_tracker, search_text)
@@ -1019,3 +1023,110 @@ def _apply_source_tracking(dossier: DossierMedical, page_tracker, search_text: s
if tracked:
logger.info(" Traçabilité source : %d/%d diagnostics localisés", tracked, len(all_diags))
def _validate_justifications(dossier: DossierMedical) -> None:
"""Validation croisée de tous les diagnostics via un appel LLM unique.
Vérifie la cohérence, les preuves cliniques et la spécificité des codes.
Ajuste la confiance si la justification est faible et ajoute des alertes QC.
"""
try:
from .ollama_client import call_ollama
from .clinical_context import build_enriched_context, format_enriched_context
except ImportError:
logger.warning("Module clinical_context non disponible pour la validation QC")
return
all_diags: list[tuple[str, Diagnostic]] = []
if dossier.diagnostic_principal:
all_diags.append(("DP", dossier.diagnostic_principal))
for das in dossier.diagnostics_associes:
all_diags.append(("DAS", das))
if not all_diags:
return
# Construire le résumé des codes à valider
codes_section = ""
for i, (type_diag, diag) in enumerate(all_diags, 1):
code = diag.cim10_suggestion or "?"
justif = (diag.justification or "")[:150]
preuves = ", ".join(p.element for p in diag.preuves_cliniques[:3]) or "aucune"
codes_section += f"{i}. [{type_diag}] {code}{diag.texte}\n"
codes_section += f" Justification: {justif}\n"
codes_section += f" Preuves: {preuves}\n\n"
ctx = build_enriched_context(dossier)
ctx_str = format_enriched_context(ctx)
prompt = f"""Tu es un médecin DIM contrôleur qualité PMSI.
Vérifie la cohérence et la justification de ce codage complet.
DOSSIER CLINIQUE :
{ctx_str}
CODAGE À VALIDER :
{codes_section}
Pour CHAQUE code, vérifie :
1. Existe-t-il une preuve clinique concrète dans le dossier ?
2. Le code est-il le plus spécifique possible ?
3. Y a-t-il des conflits ou redondances avec d'autres codes ?
Réponds avec un JSON :
{{
"validations": [
{{
"numero": 1,
"code": "X99.9",
"verdict": "maintenir|reclasser|supprimer",
"confidence_recommandee": "high|medium|low",
"commentaire": "explication courte"
}}
],
"alertes_globales": ["..."]
}}"""
try:
result = call_ollama(prompt, temperature=0.1, max_tokens=2500)
except Exception:
logger.warning("Erreur lors de l'appel Ollama pour validation QC", exc_info=True)
return
if result is None:
return
# Appliquer les ajustements
validations = result.get("validations", [])
for v in validations:
if not isinstance(v, dict):
continue
num = v.get("numero")
if not isinstance(num, int) or num < 1 or num > len(all_diags):
continue
type_diag, diag = all_diags[num - 1]
conf = v.get("confidence_recommandee")
verdict = v.get("verdict")
commentaire = v.get("commentaire", "")
if conf in ("high", "medium", "low") and conf != diag.cim10_confidence:
old = diag.cim10_confidence
diag.cim10_confidence = conf
if old and conf != old:
dossier.alertes_codage.append(
f"QC: {type_diag} {diag.cim10_suggestion} confiance {old}\u2192{conf} \u2014 {commentaire}"
)
if verdict == "supprimer" and type_diag == "DAS":
dossier.alertes_codage.append(
f"QC: DAS {diag.cim10_suggestion} ({diag.texte}) à reconsidérer \u2014 {commentaire}"
)
alertes_globales = result.get("alertes_globales", [])
for a in alertes_globales:
if isinstance(a, str) and a.strip():
dossier.alertes_codage.append(f"QC: {a}")
logger.info(" QC batch : %d validations, %d alertes globales",
len(validations), len(alertes_globales))