feat: méthode TIM experte CPAM + moteur de règles étendu

CPAM — Méthode TIM (mémoire en défense) :
- Réécriture CPAM_ARGUMENTATION avec raisonnement 5 passes TIM
  (contexte admin → motif réel → confrontation bio → hiérarchie → validation défensive)
- _BIO_THRESHOLDS (19 entrées) + _build_bio_confrontation() pour
  confrontation biologie/diagnostic avec seuils chiffrés et verdicts
- _format_response() dual format : nouveau TIM (moyens numérotés, tableau
  bio, codes non défendables, conclusion dispositive) + rétrocompat legacy
- CPAM_ADVERSARIAL mis à jour pour vérifier honnêteté intellectuelle
- Tests adaptés + 12 nouveaux tests (bio confrontation, format TIM)

Moteur de règles :
- Nouvelles règles YAML : demographic, diagnostic_conflicts,
  procedure_diagnosis, temporal, parcours
- Bio extraction FAISS (synonymes vectoriels)
- Veto engine enrichi (citations, Trackare skip, règles démographiques)
- Decision engine : _apply_bio_rules_gen() + matchers analytiques

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
dom
2026-03-04 11:57:07 +01:00
parent 795110d2e6
commit ce7a9650af
19 changed files with 1681 additions and 418 deletions

View File

@@ -49,6 +49,64 @@
{% endif %}
</div>
{# ---- Synthèse Expert (Refonte) ---- #}
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 1rem; margin-top: 1rem; margin-bottom: 1.5rem;">
{# 1. Sécurité & Conformité (Vetos) #}
<div class="card" style="margin:0; border-top: 4px solid #ef4444; background: #fff1f2;">
<h3 style="color:#991b1b; font-size: 1rem; margin-bottom: 0.75rem;">🛡️ Sécurité & Conformité</h3>
<div style="font-size: 0.85rem; color: #7f1d1d;">
{% if dossier.veto_report and dossier.veto_report.issues %}
{% for issue in dossier.veto_report.issues if issue.severity in ['HARD', 'MEDIUM'] %}
<div style="margin-bottom: 0.5rem; border-bottom: 1px solid #fecaca; padding-bottom: 0.25rem;">
<strong>[{{ issue.severity|replace('HARD', 'Bloquant')|replace('MEDIUM', 'À vérifier') }}]</strong> {{ issue.message }}
{% if issue.citation %}<br><em style="font-size:0.75rem; opacity:0.8;">ATIH: {{ issue.citation }}</em>{% endif %}
</div>
{% endfor %}
{% else %}
<div style="color: #059669; font-weight: 600;">✅ Aucune anomalie majeure détectée.</div>
{% endif %}
</div>
</div>
{# 2. Optimisation de la Recette (CMA) #}
<div class="card" style="margin:0; border-top: 4px solid #10b981; background: #ecfdf5;">
<h3 style="color:#065f46; font-size: 1rem; margin-bottom: 0.75rem;">💰 Valorisation (CMA)</h3>
<div style="font-size: 0.85rem; color: #064e3b;">
{% set cma_alerts = [] %}
{% for alerte in dossier.alertes_codage if alerte.startswith('CMA') %}{% set _ = cma_alerts.append(alerte) %}{% endfor %}
{% if cma_alerts %}
<ul style="margin:0; padding-left: 1.2rem;">
{% for alerte in cma_alerts %}
<li style="margin-bottom: 0.25rem;">{{ alerte }}</li>
{% endfor %}
</ul>
{% else %}
<div style="opacity: 0.7;">Aucune comorbidité (CMA) détectée.</div>
{% endif %}
</div>
</div>
{# 3. Audit & Analyse IA (QC) #}
<div class="card" style="margin:0; border-top: 4px solid #3b82f6; background: #eff6ff;">
<h3 style="color:#1e40af; font-size: 1rem; margin-bottom: 0.75rem;">🔍 Audit de l'Expert IA</h3>
<div style="font-size: 0.85rem; color: #1e3a8a;">
{% set qc_alerts = [] %}
{% for alerte in dossier.alertes_codage if alerte.startswith('QC:') %}{% set _ = qc_alerts.append(alerte) %}{% endfor %}
{% if qc_alerts %}
{% for alerte in qc_alerts %}
<div style="margin-bottom: 0.5rem; border-bottom: 1px solid #bfdbfe; padding-bottom: 0.25rem; font-style: italic;">
{{ alerte|replace('QC: ', '') }}
</div>
{% endfor %}
{% else %}
<div style="opacity: 0.7;">Aucune recommandation particulière.</div>
{% endif %}
</div>
</div>
</div>
{# ---- Séjour ---- #}
{% set s = dossier.sejour %}
{% if s.sexe or s.age or s.date_entree or s.date_sortie or s.duree_sejour is not none or s.imc or s.poids or s.taille %}
@@ -301,22 +359,6 @@
</div>
{% endif %}
{# ---- Alertes de codage ---- #}
{% if dossier.alertes_codage %}
<div class="card section" style="border-left:4px solid #f97316;background:#fff7ed;">
<h3 style="color:#c2410c;">Alertes de codage ({{ dossier.alertes_codage|length }})</h3>
<ul style="margin:0;padding-left:1.2rem;">
{% for alerte in dossier.alertes_codage %}
{% if alerte.startswith('NON-CUMUL') %}
<li class="alerte-noncumul" style="font-size:0.85rem;margin-bottom:0.25rem;">{{ alerte }}</li>
{% else %}
<li class="alerte-standard" style="font-size:0.85rem;margin-bottom:0.25rem;">{{ alerte }}</li>
{% endif %}
{% endfor %}
</ul>
</div>
{% endif %}
{# ---- Contestabilité (VetoReport) ---- #}
{% if dossier.veto_report %}
{% set vr = dossier.veto_report %}
@@ -328,36 +370,37 @@
{% set vr_color = '#ef4444' %}
{% endif %}
<div class="card section" style="border-left:4px solid {{ vr_color }};">
<h3>Contestabilité du dossier</h3>
<h3>Contestabilité du dossier (Qualité PMSI)</h3>
<div style="display:flex;align-items:center;gap:1rem;">
{% if vr.verdict == 'PASS' %}
<span class="badge" style="background:#d1fae5;color:#065f46;font-weight:700;">PASS</span>
<span class="badge" style="background:#d1fae5;color:#065f46;font-weight:700;">CONFORME</span>
{% elif vr.verdict == 'NEED_INFO' %}
<span class="badge" style="background:#fef3c7;color:#92400e;font-weight:700;">NEED_INFO</span>
<span class="badge" style="background:#fef3c7;color:#92400e;font-weight:700;">À COMPLÉTER</span>
{% else %}
<span class="badge" style="background:#fee2e2;color:#dc2626;font-weight:700;">FAIL</span>
<span class="badge" style="background:#fee2e2;color:#dc2626;font-weight:700;">NON CONFORME</span>
{% endif %}
<div style="flex:1;height:8px;background:#e2e8f0;border-radius:4px;">
<div style="width:{{ vr.score_contestabilite }}%;height:100%;background:{{ vr_color }};border-radius:4px;"></div>
</div>
<span style="font-weight:600;">{{ vr.score_contestabilite }}/100</span>
<span style="font-weight:600;">Score : {{ vr.score_contestabilite }}/100</span>
</div>
{% if vr.issues %}
<details style="margin-top:0.5rem;">
<summary style="font-size:0.8rem;color:#64748b;">Problèmes détectés ({{ vr.issues|length }})</summary>
<summary style="font-size:0.8rem;color:#64748b;cursor:pointer;">Détail des anomalies détectées ({{ vr.issues|length }})</summary>
<table style="margin-top:0.25rem;">
<thead><tr><th>Veto</th><th>Sévérité</th><th>Localisation</th><th>Message</th></tr></thead>
<thead><tr><th>Code Règle</th><th>Sévérité</th><th>Localisation</th><th>Message d'alerte</th><th>Référence ATIH</th></tr></thead>
<tbody>
{% for issue in vr.issues %}
<tr>
<td><code style="font-size:0.75rem;">{{ issue.veto }}</code></td>
<td>
{% if issue.severity == 'HARD' %}<span class="badge" style="background:#fee2e2;color:#dc2626;">HARD</span>
{% elif issue.severity == 'MEDIUM' %}<span class="badge" style="background:#fef3c7;color:#92400e;">MEDIUM</span>
{% else %}<span class="badge" style="background:#f0fdf4;color:#166534;">LOW</span>{% endif %}
{% if issue.severity == 'HARD' %}<span class="badge" style="background:#fee2e2;color:#dc2626;">Bloquant</span>
{% elif issue.severity == 'MEDIUM' %}<span class="badge" style="background:#fef3c7;color:#92400e;">À vérifier</span>
{% else %}<span class="badge" style="background:#f0fdf4;color:#166534;">Optimisation</span>{% endif %}
</td>
<td style="font-size:0.75rem;color:#64748b;">{{ issue.where }}</td>
<td style="font-size:0.75rem;color:#64748b;">{{ issue.where|human_where }}</td>
<td style="font-size:0.8rem;">{{ issue.message }}</td>
<td style="font-size:0.75rem;color:#475569;font-style:italic;">{{ issue.citation or '—' }}</td>
</tr>
{% endfor %}
</tbody>
@@ -506,7 +549,7 @@
<details style="margin-top:0.3rem;"><summary style="font-size:0.7rem;color:#0369a1;cursor:pointer;">preuves ({{ das.preuves_cliniques|length }})</summary>
<ul style="margin:0.15rem 0 0 0;padding-left:1rem;font-size:0.75rem;">
{% for p in das.preuves_cliniques %}
<li><span style="font-weight:600;color:#0369a1;">[{{ p.type }}]</span> {{ p.element }} <span style="color:#64748b;">&rarr; {{ p.interpretation }}</span></li>
<li><span style="font-weight:600;{% if p.type == 'biologie' %}color:#0891b2;{% else %}color:#0369a1;{% endif %}">[{{ p.type }}]</span> {{ p.element }} <span style="color:#64748b;">&rarr; {{ p.interpretation }}</span></li>
{% endfor %}
</ul>
</details>