227 lines
8.7 KiB
Python
227 lines
8.7 KiB
Python
"""Tests P1-lite — LOGIC-2 (CPAM dégradé), LOGIC-3 (modèles identiques).
|
|
|
|
Sans mocks : manipulation directe des structures de données et env vars.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from src.config import (
|
|
ControleCPAM,
|
|
DossierMedical,
|
|
OLLAMA_MODELS,
|
|
Sejour,
|
|
check_adversarial_model_config,
|
|
)
|
|
|
|
|
|
# ============================================================
|
|
# LOGIC-2 — CPAM passe 1 échoue → mode dégradé tracé
|
|
# ============================================================
|
|
|
|
class TestCpamDegradedMode:
|
|
"""Vérifie que le mode dégradé passe 1 est correctement tracé."""
|
|
|
|
def test_degraded_sets_alertes_codage(self):
|
|
"""Si extraction est None, alertes_codage doit contenir le message."""
|
|
dossier = DossierMedical(sejour=Sejour())
|
|
|
|
# Simule le comportement de generate_cpam_response quand extraction = None
|
|
extraction = None
|
|
degraded_pass1 = extraction is None
|
|
if degraded_pass1:
|
|
dossier.alertes_codage.append(
|
|
"CPAM: passe 1 (extraction structurée) échouée → mode dégradé"
|
|
)
|
|
|
|
assert any("passe 1" in a for a in dossier.alertes_codage)
|
|
assert any("dégradé" in a for a in dossier.alertes_codage)
|
|
|
|
def test_degraded_sets_quality_flags_on_result(self):
|
|
"""quality_flags ajouté au résultat quand dégradé."""
|
|
result = {"conclusion": "test"}
|
|
|
|
degraded_pass1 = True
|
|
if degraded_pass1:
|
|
result.setdefault("quality_flags", {})
|
|
result["quality_flags"]["cpam_pass1_failed"] = True
|
|
result["quality_flags"]["degraded_mode"] = True
|
|
|
|
assert result["quality_flags"]["cpam_pass1_failed"] is True
|
|
assert result["quality_flags"]["degraded_mode"] is True
|
|
|
|
def test_non_degraded_no_quality_flags(self):
|
|
"""Pas de quality_flags quand extraction réussit."""
|
|
result = {"conclusion": "test"}
|
|
|
|
extraction = {"comprehension_contestation": "ok"}
|
|
degraded_pass1 = extraction is None
|
|
|
|
assert degraded_pass1 is False
|
|
assert "quality_flags" not in result
|
|
|
|
def test_quality_flags_format_matches_spec(self):
|
|
"""Format quality_flags conforme au spec."""
|
|
result: dict = {}
|
|
result.setdefault("quality_flags", {})
|
|
result["quality_flags"]["cpam_pass1_failed"] = True
|
|
result["quality_flags"]["degraded_mode"] = True
|
|
|
|
flags = result["quality_flags"]
|
|
assert isinstance(flags, dict)
|
|
assert "cpam_pass1_failed" in flags
|
|
assert "degraded_mode" in flags
|
|
|
|
|
|
# ============================================================
|
|
# LOGIC-3 — Modèles CPAM et validation identiques
|
|
# ============================================================
|
|
|
|
class TestAdversarialModelCheck:
|
|
"""Vérifie la détection de modèles identiques CPAM/validation."""
|
|
|
|
def test_same_model_detected(self):
|
|
"""Modèles identiques → (True, message)."""
|
|
old_cpam = OLLAMA_MODELS["cpam"]
|
|
old_val = OLLAMA_MODELS["validation"]
|
|
OLLAMA_MODELS["cpam"] = "test-same-model"
|
|
OLLAMA_MODELS["validation"] = "test-same-model"
|
|
try:
|
|
same, msg = check_adversarial_model_config()
|
|
assert same is True
|
|
assert "identiques" in msg
|
|
assert "test-same-model" in msg
|
|
finally:
|
|
OLLAMA_MODELS["cpam"] = old_cpam
|
|
OLLAMA_MODELS["validation"] = old_val
|
|
|
|
def test_different_models_ok(self):
|
|
"""Modèles différents → (False, '')."""
|
|
old_cpam = OLLAMA_MODELS["cpam"]
|
|
old_val = OLLAMA_MODELS["validation"]
|
|
OLLAMA_MODELS["cpam"] = "model-a"
|
|
OLLAMA_MODELS["validation"] = "model-b"
|
|
try:
|
|
same, msg = check_adversarial_model_config()
|
|
assert same is False
|
|
assert msg == ""
|
|
finally:
|
|
OLLAMA_MODELS["cpam"] = old_cpam
|
|
OLLAMA_MODELS["validation"] = old_val
|
|
|
|
def test_adversarial_skip_returns_degraded_result(self):
|
|
"""Si même modèle, la validation adversariale retourne un résultat dégradé."""
|
|
old_cpam = OLLAMA_MODELS["cpam"]
|
|
old_val = OLLAMA_MODELS["validation"]
|
|
OLLAMA_MODELS["cpam"] = "same-model"
|
|
OLLAMA_MODELS["validation"] = "same-model"
|
|
try:
|
|
same, msg = check_adversarial_model_config()
|
|
assert same is True
|
|
|
|
# Simule le comportement de _validate_adversarial quand same_model
|
|
degraded = {
|
|
"coherent": True,
|
|
"erreurs": [f"Validation adversariale dégradée : {msg}"],
|
|
"score_confiance": 0,
|
|
}
|
|
assert degraded["score_confiance"] == 0
|
|
assert "dégradée" in degraded["erreurs"][0]
|
|
finally:
|
|
OLLAMA_MODELS["cpam"] = old_cpam
|
|
OLLAMA_MODELS["validation"] = old_val
|
|
|
|
def test_empty_model_not_flagged(self):
|
|
"""Modèles vides ne déclenchent pas le flag."""
|
|
old_cpam = OLLAMA_MODELS["cpam"]
|
|
old_val = OLLAMA_MODELS["validation"]
|
|
OLLAMA_MODELS["cpam"] = ""
|
|
OLLAMA_MODELS["validation"] = ""
|
|
try:
|
|
same, msg = check_adversarial_model_config()
|
|
assert same is False
|
|
finally:
|
|
OLLAMA_MODELS["cpam"] = old_cpam
|
|
OLLAMA_MODELS["validation"] = old_val
|
|
|
|
|
|
# ============================================================
|
|
# LOGIC-2 & LOGIC-3 — quality_flags + alertes visibles output
|
|
# ============================================================
|
|
|
|
class TestQualityFlagsOutput:
|
|
"""Vérifie que les quality_flags et alertes sont visibles dans l'output."""
|
|
|
|
def test_cpam_pass1_failure_sets_quality_flags_and_alert(self):
|
|
"""LOGIC-2 — passe 1 échouée → quality_flags + alerte dans dossier."""
|
|
dossier = DossierMedical(sejour=Sejour())
|
|
result: dict = {"conclusion": "test argument"}
|
|
|
|
# Simule le flow exact de generate_cpam_response (lines 122-165)
|
|
extraction = None # passe 1 échouée
|
|
degraded_pass1 = extraction is None
|
|
if degraded_pass1:
|
|
dossier.alertes_codage.append(
|
|
"CPAM: passe 1 (extraction structurée) échouée → mode dégradé"
|
|
)
|
|
if degraded_pass1:
|
|
result.setdefault("quality_flags", {})
|
|
result["quality_flags"]["cpam_pass1_failed"] = True
|
|
result["quality_flags"]["degraded_mode"] = True
|
|
|
|
# Vérifications
|
|
assert result["quality_flags"]["cpam_pass1_failed"] is True
|
|
assert result["quality_flags"]["degraded_mode"] is True
|
|
assert any("passe 1" in a and "dégradé" in a for a in dossier.alertes_codage)
|
|
|
|
def test_adversarial_same_model_sets_quality_flag_and_alert(self):
|
|
"""LOGIC-3 — modèles identiques → quality_flags + alerte dans dossier."""
|
|
dossier = DossierMedical(sejour=Sejour())
|
|
result: dict = {"conclusion": "test argument"}
|
|
|
|
old_cpam = OLLAMA_MODELS["cpam"]
|
|
old_val = OLLAMA_MODELS["validation"]
|
|
OLLAMA_MODELS["cpam"] = "same-test-model"
|
|
OLLAMA_MODELS["validation"] = "same-test-model"
|
|
try:
|
|
# Simule le flow exact de generate_cpam_response (lines 192-199)
|
|
same_model, model_msg = check_adversarial_model_config()
|
|
if same_model:
|
|
result.setdefault("quality_flags", {})
|
|
result["quality_flags"]["adversarial_disabled_same_model"] = True
|
|
dossier.alertes_codage.append(
|
|
"Validation adversariale désactivée (modèles identiques)"
|
|
)
|
|
|
|
assert same_model is True
|
|
assert result["quality_flags"]["adversarial_disabled_same_model"] is True
|
|
assert any("adversariale" in a and "identiques" in a
|
|
for a in dossier.alertes_codage)
|
|
finally:
|
|
OLLAMA_MODELS["cpam"] = old_cpam
|
|
OLLAMA_MODELS["validation"] = old_val
|
|
|
|
def test_no_flags_when_all_ok(self):
|
|
"""Pas de quality_flags quand tout fonctionne correctement."""
|
|
dossier = DossierMedical(sejour=Sejour())
|
|
result: dict = {"conclusion": "test argument"}
|
|
|
|
# Passe 1 OK
|
|
extraction = {"comprehension_contestation": "ok"}
|
|
degraded_pass1 = extraction is None
|
|
assert degraded_pass1 is False
|
|
|
|
# Modèles différents
|
|
old_cpam = OLLAMA_MODELS["cpam"]
|
|
old_val = OLLAMA_MODELS["validation"]
|
|
OLLAMA_MODELS["cpam"] = "model-a"
|
|
OLLAMA_MODELS["validation"] = "model-b"
|
|
try:
|
|
same_model, _ = check_adversarial_model_config()
|
|
assert same_model is False
|
|
finally:
|
|
OLLAMA_MODELS["cpam"] = old_cpam
|
|
OLLAMA_MODELS["validation"] = old_val
|
|
|
|
assert "quality_flags" not in result
|
|
assert len(dossier.alertes_codage) == 0
|