feat: architecture multi-modèles LLM + quality engine + benchmark
- 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>
This commit is contained in:
34
config/bio_rules.yaml
Normal file
34
config/bio_rules.yaml
Normal file
@@ -0,0 +1,34 @@
|
||||
version: 2
|
||||
|
||||
# Règles biologiques (contradiction bio ⇒ ruled_out)
|
||||
# + garde-fou "preuve manquante" (diag d'ionogramme sans valeur extraite ⇒ NEED_INFO)
|
||||
#
|
||||
# Objectif: éviter des FAIL "bêtes" quand la biologie contredit clairement un diagnostic,
|
||||
# et éviter des PASS "trop optimistes" quand on n'a même pas la valeur biologique.
|
||||
#
|
||||
# Hiérarchie des seuils:
|
||||
# - Priorité aux normes du document (ex: [N: 135-145])
|
||||
# - Sinon fallback config/reference_ranges.yaml
|
||||
# - Si âge inconnu/enfant: safe zones conservatrices (reference_ranges.yaml)
|
||||
|
||||
missing_evidence:
|
||||
enabled: true
|
||||
veto: VETO-17
|
||||
severity: LOW
|
||||
score_penalty: 2
|
||||
|
||||
rules:
|
||||
hyponatremia:
|
||||
enabled: true
|
||||
codes: ["E87.1"] # hyponatrémie
|
||||
analyte: sodium
|
||||
|
||||
hyperkalemia:
|
||||
enabled: true
|
||||
codes: ["E87.5"] # hyperkaliémie
|
||||
analyte: potassium
|
||||
|
||||
hypokalemia:
|
||||
enabled: true
|
||||
codes: ["E87.6"] # hypokaliémie
|
||||
analyte: potassium
|
||||
62
config/lab_value_sanity.yaml
Normal file
62
config/lab_value_sanity.yaml
Normal file
@@ -0,0 +1,62 @@
|
||||
# Garde-fous de parsing des valeurs biologiques
|
||||
# ------------------------------------------------
|
||||
# Objectif: éviter des faux positifs dus à des artefacts PDF/OCR
|
||||
# (ex: "8" au lieu de "4.8" pour le potassium).
|
||||
#
|
||||
# IMPORTANT:
|
||||
# - Ce fichier ne définit PAS des "normes biologiques" (ça c'est reference_ranges.yaml)
|
||||
# - Ici on définit des bornes *plausibles* très larges + quelques heuristiques "anti-OCR".
|
||||
#
|
||||
# Clés des tests: minuscules, sans accents, ex: potassium, sodium, plaquettes, hemoglobine...
|
||||
version: 1
|
||||
|
||||
policy:
|
||||
drop_out_of_range: true # écarte les valeurs hors bornes plausibles du dossier
|
||||
keep_suspect: true # conserve les valeurs suspectes (audit) mais les règles privilégient les valeurs ok
|
||||
|
||||
tests:
|
||||
potassium:
|
||||
hard_min: 0.5
|
||||
hard_max: 9.0
|
||||
suspect:
|
||||
single_digit_over: 6.0 # "8" seul est souvent une décimale perdue (4,8 -> 8)
|
||||
|
||||
sodium:
|
||||
hard_min: 90
|
||||
hard_max: 200
|
||||
|
||||
plaquettes:
|
||||
hard_min: 5
|
||||
hard_max: 2000
|
||||
|
||||
hemoglobine:
|
||||
hard_min: 3
|
||||
hard_max: 25
|
||||
|
||||
creatinine:
|
||||
hard_min: 1
|
||||
hard_max: 5000
|
||||
|
||||
crp:
|
||||
hard_min: 0
|
||||
hard_max: 1000
|
||||
|
||||
alat:
|
||||
hard_min: 0
|
||||
hard_max: 5000
|
||||
|
||||
asat:
|
||||
hard_min: 0
|
||||
hard_max: 5000
|
||||
|
||||
ggt:
|
||||
hard_min: 0
|
||||
hard_max: 5000
|
||||
|
||||
pal:
|
||||
hard_min: 0
|
||||
hard_max: 5000
|
||||
|
||||
bilirubine totale:
|
||||
hard_min: 0
|
||||
hard_max: 2000
|
||||
30
config/reference_ranges.yaml
Normal file
30
config/reference_ranges.yaml
Normal file
@@ -0,0 +1,30 @@
|
||||
# Références biologiques (fallback) pour règles de qualité (VETO-09 / ruled_out)
|
||||
# Ordre de priorité recommandé:
|
||||
# 1) Normes du document (ex: "[N: 135-145]")
|
||||
# 2) Fallback ci-dessous (par bande d'âge)
|
||||
# 3) Safe zones conservatrices si âge inconnu (évite les faux "barrés")
|
||||
|
||||
version: 1
|
||||
|
||||
age_bands:
|
||||
adult_min_years: 18
|
||||
|
||||
fallback_ranges:
|
||||
adult:
|
||||
platelets: { low: 150, high: 450, unit: "G/L" }
|
||||
sodium: { low: 135, high: 145, unit: "mmol/L" }
|
||||
potassium: { low: 3.5, high: 5.0, unit: "mmol/L" }
|
||||
|
||||
# Pédiatrie: à affiner par tranches d'âge si besoin.
|
||||
# NB: pour les décisions "ruled_out" avec âge inconnu, on applique plutôt les safe zones.
|
||||
child:
|
||||
platelets: { low: 150, high: 450, unit: "G/L" }
|
||||
sodium: { low: 135, high: 145, unit: "mmol/L" }
|
||||
potassium: { low: 3.5, high: 5.0, unit: "mmol/L" }
|
||||
|
||||
# Seuils "safe" quand l'âge n'est pas connu (plus conservateurs que les bornes normales)
|
||||
safe_zones_unknown_age:
|
||||
platelets_ruled_out_low: 170 # si PLT >= 170 -> thrombopénie ruled_out
|
||||
sodium_ruled_out_low: 138 # si Na >= 138 -> hyponatrémie ruled_out
|
||||
potassium_ruled_out_high: 4.9 # si K <= 4.9 -> hyperkaliémie ruled_out
|
||||
potassium_ruled_out_low: 3.7 # si K >= 3.7 -> hypokaliémie ruled_out
|
||||
68
config/rules/README.md
Normal file
68
config/rules/README.md
Normal file
@@ -0,0 +1,68 @@
|
||||
# Règles (vetos + décisions)
|
||||
|
||||
Ce dossier contient la configuration "métier" pour piloter le moteur qualité.
|
||||
|
||||
## Fichiers
|
||||
|
||||
- `base.yaml` : socle commun (règles activées par défaut).
|
||||
- `enabled.yaml` : choisit les overlays à activer (site/spécialité).
|
||||
- `specialties/*.yaml` : overrides par spécialité.
|
||||
- `sites/*.yaml` : overrides par établissement.
|
||||
|
||||
## Principe
|
||||
|
||||
- Une règle **non listée** est considérée **activée**.
|
||||
- Ça évite de casser le comportement historique lors d'une montée de version.
|
||||
- Une règle listée peut être :
|
||||
- `enabled: false` → désactivée
|
||||
- (VETO) `force_severity: "HARD"|"MEDIUM"|"LOW"` → force la sévérité
|
||||
|
||||
## Exemple d'override
|
||||
|
||||
Créer `config/rules/sites/chu_poitiers.yaml` :
|
||||
|
||||
```yaml
|
||||
version: 1
|
||||
rules:
|
||||
VETO-12:
|
||||
enabled: false
|
||||
VETO-09:
|
||||
force_severity: "HARD"
|
||||
```
|
||||
|
||||
Puis activer dans `enabled.yaml` :
|
||||
|
||||
```yaml
|
||||
active:
|
||||
site: "chu_poitiers"
|
||||
specialty: ""
|
||||
extra: []
|
||||
```
|
||||
|
||||
|
||||
## Routage automatique (router.yaml)
|
||||
|
||||
Le fichier `router.yaml` permet d’activer automatiquement des **packs** de règles en fonction des signaux du dossier (codes, biologie, extraits). Concrètement :
|
||||
|
||||
- Par défaut, seuls les packs listés dans `defaults.enabled_packs` sont actifs.
|
||||
- Quand un trigger match, on ajoute ses `enable_packs`.
|
||||
- Le routage est appliqué **par dossier** (et re-appliqué sur la version fusionnée).
|
||||
|
||||
### Mode strict
|
||||
|
||||
Quand `mode: strict`, une règle *non listée* dans `base.yaml` est considérée **désactivée** dès que le routage runtime est actif.
|
||||
|
||||
Ça force une approche “catalogue explicite” : tout ce qui tourne en prod est visible et gouvernable.
|
||||
|
||||
### Exemple
|
||||
|
||||
Activer les règles ionogramme uniquement si un code `E87.*` est détecté ou si la biologie mentionne Sodium/Potassium :
|
||||
|
||||
```yaml
|
||||
triggers:
|
||||
- id: TRG-ELECTROLYTES
|
||||
enable_packs: [bio_electrolytes]
|
||||
when_any:
|
||||
codes_prefix: ["E87."]
|
||||
lab_tests: ["sodium", "potassium"]
|
||||
```
|
||||
82
config/rules/base.yaml
Normal file
82
config/rules/base.yaml
Normal file
@@ -0,0 +1,82 @@
|
||||
version: 1
|
||||
|
||||
# Catalogue "socle" de règles.
|
||||
#
|
||||
# Objectif : piloter (sans toucher au code) :
|
||||
# - l'activation/désactivation de règles (vetos + décisions)
|
||||
# - éventuellement un forçage de sévérité pour un VETO
|
||||
#
|
||||
# Important : si une règle n'est pas listée ici, elle est considérée activée.
|
||||
# (=> comportement historique conservé)
|
||||
|
||||
packs:
|
||||
vetos_core:
|
||||
enabled: true
|
||||
rules:
|
||||
VETO-02:
|
||||
enabled: true
|
||||
description: "Code sans preuve exploitable"
|
||||
VETO-03:
|
||||
enabled: true
|
||||
description: "Conditionnel / négation / contradictions dans la preuve"
|
||||
VETO-06:
|
||||
enabled: true
|
||||
description: "DP dupliqué dans les DAS"
|
||||
VETO-07:
|
||||
enabled: true
|
||||
description: "Doublons DAS"
|
||||
VETO-09:
|
||||
enabled: true
|
||||
description: "Contradiction biologique (plaquettes/créat)"
|
||||
# force_severity: "HARD" # Optionnel : forcer la sévérité globale
|
||||
VETO-12:
|
||||
enabled: true
|
||||
description: "Sur-confiance (high sans preuve)"
|
||||
VETO-15:
|
||||
enabled: true
|
||||
description: "Preuve issue d'un score/test (risque de sur-codage)"
|
||||
VETO-16:
|
||||
enabled: true
|
||||
description: "Heuristique libellé→code (hors-sujet probable)"
|
||||
VETO-17:
|
||||
enabled: true
|
||||
description: "Preuve biologique manquante => NEED_INFO (non bloquant)"
|
||||
|
||||
decisions_core:
|
||||
enabled: true
|
||||
rules:
|
||||
RULE-D50-NEEDS-IRON:
|
||||
enabled: true
|
||||
description: "D50 sans preuve martiale => downgrade D64.9 + NEED_INFO"
|
||||
RULE-D69.6-PLT-NORMAL:
|
||||
enabled: true
|
||||
description: "D69.6 incompatible avec plaquettes normales => ruled_out (barré)"
|
||||
|
||||
bio_electrolytes:
|
||||
enabled: true
|
||||
rules:
|
||||
RULE-E87.1-NA-NORMAL:
|
||||
enabled: true
|
||||
description: "E87.1 suggérée mais Na normal => ruled_out"
|
||||
RULE-E87.1-MISSING-NA:
|
||||
enabled: true
|
||||
description: "E87.1 suggérée mais Na absent => NEED_INFO"
|
||||
RULE-E87.5-K-NORMAL:
|
||||
enabled: true
|
||||
description: "E87.5 suggérée mais K normal => ruled_out"
|
||||
RULE-E87.5-MISSING-K:
|
||||
enabled: true
|
||||
description: "E87.5 suggérée mais K absent => NEED_INFO"
|
||||
RULE-E87.6-K-NORMAL:
|
||||
enabled: true
|
||||
description: "E87.6 suggérée mais K normal => ruled_out"
|
||||
RULE-E87.6-MISSING-K:
|
||||
enabled: true
|
||||
description: "E87.6 suggérée mais K absent => NEED_INFO"
|
||||
|
||||
placeholders_future:
|
||||
enabled: false
|
||||
rules:
|
||||
RULE-PDF-PROTECTED-NEED_INFO:
|
||||
enabled: false
|
||||
description: "PDF protégé => NEED_INFO (à implémenter si besoin)"
|
||||
12
config/rules/enabled.yaml
Normal file
12
config/rules/enabled.yaml
Normal file
@@ -0,0 +1,12 @@
|
||||
version: 1
|
||||
|
||||
# Sélection d'overlays (facile à brancher plus tard sur une UI).
|
||||
#
|
||||
# - specialty : charge config/rules/specialties/<specialty>.yaml
|
||||
# - site : charge config/rules/sites/<site>.yaml
|
||||
# - extra : charge des fichiers YAML additionnels (chemins relatifs à config/rules/)
|
||||
|
||||
active:
|
||||
specialty: ""
|
||||
site: ""
|
||||
extra: []
|
||||
35
config/rules/router.yaml
Normal file
35
config/rules/router.yaml
Normal file
@@ -0,0 +1,35 @@
|
||||
version: 1
|
||||
|
||||
# 'strict' => si un rule_id n'est pas listé dans base.yaml, il est considéré OFF
|
||||
# quand le routage runtime est actif (objectif: pro / pas de surprise).
|
||||
mode: strict
|
||||
|
||||
defaults:
|
||||
# Socle pro: toujours actif (peu coûteux, structure la contestabilité)
|
||||
enabled_packs:
|
||||
- vetos_core
|
||||
- decisions_core
|
||||
|
||||
# (Optionnel) règles toujours ON même si leur pack n'est pas actif
|
||||
always_on_rules: []
|
||||
|
||||
# Triggers : activer des packs additionnels seulement si le dossier a des signaux pertinents
|
||||
triggers:
|
||||
- id: TRG-ELECTROLYTES
|
||||
enable_packs: ["bio_electrolytes"]
|
||||
when_any:
|
||||
# Codes souvent porteurs d'ionogramme (hyponatrémie/hyperkaliémie/hypokaliémie)
|
||||
codes_prefix: ["E87."]
|
||||
# Ou biologie présente
|
||||
lab_tests: ["ionogramme", "sodium", "potassium", "na", "k"]
|
||||
# Ou texte
|
||||
keywords:
|
||||
- "ionogramme"
|
||||
- "hypokali"
|
||||
- "hyperkali"
|
||||
- "hyponatr"
|
||||
- "hypernatr"
|
||||
- "kaliémie"
|
||||
- "natrémie"
|
||||
- "sodium"
|
||||
- "potassium"
|
||||
9
config/rules/sites/_template.yaml
Normal file
9
config/rules/sites/_template.yaml
Normal file
@@ -0,0 +1,9 @@
|
||||
version: 1
|
||||
|
||||
# Overlay établissement (ex: chu_poitiers, clinique_x, etc.)
|
||||
# Ce fichier ne contient que des overrides.
|
||||
|
||||
rules:
|
||||
# Exemple : forcer VETO-09 en HARD
|
||||
# VETO-09:
|
||||
# force_severity: "HARD"
|
||||
13
config/rules/specialties/_template.yaml
Normal file
13
config/rules/specialties/_template.yaml
Normal file
@@ -0,0 +1,13 @@
|
||||
version: 1
|
||||
|
||||
# Overlay spécialité (ex: digestif, cardio, pneumo, onco...)
|
||||
# Ce fichier ne contient que des overrides.
|
||||
|
||||
rules:
|
||||
# Exemple : être plus strict sur le conditionnel
|
||||
# VETO-03:
|
||||
# force_severity: "MEDIUM"
|
||||
|
||||
# Exemple : désactiver un downgrade jugé trop agressif
|
||||
# RULE-D50-NEEDS-IRON:
|
||||
# enabled: false
|
||||
Reference in New Issue
Block a user