Files
rpa_vision_v3/docs/handoffs/2026-05-12_audit_complet_decision_t2a.md
Dom 5ea4960e65
Some checks failed
tests / Lint (ruff + black) (push) Successful in 1m50s
tests / Tests unitaires (sans GPU) (push) Failing after 1m50s
tests / Tests sécurité (critique) (push) Has been skipped
backup: snapshot post-démo GHT 2026-05-19
Backup état complet après enregistrement vidéo démo de bout en bout.
À utiliser comme point de référence pour la consolidation post-démo.

Changements majeurs de la session 18-19 mai :
- AIVA-URGENCE : page autonome avec preset URL + auto-focus chain
- Workflow Demo_urgence_3_db : merge linux_db + steps AIVA + pause humaine NoMachine
- Bypass LLM (static_result / static_text) dans replay_engine
  pour démos déterministes sans appel Ollama
- Fix api_stream:3013 — replay_paused au premier polling /next
- dag_execute : lift duration_ms vers top-level pour wait runtime
- NPM bypass auth /aiva-urgence/ via location ^~ (proxy_host/10.conf hors git)
- scripts/cancel-replays.sh — workaround Stop VWB qui ne purge pas la queue

Anchors visuels (468) forcés dans le commit pour garantir restorabilité.
DB workflows actuelle + ~12 .bak DB de la journée incluses.

Sujets identifiés pour consolidation post-démo (TODO) :
1. Bug VWB recapture anchor ne régénère pas le PNG
2. Léa client accumule état mémoire (restart périodique requis)
3. Stop VWB ne purge pas la queue serveur (lien manquant vers /replay/cancel)
4. Bug coord client mss tronqué 2560x60 → mapping Y cassé
5. delay_before/delay_after ignorés au runtime (fix partiel duration_ms)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 14:55:06 +02:00

38 KiB
Raw Blame History

Audit complet — Chaîne décision t2a (démo GHT Sud 95)

Date : 2026-05-12 Branche : feature/qw-suite-mai Workflow audité : Demo_urgence_2 (id wf_d04d2dc7c118_1778493082) Sauvegardes : docs/handoffs/2026-05-12_pre_top_decision_{workflows.db,t2a_decision.py,replay_engine.py} Sources officielles archivées : docs/clients/ght_sud_95/referentiels_officiels/ (12 PDF)


TL;DR — 6 trouvailles capitales

  1. La règle "2/3 critères ⇒ UHCD" est officiellement FAUSSE. Source primaire : Instruction DGOS/R1/DSS/1A/2020/52 Annexe 3 + Guide ATIH MCO 2025 et 2026 §1.2. Vraie règle : conjonction ET (3/3 cumulatifs) appréciée AVANT admission UHCD. La règle 2/3 expose à requalification CPAM.

  2. Aucun seuil de durée opposable en droit français. Le "6h" évoqué (DIM sénior) relève du folklore / Clinical Decision Units anglo-saxonnes. DMS cible < 24h, tolérance jusqu'à 36h/2 nuits (recommandation SFMU, pas opposable).

  3. 🔴 TWIST MOREL Catherine : selon règle officielle, les 3 critères cumulatifs sont remplis ⇒ UHCD légalement justifiable (~375€). La VT actuelle server.py:32 (FORFAIT) est probablement à inverser. Le vrai bug = mode_sortie codé "Consultation externe" au lieu de "Domicile" (mode 8) attendu pour UHCD ⇒ perte de recettes détectable, VRAI ROI démo.

  4. "FF1/FF2/FF3" n'existent PAS dans la nomenclature officielle. Vraie nomenclature : FU0/FU1/FU2/FU3/FU4 (forfaits âge) + suppléments SU2/SU3, PE1/PE2, SUB/SB2/SB3, SIM/SIC, SUN/SUF, SUM, SAS, SSN/SSF.

  5. PE2 ≠ matériel pédiatrique : c'est un supplément clinique pédiatrique (diagnostic CIM-10 de la liste 2 annexe 19), pas un forfait matériel.

  6. 5 causes racines bugs MOREL : règle PMSI mal formulée (1) + non-déterminisme Ollama T=0.1 sans seed (2) + confusion durée_symptômes/passage (3) + pas de verrou cohérence step 17 (4) + listes hospi/forfait non mutuellement exclusives (5).


Rapport 1 — DIM sénior (audit clinique PMSI)

Verdict global

  • PROMPT 1 (t2a_decision) : 5/10. Règle 2/3 fausse, durée sous-spécifiée, critère décisif (mode sortie) manquant, listes hospi/forfait non contraintes.
  • PROMPT 2 (Résumé) : 7/10. Globalement bon, 3 lignes à ajouter.
  • PROMPT 3 (Justification) : 4/10. Verrouillage insuffisant, pas d'auto-vérification, conflit de cardinalité décision/preuves.

Diagnostic MOREL (lecture stricte avant validation référentiel officiel)

  • Critère 1 (pathologie évolutive) : OUI (77 ans, insuf coronarienne, asthme, fièvre 39.4°C, peakflow 260, tachycardie 117)
  • Critère 2 (surveillance >6h) : NON selon DIM sénior (durée 3h37)
  • Critère 3 (actes/examens) : OUI (radio, PCR VRS, bio, aérosols, antibio)
  • DIM sénior tranche FORFAIT car durée < 6h + RAD ⇒ critère 2 invalidé ⇒ pas 2/3.
  • ⚠️ Référentiel officiel postérieur (rapport 4) contredit : le seuil 6h n'est pas opposable, donc critère 2 reste OUI (surveillance hospitalière documentée par aérosols ×3 + ATB initiée + monitoring) ⇒ 3/3 ⇒ UHCD.

Amendements PROMPT 1 proposés par DIM

Ajouter avant la liste des critères :

RÈGLE ÉLIMINATOIRE — DURÉE ET MODE DE SORTIE :
- Si durée_passage < 6h ET mode sortie = "domicile/RAD" ⇒ FORFAIT_URGENCE,
  sauf surveillance médicale continue documentée explicitement.
- Si durée_passage > 24h ⇒ hospitalisation conventionnelle (hors UHCD).
- "durée du passage" = horaire ADMISSION → horaire SORTIE.
  N'EST PAS la durée des symptômes pré-hospitaliers (« douleurs depuis 23h » ≠ durée passage).

⚠️ Cette règle éliminatoire ne doit PAS être adoptée telle quelle car le seuil 6h n'a aucune base réglementaire. À retenir uniquement : la distinction durée_symptômes / durée_passage, et l'importance du mode de sortie.

Amendements PROMPT 1 — ajouts dans INSTRUCTIONS STRICTES

- `elements_pour_hospitalisation` = éléments qui PROLONGENT le séjour ou imposent surveillance hospitalière
  (ex: « scope continu », « réévaluation H+2 et H+4 », « transfert UHCD demandé »)
- `elements_pour_forfait` = éléments qui RACCOURCISSENT ou ORIENTENT vers la sortie
  (ex: « RAD à H+3:30 », « constantes stabilisées », « sortie domicile avec ordonnance »)
- Une « sortie en consultation externe à 48h » est un argument POUR le forfait, jamais pour l'hospitalisation.
- decision_court DOIT être strictement couplé à decision (jamais "UHCD" + FORFAIT).

Amendements PROMPT 2 — 3 lignes à ajouter

11. Mentionne explicitement la durée du passage aux urgences en heures et minutes (admission → sortie),
    distincte de la durée des symptômes rapportés par le patient.
12. Cite explicitement le mode de sortie (retour à domicile, UHCD, mutation MCO, transfert) si présent au dossier.
13. Liste les actes techniques et examens complémentaires effectués (biologie, imagerie, gestes CCAM).

Amendements PROMPT 3 — verrouillage anti-divergence

RÈGLES STRICTES :
1. La décision EST {{dec.decision}} ({{dec.decision_court}}). Tu ne la discutes pas, tu ne la nuances pas,
   tu ne la contredis pas, même implicitement. Toute formulation suggérant la décision opposée est interdite.
2. Source unique : RESUME_CLINIQUE et PREUVES. N'invente aucun argument.
3. Rédige 4 à 7 phrases.
4. Structure imposée :
   a) Phrase 1-2 : éléments en faveur de la décision (citer le résumé).
   b) Phrase 3-4 : éléments contradictoires, EXPLICITEMENT minorés.
   c) Phrase finale OBLIGATOIRE : "En conclusion, ce passage relève de {{dec.decision_court}} au regard de
      [résumé en une phrase du critère décisif]."
5. Si FORFAIT_URGENCE : phrase finale doit contenir "forfait" et NE PAS contenir "UHCD"/"requalification"/"hospitalisation"
   autrement qu'en négation explicite.
6. Si REQUALIFICATION_HOSPITALISATION : inversement.
7. Avant de répondre, relis ta dernière phrase et vérifie qu'elle est cohérente avec la décision imposée.

Cas PE2/SU2 (4 dossiers démo)

Pipeline actuel ne les gère pas. Recommandation : branche déterministe CCAM en amont OU exclusion du scope démo.


Rapport 2 — Ingénieur prompt (robustesse + verrou)

Cause racine oscillation MOREL (4 runs / 2 décisions)

  • T=0.1 + pas de seed + format=json + bruit OCR variable cross-runs
  • Combo nécessaire pour déterminisme : temperature=0, seed=42, top_p=1.0, top_k=0
  • Geler dpi_canonique en variable de session (SQLite replay_state.variables) pour tuer la variance OCR.

Cause racine contradiction step 3 ("justifie l'UHCD" alors decision=FORFAIT)

Combinaison de :

  • A (conflit prompt vs context) : secondaire
  • B (séparation prompt/context dans LLMActionHandler.generate_text) : significative
  • C (pas de clause de refus / loi de cardinalité contextuelle) : cause structurelle

Le LLM, dressé à produire un texte cohérent, doit émettre quelque chose. Si les preuves disent "hospitalisation" et la décision dit "forfait", il rationalise vers la masse de signaux (les preuves gagnent).

PROMPT 1 réécrit complet (à coller dans core/llm/t2a_decision.py)

Tu es médecin DIM senior, expert PMSI/T2A urgences hospitalières en France. Tu produis une décision de
facturation auditable et reproductible.

# RÈGLES PMSI URGENCES

Deux décisions possibles, mutuellement exclusives :
- FORFAIT_URGENCE → libellé court "Forfait Urgences" → passage simple, sortie domicile, pas de soins continus.
- REQUALIFICATION_HOSPITALISATION → libellé court "UHCD" → séjour MCO requis.

Trois critères UHCD (ATIH) :
C1. Pathologie potentiellement évolutive (instabilité hémodynamique, terrain à risque, traitement nécessitant
    adaptation).
C2. Surveillance médicale ou paramédicale prolongée (constantes itératives, observations IDE/médecin
    répétées, durée de surveillance > 6 h).
C3. Examens complémentaires ou actes thérapeutiques significatifs (biologie, imagerie, sutures,
    gestes techniques).

RÈGLE DE DÉCISION (algorithme, à appliquer LITTÉRALEMENT, dans cet ordre) :
1. Évalue C1, C2, C3 indépendamment à partir des citations littérales du DPI.
2. Compte N = nombre de critères validés à true.
3. Si N >= 2 → decision = "REQUALIFICATION_HOSPITALISATION", decision_court = "UHCD".
4. Sinon → decision = "FORFAIT_URGENCE", decision_court = "Forfait Urgences".
5. decision_court DOIT être strictement couplé à decision.

# DURÉE DU PASSAGE

duree_passage_heures = (heure de sortie OU heure de transfert)  (heure d'admission OU heure d'arrivée).
- Cherche EXACTEMENT les champs d'admission/sortie du dossier (libellés "Admission", "Arrivée", "Entrée",
  "Sortie", "Transfert", "Sortie effective").
- N'utilise JAMAIS l'heure d'un examen, d'une biologie, ou d'un soin comme borne.
- Si une borne manque → duree_passage_heures = null (NE PAS deviner).
- Ne confonds JAMAIS la durée des symptômes (anamnèse) avec la durée du séjour.

# CITATIONS

Chaque preuve_critereN doit contenir AU MOINS UNE citation littérale entre « » d'un fragment du DPI.
Pas de paraphrase silencieuse. Si critère non validé, cite ce qui MANQUE (ex: « Sortie à H+2 »).

# LISTES MUTUELLEMENT EXCLUSIVES

elements_pour_hospitalisation et elements_pour_forfait sont mutuellement exclusives. Un même fait ne peut
pas apparaître dans les deux. Un fait ne peut être dans elements_pour_hospitalisation QUE s'il argumente
pour REQUALIFICATION (ex: « surveillance scope 12 h », « bilan biologique répété »). Un fait orientant vers
la sortie (« retour domicile », « sortie consultation externe à 48 h ») doit aller dans elements_pour_forfait.

# ANTI-HALLUCINATION

Si une information manque, NE LA FABRIQUE PAS. Préfère null, [] ou "NON_RENSEIGNE". Toute valeur numérique
non présente dans le dossier est interdite.

# EXEMPLE 1 — FORFAIT (passage court, retour domicile)

DPI (extrait) : « Admission 14h12, motif: entorse cheville droite. Examen: œdème modéré, pas de douleur
osseuse. Rx: pas de fracture. Bandage. Sortie 16h05 retour domicile. »

Sortie attendue :
{"duree_passage_heures": 1.9, "elements_pour_hospitalisation": [],
 "elements_pour_forfait": ["Sortie 16h05 retour domicile", "Rx: pas de fracture", "Bandage"],
 "preuve_critere1": "Motif « entorse cheville droite », pas de terrain à risque cité. Pathologie bénigne
 non évolutive. Critère non validé.", "critere1_valide": false,
 "preuve_critere2": "Surveillance courte : « Admission 14h12 » → « Sortie 16h05 », durée 1.9 h. Pas
 d'observation IDE itérative. Critère non validé.", "critere2_valide": false,
 "preuve_critere3": "Un seul acte : « Rx: pas de fracture » et « Bandage ». Acte technique mineur isolé.
 Critère non validé.", "critere3_valide": false,
 "decision": "FORFAIT_URGENCE", "decision_court": "Forfait Urgences",
 "justification": "Passage court 1.9 h avec « Sortie 16h05 retour domicile ». Aucun des 3 critères UHCD
 validé. Forfait urgences applicable.", "confiance": "elevee"}

# EXEMPLE 2 — REQUALIFICATION (3 critères validés)

DPI (extrait) : « Admission 22h40, patient 78 ans, ATCD insuffisance cardiaque. Dyspnée stade IV. FC 124,
TA 95/58. ECG: AC/FA rapide. NT-proBNP 4800. Scope, O2 4L. Surveillance horaire. Transfert USIC 09h15. »

Sortie attendue :
{"duree_passage_heures": 10.6,
 "elements_pour_hospitalisation": ["ATCD insuffisance cardiaque", "Dyspnée stade IV", "FC 124, TA 95/58",
 "AC/FA rapide", "Scope, O2 4L", "Surveillance horaire", "Transfert USIC 09h15"],
 "elements_pour_forfait": [],
 "preuve_critere1": "Terrain à risque : « patient 78 ans, ATCD insuffisance cardiaque ». Instabilité :
 « FC 124, TA 95/58 » et « AC/FA rapide ». Critère validé.", "critere1_valide": true,
 "preuve_critere2": "Durée 10.6 h (« Admission 22h40 » → « Transfert USIC 09h15 ») avec « Scope, O2 4L »
 et « Surveillance horaire ». Critère validé.", "critere2_valide": true,
 "preuve_critere3": "Bilan : « ECG: AC/FA rapide », « NT-proBNP 4800 ». Examens significatifs orientant le
 diagnostic. Critère validé.", "critere3_valide": true,
 "decision": "REQUALIFICATION_HOSPITALISATION", "decision_court": "UHCD",
 "justification": "3 critères UHCD validés. Terrain « 78 ans, ATCD insuffisance cardiaque » avec
 décompensation aiguë, surveillance prolongée 10.6 h et transfert USIC. Séjour MCO justifié.",
 "confiance": "elevee"}

# FORMAT DE SORTIE (JSON STRICT, ordre des clés OBLIGATOIRE — preuves AVANT décision)

{
  "duree_passage_heures": <number|null>,
  "preuve_critere1": "<2-3 phrases avec citation « ... »>",
  "critere1_valide": <true|false>,
  "preuve_critere2": "<2-3 phrases avec citation « ... »>",
  "critere2_valide": <true|false>,
  "preuve_critere3": "<2-3 phrases avec citation « ... »>",
  "critere3_valide": <true|false>,
  "elements_pour_hospitalisation": [<phrases littérales>],
  "elements_pour_forfait": [<phrases littérales>],
  "decision": "FORFAIT_URGENCE" | "REQUALIFICATION_HOSPITALISATION",
  "decision_court": "UHCD" | "Forfait Urgences",
  "justification": "<2-3 phrases avec au moins une citation>",
  "confiance": "elevee" | "moyenne" | "faible"
}

# DOSSIER PATIENT

{dpi}

⚠️ À ré-arbitrer avec rapport 4 : la règle "N >= 2 ⇒ UHCD" de ce PROMPT 1 est cliniquement fausse selon les sources officielles. À remplacer par la règle cumulative ET (texte Section F du rapport 4).

PROMPT 2 réécrit (DB workflow step 14)

Tu es médecin urgentiste senior. Tu produis un résumé clinique fidèle, propre et auditable du dossier
ci-dessous.

Tu ne prends AUCUNE décision de facturation. Tu ne produis AUCUN JSON. Tu ne fais AUCUNE déduction
diagnostique non explicitement présente dans le dossier.

RÈGLES STRICTES :
1. Source unique : le dossier ci-dessous. Tout fait non présent est interdit.
2. Bruit OCR : ignore menus, libellés d'interface, fragments isolés, doublons, lignes incohérentes.
3. Déduplication : si la même information apparaît plusieurs fois sous des formes proches, garde la plus
   précise et lisible.
4. Conflit OCR : si deux passages donnent des valeurs incompatibles pour le même fait (ex: FC 110 vs FC 010),
   écris la valeur la plus plausible suivie de "(à confirmer)".
5. Horaires : reproduis LITTÉRALEMENT les heures d'admission, sortie ou transfert (format HHhMM).
   Ne paraphrase JAMAIS un horaire.
6. Constantes : reproduis les valeurs numériques telles quelles avec leur unité (FC en bpm, TA en mmHg,
   SpO2 en %, T° en °C).
7. Style : français médical clair, fluide, sans puces, sans titres, sans JSON.
8. Longueur : 8 à 14 phrases.
9. Ordre clinique : motif d'admission → contexte/ATCD → examen et constantes → examens complémentaires →
   traitements/gestes → évolution et orientation.
10. Si une section est absente, ne la mentionne pas.
11. Commence par le motif d'admission. Termine par l'orientation (sortie domicile, hospitalisation, transfert).

DOSSIER :
{context}

PROMPT 3 réécrit (DB workflow step 17)

Tu es médecin DIM. Tu rédiges UNE justification PMSI courte qui défend une décision déjà prise. Tu n'as
PAS le droit de la changer.

<DECISION_IMPOSEE>
decision = {{dec.decision}}
decision_court = {{dec.decision_court}}
</DECISION_IMPOSEE>

<RESUME_CLINIQUE>
{{resume_patient}}
</RESUME_CLINIQUE>

<PREUVES>
C1 : {{dec.preuve_critere1}}
C2 : {{dec.preuve_critere2}}
C3 : {{dec.preuve_critere3}}
</PREUVES>

RÈGLES STRICTES :
1. Ta sortie DOIT défendre la décision = {{dec.decision}} et se terminer par une phrase finale citant
   explicitement « {{dec.decision_court}} ».
2. Source unique : RESUME_CLINIQUE et PREUVES. N'invente AUCUN argument.
3. Pas de JSON, pas de liste à puces, pas de titre. Texte fluide, 4 à 7 phrases.
4. Structure :
   - Phrase 1-2 : éléments en faveur de {{dec.decision}} (citations courtes).
   - Phrase 3-4 : éléments contradictoires ou limitants, traités factuellement.
   - Phrase finale : conclusion citant exactement « {{dec.decision_court}} ».
5. Si FORFAIT_URGENCE : explique pourquoi UHCD/hospitalisation n'est pas requise.
6. Si REQUALIFICATION_HOSPITALISATION : explique pourquoi le passage dépasse un simple forfait.

VERROU DE COHÉRENCE — OBLIGATOIRE :
Avant d'émettre, vérifie que ta dernière phrase contient littéralement « {{dec.decision_court}} » et défend
la décision imposée. Si tu détectes que les preuves contredisent gravement la décision imposée au point
que tu ne peux PAS rédiger une justification honnête, tu DOIS répondre EXACTEMENT et SEULEMENT cette ligne :

INCOHERENCE_DETECTEE: la décision {{dec.decision}} ne correspond pas aux preuves fournies, validation
humaine requise.

Sinon, rédige la justification. Aucun autre format de sortie n'est accepté.

Paramètres Ollama recommandés

PROMPT 1 (déterminisme maximal) :

temperature: 0
top_p: 1.0
top_k: 0
repeat_penalty: 1.0
seed: 42
num_predict: 2000
num_ctx: 16384
keep_alive: "30m"

PROMPT 2 (un peu de fluidité) :

temperature: 0.2
top_p: 0.9
top_k: 40
seed: 42
num_predict: 800

PROMPT 3 (texte démonstratif court) :

temperature: 0.1
top_p: 0.9
top_k: 40
seed: 42
num_predict: 500

Garde-fous serveur (à appliquer dans _handle_t2a_decision_action, replay_engine.py:1204)

# Après result = analyze_dpi(...) :

# 1. Validation schéma minimal
required = {"decision", "decision_court", "critere1_valide", "critere2_valide",
            "critere3_valide", "duree_passage_heures"}
if not required.issubset(result.keys()):
    result["_schema_error"] = sorted(required - result.keys())

# 2. Coercion decision_court ↔ decision (single source of truth = decision)
MAP = {"FORFAIT_URGENCE": "Forfait Urgences",
       "REQUALIFICATION_HOSPITALISATION": "UHCD"}
if result.get("decision") in MAP:
    expected = MAP[result["decision"]]
    if result.get("decision_court") != expected:
        result["_coerced_decision_court"] = result.get("decision_court")
        result["decision_court"] = expected

# 3. Règle 2/3 — recompute decision depuis les flags (À ADAPTER selon règle officielle ATIH)
n_valid = sum(bool(result.get(f"critere{i}_valide")) for i in (1, 2, 3))
expected_decision = ("REQUALIFICATION_HOSPITALISATION" if n_valid >= 3
                     else "FORFAIT_URGENCE")  # ⚠️ Changé en 3/3 selon rapport 4
if result.get("decision") != expected_decision:
    result["_rule_violation"] = {
        "n_critere_valides": n_valid,
        "decision_llm": result.get("decision"),
        "decision_attendue": expected_decision,
    }
    result["decision"] = expected_decision
    result["decision_court"] = MAP[expected_decision]

# 4. Détection contradiction justification ↔ decision (regex)
import re
justif = (result.get("justification") or "").lower()
if result["decision"] == "FORFAIT_URGENCE" and re.search(
        r"\b(requalification|uhcd|hospitalis)", justif):
    result["_justif_contradiction"] = True
if result["decision"] == "REQUALIFICATION_HOSPITALISATION" and re.search(
        r"\bforfait\b", justif) and not re.search(r"dépasse|au[- ]delà", justif):
    result["_justif_contradiction"] = True

# 5. Sanity check durée
duree = result.get("duree_passage_heures")
if duree is not None and (duree < 0 or duree > 72):
    result["_duree_suspect"] = duree

# 6. Retry automatique 1 fois si contradiction grave
contradictions = any(k.startswith("_") and k not in ("_elapsed_s", "_model", "_eval_count")
                     for k in result)
if contradictions and not result.get("_retry"):
    result_retry = analyze_dpi(dpi_text, model=..., seed=43)
    result_retry["_retry"] = True
    if not any(k.startswith("_rule_violation") for k in result_retry):
        result = result_retry

Coherence check optionnel pour step 17 (dans _handle_llm_generate_action)

if action.parameters.get("coherence_check") == True:
    imposed = (params.get("imposed_decision_court") or "").strip()  # ex: "UHCD"
    if imposed and imposed.lower() not in generated.lower():
        generated_retry = handler.generate_text(prompt=prompt, context=context, ...)
        if imposed.lower() in generated_retry.lower():
            generated = generated_retry
        else:
            generated = (f"Décision retenue : {imposed}. "
                         f"Voir critères PMSI dans le détail technique. "
                         f"Justification automatique non concluante — révision humaine recommandée.")

3 actions prioritaires (effort/impact)

  1. (15 min) Fixer temperature=0, seed=42, top_p=1 dans t2a_decision.py → tue l'oscillation.
  2. (30 min) Geler dpi_canonique en variable de session après première extraction.
  3. (1 h) Remplacer PROMPT 1 + ajouter garde-fous serveur → tue les contradictions structurelles.

Rapport 3 — Analyste données (chronologie + dates)

Cohérence des 11 dossiers

  • Âges ↔ dates de naissance ↔ date passage : OK pour tous les 11. Aucun off-by-one anniversaire.
  • Horaires arrivée→sortie : toutes durées positives, cohérentes avec recap_rpu.

Incohérences trouvées dans data.js

# Dossier Ligne Problème Risque
a MOREL 25003284 228 « Terrain à risque : 78 ans » alors qu'âge = 77 ans LLM peut halluciner âge
b MOREL 43, 178, 201 « depuis 23h » répété 3× Piège durée (hallucination 23h au lieu de 3h37)
c PERRIN 25003475 1519-1524, 1549 GEMSA 4 (hospitalisé) vs decision "RAD" vs us_destination "UC CONSULT.URGENCES" vs recap codage "hospitalisée UHCD" Codage admin incohérent
d DA SILVA 25151530 1064 vs 1184 arrivee 03:25 vs episode_heure 03:00 (Δ 25 min) 2 horaires admission différents pour LLM
e PIRES 25056615 923, 940, 988, 1013, 1031 Statut "Transfert" + « depuis 1 semaine » répété Risque conversion 168h en durée_passage
f LEROY 25003364 1253, 1358, 1360 « depuis 15 jours » répété + episode 13:55 vs orientation 14:47 Risque 360h en durée_passage
g BRUNEL 25012257 1784, 1788, 1841, 1896 « >48h », « >72h », « >24h », « depuis 10/01 », « depuis 30/09/2024 » Densité maximale de marqueurs temporels
h NGUYEN 25048485 788 Note IDE « vu la nuit » sans heure Information temporelle floue
i MARTINS 25005866 1648-1651 Note bilingue anglais + « IRM il y a 2 semaines » + « amnésie de plusieurs jours » Confusion durées possible
j ORDRE_DOSSIERS 2025 Commentaire « 25003284 — UHCD asthme » désynchronisé avec VT actuelle FORFAIT (server.py:34) Confusion si lecture à l'écran

Solution recommandée — préprocesseur Python build_dpi_enriched

Approche HYBRIDE : Python calcule les valeurs critiques (durée, âge), les injecte en tête du DPI dans un bloc FAITS_CALCULÉS, le LLM garde la liberté d'argumenter sur les preuves cliniques.

import re
from datetime import datetime

DATE_RE = re.compile(r"(\d{2}/\d{2}/\d{4})\s+(\d{2}:\d{2})")
ARR_RE  = re.compile(r"Arriv[ée]e\s*[:\-]?\s*(\d{2}/\d{2}/\d{4})\s+(\d{2}:\d{2})", re.I)
SOR_RE  = re.compile(r"Sortie\s*[:\-]?\s*(\d{2}/\d{2}/\d{4})\s+(\d{2}:\d{2})", re.I)
PEC_RE  = re.compile(r"prise en charge.*?(\d{2}/\d{2}/\d{4})\s+(\d{2}:\d{2})", re.I|re.S)
DEC_RE  = re.compile(r"d[ée]cision.*?(\d{2}/\d{2}/\d{4})\s+(\d{2}:\d{2})", re.I|re.S)
NE_RE   = re.compile(r"N[ée]\(?e?\)?\s+le\s+(\d{2}/\d{2}/\d{4})", re.I)

def parse_dt(d, h):
    return datetime.strptime(f"{d} {h}", "%d/%m/%Y %H:%M")

def calc_facts(dpi: str) -> dict:
    arr = ARR_RE.search(dpi) or PEC_RE.search(dpi)
    sor = SOR_RE.search(dpi) or DEC_RE.search(dpi)
    ne  = NE_RE.search(dpi)
    out = {}
    if arr and sor:
        a = parse_dt(*arr.groups()); s = parse_dt(*sor.groups())
        if s < a:
            return {"_warn_negative": True}
        dur_h = round((s - a).total_seconds()/3600, 2)
        out["arrivee"] = a.strftime("%d/%m/%Y %H:%M")
        out["sortie"]  = s.strftime("%d/%m/%Y %H:%M")
        out["duree_passage_heures"] = dur_h
    if ne and arr:
        n = datetime.strptime(ne.group(1), "%d/%m/%Y")
        a = parse_dt(*arr.groups())
        age = a.year - n.year - ((a.month, a.day) < (n.month, n.day))
        out["age_au_passage_ans"] = age
    return out

def build_dpi_enriched(dpi_raw: str) -> str:
    facts = calc_facts(dpi_raw)
    if not facts:
        return dpi_raw  # fallback : LLM calcule lui-même
    bloc = "===== FAITS CALCULÉS (déterministes — à utiliser tels quels) =====\n"
    if "duree_passage_heures" in facts:
        bloc += f"DUREE_PASSAGE_HEURES = {facts['duree_passage_heures']}\n"
        bloc += f"ADMISSION = {facts['arrivee']}\n"
        bloc += f"SORTIE    = {facts['sortie']}\n"
    if "age_au_passage_ans" in facts:
        bloc += f"AGE_AU_PASSAGE = {facts['age_au_passage_ans']} ans\n"
    bloc += "Note : ces valeurs prévalent sur toute durée de symptômes mentionnée dans le texte clinique.\n"
    bloc += "=====================================================\n\n"
    return bloc + dpi_raw

Et ajouter à PROMPT_TEMPLATE :

Si un bloc FAITS_CALCULÉS est présent en tête du dossier, utilise EXACTEMENT DUREE_PASSAGE_HEURES pour le champ duree_passage_heures. NE PAS confondre avec les durées de symptômes (« depuis 23h », « depuis 15 jours »).

Bloc canonique à injecter (exemple MOREL)

===== FAITS CALCULÉS =====
ADMISSION              = 01/01/2025 03:12
SORTIE                 = 01/01/2025 06:49
DUREE_PASSAGE_HEURES   = 3.62
AGE_AU_PASSAGE         = 77 ans
SEXE                   = F
CCMU                   = 3
GEMSA                  = 2
DIAGNOSTIC_PRINCIPAL   = J12.1 Pneumopathie VRS [CMA2]
DECISION_ADMINISTRATIVE = Consultation externe (UC CONSULT.URGENCES)
==========================

Ranking risque de bug LLM

Top 3 risqués :

  1. 25003284 MOREL — « depuis 23h » × 3, bandeau Arrivée/Sortie hors zone OCR
  2. 25003364 LEROY — « depuis 15 jours » × 3, terrain lourd SLA+BPCO, durée passage 7h35 limite
  3. 25012257 BRUNEL — multiples « >48h », « >72h », « >24h », allergie « il y a 30 ans »

Top 3 safe :

  1. 25003451 ROUX — 2h00, plaie nette, récit linéaire
  2. 25010621 FAURE — 2h49, dates absolues
  3. 25048485 NGUYEN — 6h50, crise convulsive datée

Corrections data.js recommandées

  • Ligne 228 : "78 ans" → "77 ans" (MOREL)
  • Ligne 1519 : PERRIN GEMSA 4 → 2, OU us_destination → UHCD (cohérence VT)
  • Ligne 1184 : DA SILVA episode_heure 03:0003:25
  • Ligne 2025 : commentaire ORDRE_DOSSIERS aligné avec VT actuelle

Rapport 4 — Référentiel officiel PMSI 2026 (sources web)

Verdict liminaire — règle officielle

Le DIM senior avait raison sur la conjonction ET. La règle 2/3 est officiellement fausse.

Source primaire (citation textuelle Instruction DGOS/R1/DSS/1A/2020/52 Annexe 3) :

« Ces trois conditions présentent un caractère cumulatif et s'apprécient avant l'admission du patient en UHCD. Dès que l'une d'entre elles n'est pas remplie, la prise en charge ne donne pas lieu à facturation d'un GHS mais à celle d'un forfait "accueil et traitement des urgences" (ATU). »

Guide ATIH MCO 2025 et 2026 (chap. I §1.2, identique entre les deux versions) :

« Lorsque l'une de ces conditions n'est pas remplie, il ne doit pas être produit de RUM. »

Arbre de décision officiel

PASSAGE URGENCES
       |
[Suivi d'une hospitalisation MCO de la même entité géographique ?]
   /                       \
 OUI                       NON
  |                         |
GHS du séjour MCO   [Les 3 critères UHCD sont-ils CUMULATIVEMENT remplis,
(avec quote-part     appréciés AVANT l'admission en UHCD ?]
UHCD si passage         /                                  \
UHCD initial)       OUI (3/3)                      NON (0, 1 ou 2 sur 3)
                       |                                    |
            (1) Pathologie potentiellement       FORFAIT URGENCES :
                évolutive susceptible            - FU (forfait âge) à l'AM
                d'aggravation OU diagnostic       + FPU (côté patient, 23€)
                incertain                         + suppléments éventuels
            (2) ET surveillance médicale          (SU2/SU3, SUB/SB2/SB3,
                + environnement                   SIM/SIC, SUN/SUF, SUM,
                paramédical délivrables           SAS, PE1/PE2)
                uniquement en hospitalisation     + actes/consultations
            (3) ET examens complémentaires        en sus
                ou actes thérapeutiques
                       |
            GHS mono-RUM UHCD
            (date entrée = date sortie ;
             niveau de sévérité le plus
             bas de la racine GHM ;
             tarif unique quelle que soit
             la durée)

Précisions critiques

  • Pas de seuil 6h opposable : aucune source primaire (ATIH MCO 2025/2026, DGOS 2020, arrêté 19/02/2015) n'impose une durée plancher. Le seuil 6h relève du folklore DIM ou Clinical Decision Units anglo-saxonnes.
  • Plafond pratique : DMS < 24h recommandée (SFMU 2024), tolérance ≤ 36h ou 2 nuits consécutives.
  • Critère 3 ne renvoie pas exclusivement à des actes CCAM : pas de critère répétitif (Annexe 3 DGOS 2020).
  • Mode de sortie domicile + 3 critères remplis = GHS UHCD légal. Ce sont les 3 conditions cliniques d'admission qui priment, pas le mode de sortie. MAIS : mode_sortie codé "Consultation externe" (mode ≠ 8) = drapeau rouge contrôle CPAM.

Verdict cas MOREL Catherine (selon règle officielle)

Critère État Validé
(1) Pathologie évolutive ou diagnostic incertain Pneumopathie VRS fébrile (39.4°C) chez 77 ans, asthme + insuf coronarienne, peakflow effondré OUI
(2) Surveillance + environnement paramédical hospitalier Aérosols ×3, monitoring tachycardie 117→91, antibio initiée, surveillance peakflow OUI
(3) Examens complémentaires ou actes thérapeutiques RT, NFS, CRP, PCR VRS, aérosols répétés, ATB OUI

3/3 cumulatifs ⇒ GHS UHCD légalement justifiable (racine 04M Pneumopathies), indépendamment de la durée 3h37.

Points de vigilance MOREL pour défense contrôle CPAM

  1. Durée 3h37 anormalement courte — signal d'alerte habituel ; dossier doit documenter explicitement le caractère évolutif AVANT admission UHCD.
  2. Mode de sortie "Consultation externe" = drapeau rouge. Code mode 8 (domicile) attendu pour UHCD.
  3. CCMU 3 + GEMSA 2 = cohérent UHCD.
  4. DP J12.1 (CMA2) = pertinent, supporte GHS.

Conclusion MOREL

Le passage relève d'une UHCD facturable en GHS (et non d'un forfait pur), à condition que le dossier documente les 3 critères AVANT admission UHCD et que le mode de sortie soit harmonisé. La VT actuelle server.py:32 (FORFAIT) est donc à réinterroger.

Nomenclature officielle (post-réforme 2024-2026)

Forfaits âge (un seul par passage) :

Code Description Conditions
FU0 Nouveau-né < 4 mois (créé arrêté 29/02/2024)
FU1 Nourrisson/enfant 4 mois à < 16 ans
FU2 Adulte jeune 16 à < 45 ans
FU3 Adulte mûr 45 à < 75 ans
FU4 Personne âgée ≥ 75 ans

FPU (côté patient) :

  • 23€ depuis 1er mars 2026 (vs 19,61€ avant)
  • 9,96€ minoré (ALD, AT-MP)

Suppléments cliniques :

Code Description Conditions
SU2 CCMU 2+ État CCMU 2 + acte CCAM de la liste annexe 18
SU3 CCMU 3,4,5 État CCMU 3/4/5 sans hospitalisation
PE1 Pédiatrique Patient FU0/FU1 + diagnostic CIM-10 liste 1 annexe 19 (~800 codes)
PE2 Pédiatrique + Patient FU0/FU1 + diagnostic CIM-10 liste 2 annexe 19 (sous-liste lourde)

Autres suppléments :

  • SUB/SB2/SB3 (biologie), SIM/SIC (imagerie), SUN (nuit 22-8h), SUF (soirée/férié/dimanche)
  • SUM (transport médicalisé), SAS (avis spécialisé), SSN/SSF (avis spé nuit/férié)

Règles de cumul :

  • Aucun supplément sans FU
  • Un seul FU par passage
  • SU2 / SU3 non cumulables entre eux
  • Un seul supplément biologie, un seul supplément imagerie
  • SUN / SUF non cumulables
  • PE1/PE2 cumulabilité : zone grise à clarifier avec DIM cible

"FF1/FF2/FF3" — N'EXISTENT PAS

Codes absents de la nomenclature officielle 2024-2026. Probablement dénomination interne Easily ou GHT Sud 95. À clarifier impérativement avant démo.

Texte 200 mots prêt à coller dans PROMPT 1 (Section F du rapport)

Tu évalues si un passage aux urgences relève d'un GHS UHCD ou d'un Forfait Urgences (FU+FPU). Applique cette règle officielle ATIH/DGOS 2026 (Instruction DGOS/R1/DSS/1A/2020/52 Annexe 3 + Guide ATIH MCO 2025/2026 §1.2 + arrêté 19/02/2015 art.12) :

GHS UHCD facturable UNIQUEMENT si TOUTES les conditions suivantes sont cumulativement remplies, appréciées AVANT admission en UHCD :

  1. État pathologique potentiellement évolutif et susceptible d'aggravation, OU diagnostic incertain ;
  2. ET surveillance médicale et environnement paramédical délivrables uniquement en hospitalisation ;
  3. ET réalisation d'examens complémentaires ou d'actes thérapeutiques.

Si une seule condition manque → facturation forfait urgences (FU0-FU4 selon âge + FPU patient

  • suppléments éventuels : SU2/SU3, PE1/PE2, SUB/SB2/SB3, SIM/SIC, SUN/SUF, SUM, SAS), pas de GHS.

NE PAS appliquer la règle "2 sur 3" : elle est non conforme et expose à requalification CPAM.

Exception : l'administration d'un produit de la réserve hospitalière déclenche un GHS UHCD même hors critères ci-dessus.

Aucune durée minimum n'est réglementairement opposable, mais une DMS < 24h est attendue ; au-delà de 36h ou 2 nuits, escalader vers un humain. Le passage en UHCD suivi d'une mutation MCO de même entité géographique ne donne lieu qu'à UN SEUL GHS du séjour complet.

Sources primaires (12 PDF archivés dans docs/clients/ght_sud_95/referentiels_officiels/)

Fichier Source URL d'origine
solimed_instruction.pdf Instruction DGOS/R1/DSS/1A/2020/52 du 10/09/2020 (intégrale) solimed.fr
apm_instruction.pdf Idem (copie alternative) apm
instruction_gradation_2020.pdf Idem gouv
instruction_gradation_bo.pdf Idem (BO) gouv
atih_mco_2025.pdf Guide méthodologique ATIH MCO 2025 atih.sante.fr
atih_mco_2026.pdf Guide méthodologique ATIH MCO 2026 atih.sante.fr
sfmu_uhcd_2024.pdf Guide bonnes pratiques UHCD 2024 — SFMU sfmu.org
sfmu_ref_uhcd.pdf Référentiel SFMU UHCD sfmu.org
dhumu_uhcd.pdf Critères admission UCSU 2024 dhumu.fr
oru_pdl_uhcd.pdf ORU Pays de Loire UHCD oru-pdl.org
fhpmco_2023.pdf FHP-MCO Financement médecine d'urgence n°17 (mai 2023) fhpmco.fr

URLs Légifrance non archivées en PDF (à consulter en ligne) :

  • Arrêté du 19/02/2015 modifié, art. 12, Chap. 7 : legifrance.gouv.fr/codes/section_lc/JORFTEXT000030280539/JORFSCTA000030280566/2024-03-02
  • Arrêté du 27/12/2021 financement structures urgences : JORFTEXT000044592184
  • Arrêté du 29/02/2024 modifiant 19/02/2015 (FU0 + annexes 18/19) : JORFTEXT000049219412
  • Annexe 19 Liste 1 PE1 (CIM-10) : LEGIARTI000049222509/2024-03-02
  • Arrêté du 17/12/2021 montants FPU : JORFTEXT000044592137

Confiance globale

  • ÉLEVÉ sur règle cumulative ET, nomenclature FU0-FU4, verdict MOREL, absence de seuil 6h
  • MOYEN sur listes exhaustives annexes 18/19, cumulabilité PE1+PE2
  • FAIBLE sur "FF1/FF2/FF3" (probablement Easily) et sémantique PE2 (était incorrecte dans nos hypothèses initiales)

Plan d'action P0 consolidé (à valider avec Dom + Amina)

Préalable bloquant

Trancher VT MOREL avec Amina/DIM Carvella : selon règle officielle ATIH, MOREL = UHCD. Mais server.py:32 dit FORFAIT (corrigé 2026-05-05). Soit :

  • (a) Erreur dans server.py et VT à inverser ⇒ MOREL devient cas de démo "UHCD non détectée" avec ROI ~375€
  • (b) VT volontaire pour illustrer un bug de codage administratif ⇒ système détecte l'incohérence "profil clinique UHCD + mode_sortie consultation externe" comme perte de recettes

Option (b) est plus puissante en démo. À confirmer.

Actions P0 (~3h cumulé, validation 1 par 1)

# Action Fichier Effort Risque
C Params Ollama déterministes (temp=0, seed=42, top_p=1, top_k=0) core/llm/t2a_decision.py:109-113 5 min nul
A Préprocesseur Python build_dpi_enriched (calcul durée + âge depuis OCR) core/llm/t2a_decision.py (nouvelle fonction) 45 min faible
B PROMPT 1 réécrit avec règle officielle ATIH (Section F rapport 4) + 2 few-shot + listes mutuellement exclusives + ordre clés preuves AVANT décision core/llm/t2a_decision.py:31-72 45 min moyen
D Garde-fous serveur dans _handle_t2a_decision_action (coercion + recompute 3/3 + regex contradiction + retry) agent_v0/server_v1/replay_engine.py:1204+ 45 min faible
E PROMPT 3 réécrit (balises XML + clause refus syntaxique exacte + structure conclusion verrouillée) DB workflow step 17 (UPDATE SQL) 30 min faible

Actions P1 (~1h, qualité)

# Action Fichier
F PROMPT 2 ajustements (3 lignes : durée explicite, mode sortie, actes CCAM, gestion conflit OCR) DB workflow step 14
G Corrections data.js (78→77 ans MOREL ; PERRIN GEMSA ; DA SILVA episode_heure ; commentaire ligne 2025) data.js
H Logs structurés JSON brut t2a_decision pour audit Q&A post-démo replay_engine.py

Actions P2 (hors scope démo)

  • Cas PE2/SU2 : exclure de la démo OU branche déterministe CCAM
  • Geler dpi_canonique en variable de session

Questions ouvertes pour reprise

  1. VT MOREL : Amina valide quoi (FORFAIT inversée ou maintenue avec narratif "détection bug codage") ?
  2. Cas PE2/SU2 démo : on les inclut ou on les écarte ?
  3. "FF1/FF2/FF3" : c'est interne Easily ou erreur de saisie ?
  4. Cumulabilité PE1+PE2 : à clarifier avec DIM Carvella ?
  5. Ordre d'attaque : on confirme C → A → B → D → E ?