- Multi-modèles : 4 rôles LLM (coding=gemma3:27b-cloud, cpam=gemma3:27b-cloud, validation=deepseek-v3.2:cloud, qc=gemma3:12b) avec get_model(role) - Prompts externalisés : 7 templates dans src/prompts/templates.py - Cache Ollama : modèle stocké par entrée (migration auto ancien format) - call_ollama() : paramètre role= (priorité: model > role > global) - Quality engine : veto_engine + decision_engine + rules_router (YAML) - Benchmark qualité : scripts/benchmark_quality.py (A/B, métriques CIM-10) - Fix biologie : valeurs qualitatives (troponine négative) non filtrées - Fix CPAM : gemma3:27b-cloud au lieu de deepseek (JSON tronqué par thinking) - CPAM max_tokens 4000→6000, viewer admin multi-modèles - Benchmark 10 dossiers : 100% DAS valides, 10/10 CPAM, 243s/dossier Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
341 lines
15 KiB
Python
341 lines
15 KiB
Python
"""Templates LLM externalisés pour le pipeline T2A.
|
|
|
|
Chaque template utilise str.format() avec des variables nommées.
|
|
Les accolades JSON sont doublées ({{ }}) pour échapper le format().
|
|
Les fragments conditionnels (ex: DP UCR) sont résolus AVANT l'appel
|
|
à template.format() dans les fonctions appelantes.
|
|
|
|
Variables par template :
|
|
CODING_CIM10 : texte, type_diag, ctx_str, sources_text
|
|
CODING_CCAM : texte, ctx_str, sources_text
|
|
DAS_EXTRACTION : dp_texte, existing_str, ctx_str, text_medical
|
|
QC_VALIDATION : ctx_str, codes_section
|
|
CPAM_EXTRACTION : dp_str, das_str, tagged_text, titre, arg_ucr,
|
|
decision_ucr, dp_ucr_line, da_ucr_line
|
|
CPAM_ARGUMENTATION : dossier_str, asymetrie_str, tagged_str, titre,
|
|
arg_ucr, decision_ucr, codes_str, definitions_str,
|
|
sources_text, extraction_str
|
|
CPAM_ADVERSARIAL : response_json, factual_section, normes_section,
|
|
dp_ucr_line, da_ucr_line
|
|
"""
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 1. CODING_CIM10 — Codage CIM-10 (DP ou DAS) via RAG
|
|
# Source : rag_search.py _build_prompt()
|
|
# Rôle : coding | Température : 0.1 | max_tokens : 2500
|
|
# ---------------------------------------------------------------------------
|
|
|
|
CODING_CIM10 = """\
|
|
Tu es un médecin DIM (Département d'Information Médicale) expert en codage PMSI.
|
|
Tu dois coder le diagnostic suivant en respectant STRICTEMENT les règles de l'ATIH.
|
|
|
|
RÈGLES IMPÉRATIVES :
|
|
- Le code doit provenir UNIQUEMENT des sources CIM-10 fournies
|
|
- Distingue la DESCRIPTION CLINIQUE (ce que le médecin écrit) de la LOGIQUE DE CODAGE (ce que l'ATIH impose)
|
|
- Privilégie le code le plus SPÉCIFIQUE disponible (4e ou 5e caractère)
|
|
- Vérifie les notes d'inclusion/exclusion de chaque code candidat
|
|
- Si le diagnostic est un DP, il doit refléter le motif principal de prise en charge du séjour
|
|
- Si c'est un DAS, il doit avoir mobilisé des ressources supplémentaires pendant le séjour
|
|
- EXCLUSION SYMPTÔME : Si le diagnostic est un symptôme (R00-R99) et qu'un diagnostic précis (Chapitres I-XIV, A00-N99) expliquant ce symptôme est présent, le symptôme ne doit PAS être codé comme DAS
|
|
|
|
DIAGNOSTIC À CODER : "{texte}"
|
|
TYPE : {type_diag}
|
|
|
|
CONTEXTE CLINIQUE :
|
|
{ctx_str}
|
|
|
|
SOURCES DE RÉFÉRENCE :
|
|
{sources_text}
|
|
Réponds UNIQUEMENT avec un objet JSON au format suivant, sans aucun texte avant ou après :
|
|
{{
|
|
"analyse_clinique": "que signifie ce diagnostic sur le plan médical",
|
|
"codes_candidats": "quels codes CIM-10 des sources sont compatibles",
|
|
"discrimination": "pourquoi choisir ce code plutôt qu'un autre (inclusions/exclusions, spécificité)",
|
|
"regle_pmsi": "conformité aux règles PMSI pour un {type_diag} (guide méthodologique)",
|
|
"code": "X99.9",
|
|
"confidence": "high ou medium ou low",
|
|
"justification": "explication courte en français",
|
|
"preuves_cliniques": [
|
|
{{"type": "biologie|imagerie|traitement|acte|clinique", "element": "élément concret du dossier", "interpretation": "signification clinique justifiant le code"}}
|
|
]
|
|
}}"""
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 2. CODING_CCAM — Codage CCAM via RAG
|
|
# Source : rag_search.py _build_prompt_ccam()
|
|
# Rôle : coding | Température : 0.1 | max_tokens : 2500
|
|
# ---------------------------------------------------------------------------
|
|
|
|
CODING_CCAM = """\
|
|
Tu es un médecin DIM (Département d'Information Médicale) expert en codage CCAM PMSI.
|
|
Tu dois coder l'acte chirurgical/médical suivant en respectant STRICTEMENT la nomenclature CCAM.
|
|
|
|
RÈGLES IMPÉRATIVES :
|
|
- Le code doit provenir UNIQUEMENT des sources CCAM fournies
|
|
- Un code CCAM est composé de 4 lettres + 3 chiffres (ex: HMFC004)
|
|
- Vérifie l'activité (1=acte technique, 4=anesthésie) et le regroupement
|
|
- Tiens compte du tarif secteur 1 pour valider la cohérence
|
|
- Si plusieurs codes sont possibles, choisis le plus spécifique à l'acte décrit
|
|
- En cas de doute, indique confidence "low" plutôt que de proposer un code inadapté
|
|
|
|
ACTE À CODER : "{texte}"
|
|
|
|
CONTEXTE CLINIQUE :
|
|
{ctx_str}
|
|
|
|
SOURCES CCAM :
|
|
{sources_text}
|
|
Réponds UNIQUEMENT avec un objet JSON au format suivant, sans aucun texte avant ou après :
|
|
{{
|
|
"analyse_acte": "que décrit cet acte sur le plan technique/chirurgical",
|
|
"codes_candidats": "quels codes CCAM des sources sont compatibles",
|
|
"discrimination": "pourquoi choisir ce code plutôt qu'un autre (activité, regroupement, tarif)",
|
|
"code": "ABCD123",
|
|
"confidence": "high ou medium ou low",
|
|
"justification": "explication courte en français"
|
|
}}"""
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 3. DAS_EXTRACTION — Extraction DAS supplémentaires via LLM
|
|
# Source : rag_search.py _build_prompt_das_extraction()
|
|
# Rôle : coding | Température : 0.1 | max_tokens : 2000
|
|
# ---------------------------------------------------------------------------
|
|
|
|
DAS_EXTRACTION = """\
|
|
Tu es un médecin DIM (Département d'Information Médicale) expert en codage PMSI.
|
|
Analyse le texte médical suivant et identifie les diagnostics associés significatifs (DAS) qui n'ont PAS encore été codés.
|
|
|
|
RÈGLES IMPÉRATIVES :
|
|
- Un DAS doit avoir mobilisé des ressources supplémentaires pendant le séjour
|
|
- Ne PAS proposer de doublons avec les DAS déjà codés ci-dessous
|
|
- Ne PAS proposer le diagnostic principal comme DAS
|
|
- Ne PAS coder les symptômes (R00-R99) si un diagnostic précis les explique
|
|
- 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}
|
|
|
|
DAS DÉJÀ CODÉS :
|
|
{existing_str}
|
|
|
|
CONTEXTE CLINIQUE :
|
|
{ctx_str}
|
|
|
|
TEXTE MÉDICAL :
|
|
{text_medical}
|
|
|
|
Réponds UNIQUEMENT avec un objet JSON au format suivant, sans aucun texte avant ou après :
|
|
{{
|
|
"diagnostics_supplementaires": [
|
|
{{
|
|
"texte": "description du diagnostic",
|
|
"code_cim10": "X99.9",
|
|
"justification": "pourquoi ce DAS est pertinent pour le séjour"
|
|
}}
|
|
]
|
|
}}
|
|
|
|
Si aucun DAS supplémentaire n'est pertinent, retourne : {{"diagnostics_supplementaires": []}}"""
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 4. QC_VALIDATION — Validation croisée batch des justifications
|
|
# Source : cim10_extractor.py _validate_justifications()
|
|
# Rôle : qc | Température : 0.1 | max_tokens : 2500
|
|
# ---------------------------------------------------------------------------
|
|
|
|
QC_VALIDATION = """\
|
|
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": ["..."]
|
|
}}"""
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 5. CPAM_EXTRACTION — Passe 1 extraction structurée CPAM
|
|
# Source : cpam_response.py _extraction_pass()
|
|
# Rôle : cpam | Température : 0.0 | max_tokens : 1500
|
|
# ---------------------------------------------------------------------------
|
|
|
|
CPAM_EXTRACTION = """\
|
|
Tu es un médecin DIM expert. Analyse cette contestation CPAM sans argumenter.
|
|
|
|
DOSSIER :
|
|
- DP : {dp_str}
|
|
- DAS : {das_str}
|
|
{tagged_text}
|
|
|
|
CONTESTATION CPAM :
|
|
Titre : {titre}
|
|
Argument : {arg_ucr}
|
|
Décision : {decision_ucr}
|
|
{dp_ucr_line}
|
|
{da_ucr_line}
|
|
|
|
Réponds UNIQUEMENT en JSON :
|
|
{{
|
|
"comprehension_contestation": "Résumé factuel : que conteste la CPAM et pourquoi",
|
|
"elements_cliniques_pertinents": [
|
|
{{"tag": "BIO-1 ou texte libre", "pertinence": "en quoi cet élément est pertinent pour le codage contesté"}}
|
|
],
|
|
"points_accord_potentiels": ["points où la CPAM a partiellement raison"],
|
|
"codes_en_jeu": {{
|
|
"dp_etablissement": "code + libellé",
|
|
"dp_ucr": "code + libellé si proposé",
|
|
"difference_cle": "explication de la différence entre les deux codages"
|
|
}}
|
|
}}"""
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 6. CPAM_ARGUMENTATION — Passe 2 contre-argumentation CPAM
|
|
# Source : cpam_response.py _build_cpam_prompt()
|
|
# Rôle : cpam | Température : 0.1 | max_tokens : 4000
|
|
# ---------------------------------------------------------------------------
|
|
|
|
CPAM_ARGUMENTATION = """\
|
|
Tu es un médecin DIM (Département d'Information Médicale) expert en contentieux T2A.
|
|
Tu dois produire une analyse ÉQUILIBRÉE ET CRÉDIBLE de la contestation CPAM, puis contre-argumenter en mobilisant trois axes : médical, asymétrie d'information, et réglementaire.
|
|
|
|
IMPORTANT — CRÉDIBILITÉ DE L'ANALYSE :
|
|
Une contre-argumentation crédible reconnaît TOUJOURS au moins un point valide dans le raisonnement adverse.
|
|
Répondre "Aucun point d'accord" décrédibilise l'ensemble de l'argumentation. Tu DOIS identifier au moins un élément où la CPAM a un point légitime (même partiel), puis expliquer pourquoi cela ne suffit pas à invalider le codage.
|
|
|
|
IMPORTANT — CODES CIM-10 :
|
|
Ne parle JAMAIS de « codage initial » ou « codage contesté » sans citer explicitement le code CIM-10 et son libellé (ex: Z45.80 — Ajustement et entretien d'un dispositif implantable).
|
|
Chaque argument doit désigner précisément quel code est défendu ou contesté, avec son libellé complet.
|
|
|
|
DOSSIER MÉDICAL DE L'ÉTABLISSEMENT :
|
|
{dossier_str}
|
|
{asymetrie_str}
|
|
{tagged_str}
|
|
|
|
OBJET DU DÉSACCORD : {titre}
|
|
|
|
ARGUMENTATION DE LA CPAM (UCR) :
|
|
{arg_ucr}
|
|
|
|
DÉCISION UCR : {decision_ucr}
|
|
|
|
CODES CONTESTÉS :
|
|
{codes_str}
|
|
{definitions_str}
|
|
|
|
SOURCES RÉGLEMENTAIRES (Guide méthodologique, CIM-10) :
|
|
{sources_text}
|
|
{extraction_str}
|
|
|
|
CONSIGNES :
|
|
|
|
CONTEXTE CLINIQUE :
|
|
- Prends en compte l'ÂGE du patient (pédiatrie < 18 ans, personne âgée >= 80 ans), le MODE D'ENTRÉE (urgence vs programmé), et la DURÉE DE SÉJOUR pour contextualiser ton analyse
|
|
- En pédiatrie, les normes biologiques et les codages peuvent différer de l'adulte
|
|
- Une admission en urgence implique un contexte clinique aigu qui influence le choix du DP
|
|
|
|
ÉTAPE 1 — ANALYSE HONNÊTE (avant de contre-argumenter) :
|
|
- Identifie ce que la CPAM a compris correctement dans le dossier
|
|
- Reconnais les points où leur raisonnement est fondé, même partiellement
|
|
- Explique ENSUITE pourquoi ces points ne justifient pas leur conclusion
|
|
|
|
AXE MÉDICAL :
|
|
- Analyse le bien-fondé médical du codage de l'établissement
|
|
- CITE les éléments cliniques EXACTS du dossier en utilisant les tags [XX-N] fournis (ex: [BIO-1] CRP 180 mg/L)
|
|
- Confronte l'argumentation CPAM aux sources CIM-10 et Guide Méthodologique fournies
|
|
- Ne mentionne AUCUN élément qui ne figure pas dans les éléments référencés ci-dessus
|
|
|
|
AXE ASYMÉTRIE D'INFORMATION :
|
|
- La CPAM a fondé son analyse uniquement sur le CRH et les codes transmis
|
|
- Pour CHAQUE élément clinique pertinent, cite les VALEURS EXACTES et explique leur signification clinique
|
|
- Démontre en quoi ces éléments complémentaires (biologie, imagerie, traitements, actes) justifient le codage contesté
|
|
- Ne mentionne AUCUN élément qui n'est pas dans le dossier fourni
|
|
|
|
MISE EN FORME :
|
|
- Structure chaque section avec des tirets pour lister les arguments distincts
|
|
- Un argument par puce, avec la preuve ou la référence associée
|
|
|
|
AXE RÉGLEMENTAIRE :
|
|
- Identifie si l'UCR fait une interprétation restrictive non fondée d'une règle
|
|
- Confronte le raisonnement CPAM au texte EXACT des sources fournies
|
|
- Format OBLIGATOIRE pour chaque référence : [Document - page N] suivi d'une CITATION VERBATIM du passage pertinent
|
|
- INTERDICTION ABSOLUE de citer une référence qui ne figure pas dans les sources fournies ci-dessus
|
|
- Si aucune source pertinente n'est disponible → écrire explicitement "Pas de source réglementaire disponible"
|
|
- Relève les contradictions entre l'argumentation CPAM et les règles officielles
|
|
|
|
Réponds UNIQUEMENT avec un objet JSON au format suivant :
|
|
{{
|
|
"analyse_contestation": "Résumé de ce que conteste la CPAM et sur quelle base",
|
|
"points_accord": "Points CONCRETS où la CPAM a raison ou partiellement raison (JAMAIS 'Aucun' — il y a toujours au moins un point légitime à reconnaître)",
|
|
"contre_arguments_medicaux": "Argumentation médicale en faveur du codage, en expliquant pourquoi les points d'accord ne suffisent pas à invalider le codage",
|
|
"preuves_dossier": [
|
|
{{"ref": "BIO-1", "element": "biologie|imagerie|traitement|acte|clinique", "valeur": "valeur exacte du dossier", "signification": "explication clinique"}}
|
|
],
|
|
"contre_arguments_asymetrie": "Éléments cliniques que la CPAM n'avait pas et qui justifient le codage",
|
|
"contre_arguments_reglementaires": "Erreurs d'interprétation réglementaire de la CPAM, avec citations verbatim des sources",
|
|
"references": [
|
|
{{"document": "nom du document source", "page": "numéro de page", "citation": "citation verbatim du passage"}}
|
|
],
|
|
"conclusion": "Synthèse en citant EXPLICITEMENT les codes CIM-10 défendus (ex: DP Z45.80 — libellé) : points reconnus à la CPAM, puis pourquoi ce codage précis est néanmoins justifié"
|
|
}}"""
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 7. CPAM_ADVERSARIAL — Validation adversariale de la contre-argumentation
|
|
# Source : cpam_response.py _validate_adversarial()
|
|
# Rôle : validation | Température : 0.0 | max_tokens : 800
|
|
# ---------------------------------------------------------------------------
|
|
|
|
CPAM_ADVERSARIAL = """\
|
|
Tu es un relecteur critique. Vérifie la cohérence de cette contre-argumentation CPAM.
|
|
|
|
RÉPONSE GÉNÉRÉE :
|
|
{response_json}
|
|
|
|
{factual_section}
|
|
|
|
{normes_section}
|
|
|
|
CODES CONTESTÉS :
|
|
{dp_ucr_line}
|
|
{da_ucr_line}
|
|
|
|
Vérifie STRICTEMENT :
|
|
1. Chaque valeur bio/imagerie/traitement citée dans les preuves existe dans les éléments factuels
|
|
2. Si une valeur bio est qualifiée de "élevée", "basse" ou "anormale", vérifie qu'elle est RÉELLEMENT hors normes selon les normes ci-dessus (ex: CRP 5 = NORMAL, pas élevé)
|
|
3. La conclusion est cohérente avec l'argumentation développée
|
|
4. Les points d'accord ne contredisent pas les contre-arguments
|
|
5. Les codes CIM-10 mentionnés dans la conclusion sont cohérents avec le reste
|
|
|
|
Réponds UNIQUEMENT en JSON :
|
|
{{
|
|
"coherent": true ou false,
|
|
"erreurs": ["description précise de chaque incohérence trouvée"],
|
|
"score_confiance": 0 à 10
|
|
}}"""
|