feat: mode hybride Ollama — gemma3:27b pour CPAM, 12b pour codage

Le pipeline utilise désormais gemma3:12b (rapide) pour le codage CIM-10
et gemma3:27b (meilleur raisonnement) pour la contre-argumentation CPAM.
Configurable via OLLAMA_MODEL_CPAM et OLLAMA_TIMEOUT_CPAM.

Inclut aussi : traçabilité source/page DAS, niveaux CMA ATIH, sévérité,
page tracker PDF, améliorations fusion et filtres DAS.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
dom
2026-02-17 17:53:53 +01:00
parent 4ef42dd3d3
commit 01d47f3c4b
20 changed files with 1025 additions and 98 deletions

View File

@@ -6,12 +6,16 @@ Phase 2 (future) : tables CMA/CMS officielles ATIH.
from __future__ import annotations
import json
import logging
import re
from dataclasses import dataclass, field
from typing import Optional
from .cim10_dict import load_dict, normalize_text
logger = logging.getLogger(__name__)
# --- Marqueurs de sévérité dans le texte ---
@@ -73,11 +77,34 @@ _HEURISTIC_CMA_ROOTS: set[str] = {
}
_cma_levels: dict[str, int] | None = None
def _load_cma_levels() -> dict[str, int]:
"""Charge les niveaux CMA officiels depuis data/cma_levels.json (lazy-loaded)."""
global _cma_levels
if _cma_levels is not None:
return _cma_levels
from ..config import CMA_LEVELS_PATH
try:
data = json.loads(CMA_LEVELS_PATH.read_text(encoding="utf-8"))
_cma_levels = {k: int(v) for k, v in data.items()}
logger.debug("CMA levels chargés : %d codes", len(_cma_levels))
except FileNotFoundError:
logger.warning("Fichier CMA levels non trouvé : %s", CMA_LEVELS_PATH)
_cma_levels = {}
except Exception:
logger.warning("Erreur chargement CMA levels", exc_info=True)
_cma_levels = {}
return _cma_levels
@dataclass
class SeverityInfo:
"""Résultat de l'évaluation de sévérité d'un diagnostic."""
est_cma_probable: bool = False
niveau_severite: str = "non_evalue" # "leger" | "modere" | "severe" | "non_evalue"
niveau_cma: int = 1 # 1 (pas CMA), 2, 3 ou 4 (officiel ATIH)
marqueurs_trouves: list[str] = field(default_factory=list)
@@ -119,11 +146,14 @@ def _is_heuristic_cma(code: str) -> bool:
def evaluate_severity(diagnostic) -> SeverityInfo:
"""Évalue la sévérité d'un diagnostic (texte + code CIM-10).
Utilise en priorité les niveaux CMA officiels ATIH (2/3/4),
avec fallback sur l'heuristique par racines CIM-10.
Args:
diagnostic: Objet avec attributs texte, cim10_suggestion.
Returns:
SeverityInfo avec est_cma_probable, niveau_severite, marqueurs_trouves.
SeverityInfo avec est_cma_probable, niveau_cma, niveau_severite, marqueurs_trouves.
"""
info = SeverityInfo()
@@ -147,13 +177,17 @@ def evaluate_severity(diagnostic) -> SeverityInfo:
info.niveau_severite = niveau
info.marqueurs_trouves = marqueurs
# 3. Heuristique CMA basée sur la racine CIM-10
if code and _is_heuristic_cma(code):
info.est_cma_probable = True
# Un diagnostic sévère avec un code CMA-probable = forte indication
if niveau == "severe" and info.est_cma_probable:
info.est_cma_probable = True
# 3. Lookup officiel CMA ATIH (prioritaire)
if code:
cma_levels = _load_cma_levels()
official_level = cma_levels.get(code)
if official_level:
info.niveau_cma = official_level
info.est_cma_probable = True
elif _is_heuristic_cma(code):
# Fallback heuristique → niveau 2
info.niveau_cma = 2
info.est_cma_probable = True
return info
@@ -176,6 +210,7 @@ def enrich_dossier_severity(dp, das_list: list) -> tuple[list[str], int, int]:
if dp and dp.cim10_suggestion:
info = evaluate_severity(dp)
dp.niveau_severite = info.niveau_severite
dp.niveau_cma = info.niveau_cma
if info.est_cma_probable:
dp.est_cma = True
@@ -187,15 +222,16 @@ def enrich_dossier_severity(dp, das_list: list) -> tuple[list[str], int, int]:
continue
info = evaluate_severity(das)
das.niveau_severite = info.niveau_severite
das.niveau_cma = info.niveau_cma
if info.est_cma_probable:
das.est_cma = True
cma_count += 1
# CMS = CMA sévère
if info.niveau_severite == "severe":
# CMS = CMA niveau 4 ou CMA sévère
if info.niveau_cma >= 4 or info.niveau_severite == "severe":
das.est_cms = True
cms_count += 1
alertes.append(
f"CMA probable : '{das.texte}' ({das.cim10_suggestion}) — "
f"CMA niveau {info.niveau_cma} : '{das.texte}' ({das.cim10_suggestion}) — "
f"sévérité {info.niveau_severite}"
+ (f", marqueurs : {', '.join(info.marqueurs_trouves)}" if info.marqueurs_trouves else "")
)