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

@@ -8,6 +8,7 @@ from src.medical.severity import (
enrich_dossier_severity,
_detect_severity_markers,
_is_heuristic_cma,
_load_cma_levels,
)
@@ -59,6 +60,49 @@ class TestHeuristicCMA:
assert _is_heuristic_cma(None) is False
class TestCMALevels:
"""Tests pour le lookup CMA officiel ATIH."""
def test_load_cma_levels(self):
levels = _load_cma_levels()
assert len(levels) > 0
# A01.0 est severity 2 dans cocoa_entries
assert levels.get("A01.0") == 2
def test_official_level_4(self):
"""Un code CMA niveau 4 est bien détecté."""
levels = _load_cma_levels()
level4_codes = [k for k, v in levels.items() if v == 4]
assert len(level4_codes) > 0
def test_official_level_propagated(self):
"""evaluate_severity propage le niveau CMA officiel."""
levels = _load_cma_levels()
# Prendre un code de niveau 3
code_lv3 = next((k for k, v in levels.items() if v == 3), None)
if code_lv3:
diag = Diagnostic(texte="Test diagnostic", cim10_suggestion=code_lv3)
info = evaluate_severity(diag)
assert info.niveau_cma == 3
assert info.est_cma_probable is True
def test_heuristic_fallback_level_2(self):
"""Un code heuristique CMA sans entrée officielle → niveau 2."""
# E11.9 est dans les racines heuristiques ET dans le fichier officiel
# Testons avec un code heuristique qui n'est pas dans le fichier officiel
diag = Diagnostic(texte="Test", cim10_suggestion="E11.9")
info = evaluate_severity(diag)
assert info.est_cma_probable is True
assert info.niveau_cma >= 2
def test_non_cma_remains_level_1(self):
"""Un code non-CMA reste au niveau 1."""
diag = Diagnostic(texte="Grippe", cim10_suggestion="J11.1")
info = evaluate_severity(diag)
if not info.est_cma_probable:
assert info.niveau_cma == 1
class TestEvaluateSeverity:
def test_cma_code_detected(self):
diag = Diagnostic(texte="Diabète type 2", cim10_suggestion="E11.9")
@@ -66,7 +110,8 @@ class TestEvaluateSeverity:
assert info.est_cma_probable is True
def test_non_cma_code(self):
diag = Diagnostic(texte="Pancréatite aiguë biliaire", cim10_suggestion="K85.1")
"""Un code non CMA (J11.1 grippe) n'est pas détecté comme CMA."""
diag = Diagnostic(texte="Grippe", cim10_suggestion="J11.1")
info = evaluate_severity(diag)
assert info.est_cma_probable is False
@@ -82,6 +127,12 @@ class TestEvaluateSeverity:
info = evaluate_severity(diag)
assert info.est_cma_probable is True
def test_niveau_cma_in_result(self):
"""Le champ niveau_cma est toujours renseigné."""
diag = Diagnostic(texte="Sepsis", cim10_suggestion="A41.9")
info = evaluate_severity(diag)
assert info.niveau_cma >= 1
class TestEnrichDossierSeverity:
def test_enriches_das_in_place(self):
@@ -119,3 +170,22 @@ class TestEnrichDossierSeverity:
assert das[0].est_cma is True
assert das[0].est_cms is True
assert cms_count == 1
def test_niveau_cma_set_on_das(self):
"""enrich_dossier_severity propage niveau_cma sur chaque DAS."""
dp = Diagnostic(texte="Pancréatite", cim10_suggestion="K85.1")
das = [
Diagnostic(texte="Fibrillation auriculaire", cim10_suggestion="I48.9"),
]
enrich_dossier_severity(dp, das)
assert das[0].niveau_cma is not None
assert das[0].niveau_cma >= 2
def test_alertes_contain_cma_level(self):
"""Les alertes mentionnent le niveau CMA."""
dp = Diagnostic(texte="Test", cim10_suggestion="K85.1")
das = [
Diagnostic(texte="Sepsis", cim10_suggestion="A41.9"),
]
alertes, _, _ = enrich_dossier_severity(dp, das)
assert any("CMA niveau" in a for a in alertes)