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:
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user