"""Tests unitaires pour le moteur de décisions (promotion DAS→DP).""" from unittest.mock import patch import pytest from src.config import CodeDecision, Diagnostic, DossierMedical from src.quality.decision_engine import ( _das_promotion_score, apply_decisions, decision_summaries, ) def _make_dossier(dp=None, das_list=None): """Helper : crée un DossierMedical minimal.""" d = DossierMedical() d.diagnostic_principal = dp d.diagnostics_associes = das_list or [] return d def _make_diag(texte, code, confidence="high", source="trackare", status=None, cim10_final=None): """Helper : crée un Diagnostic avec suggestion et optionnellement un final pré-rempli.""" return Diagnostic( texte=texte, cim10_suggestion=code, cim10_confidence=confidence, source=source, status=status, cim10_final=cim10_final, ) # --- Scoring --- class TestDasPromotionScore: def test_pathology_beats_symptom(self): patho = _make_diag("Pancréatite", "K85.9", cim10_final="K85.9") symptom = _make_diag("Douleur abdominale", "R10.4", cim10_final="R10.4") assert _das_promotion_score(patho) > _das_promotion_score(symptom) def test_symptom_beats_zcode(self): symptom = _make_diag("Douleur abdominale", "R10.4", cim10_final="R10.4") zcode = _make_diag("Antécédent", "Z87.1", cim10_final="Z87.1") assert _das_promotion_score(symptom) > _das_promotion_score(zcode) def test_high_confidence_beats_medium(self): high = _make_diag("Pancréatite", "K85.9", confidence="high", cim10_final="K85.9") med = _make_diag("Pancréatite", "K85.9", confidence="medium", cim10_final="K85.9") assert _das_promotion_score(high) > _das_promotion_score(med) def test_longer_code_more_specific(self): short = _make_diag("Pancréatite", "K85", cim10_final="K85") long = _make_diag("Pancréatite biliaire", "K85.1", cim10_final="K85.1") assert _das_promotion_score(long) > _das_promotion_score(short) # --- Promotion DAS→DP --- @patch("src.quality.decision_engine.load_reference_ranges", return_value={}) @patch("src.quality.decision_engine.load_bio_rules", return_value={}) class TestPromotionDasToDP: @patch("src.quality.decision_engine.rule_enabled", return_value=True) @patch("src.quality.decision_engine.cim10_validate", return_value=(True, "label")) def test_promote_best_das_when_no_dp(self, mock_validate, mock_rule, mock_bio, mock_ref): """DP absent + DAS valides → meilleur DAS promu (pathologie > symptôme > Z).""" das1 = _make_diag("Douleur abdominale", "R10.4", confidence="high") das2 = _make_diag("Pancréatite aiguë", "K85.9", confidence="high") das3 = _make_diag("Antécédent chirurgical", "Z87.1", confidence="medium") dossier = _make_dossier(dp=None, das_list=[das1, das2, das3]) apply_decisions(dossier) assert dossier.diagnostic_principal is not None assert dossier.diagnostic_principal.cim10_final == "K85.9" assert dossier.diagnostic_principal.cim10_decision.action == "PROMOTE_DP" assert "RULE-DAS-TO-DP" in dossier.diagnostic_principal.cim10_decision.applied_rules # Le DAS promu est retiré de la liste codes_das = [d.cim10_suggestion for d in dossier.diagnostics_associes] assert "K85.9" not in codes_das assert len(dossier.diagnostics_associes) == 2 @patch("src.quality.decision_engine.rule_enabled", return_value=True) @patch("src.quality.decision_engine.cim10_validate", return_value=(True, "label")) def test_no_promotion_when_dp_present(self, mock_validate, mock_rule, mock_bio, mock_ref): """DP déjà présent → pas de promotion.""" dp = _make_diag("Cholécystite aiguë", "K81.0", confidence="high") das1 = _make_diag("HTA", "I10", confidence="high") dossier = _make_dossier(dp=dp, das_list=[das1]) apply_decisions(dossier) assert dossier.diagnostic_principal.cim10_suggestion == "K81.0" assert len(dossier.diagnostics_associes) == 1 @patch("src.quality.decision_engine.rule_enabled", return_value=True) @patch("src.quality.decision_engine.cim10_validate", return_value=(True, "label")) def test_no_promotion_for_ruled_out(self, mock_validate, mock_rule, mock_bio, mock_ref): """DAS ruled_out ne doit pas être promu.""" das1 = _make_diag("Thrombopénie", "D69.6", status="ruled_out") dossier = _make_dossier(dp=None, das_list=[das1]) apply_decisions(dossier) # D69.6 est ruled_out donc cim10_final est None → pas candidat assert dossier.diagnostic_principal is None @patch("src.quality.decision_engine.rule_enabled", return_value=True) @patch("src.quality.decision_engine.cim10_validate", return_value=(False, None)) def test_no_promotion_without_cim10_final(self, mock_validate, mock_rule, mock_bio, mock_ref): """DAS sans cim10_final (code invalide) ne doit pas être promu.""" das1 = _make_diag("Diagnostic inconnu", "XXX.X") dossier = _make_dossier(dp=None, das_list=[das1]) apply_decisions(dossier) # Code invalide → cim10_final non rempli → pas candidat assert dossier.diagnostic_principal is None def test_no_promotion_when_rule_disabled(self, mock_bio, mock_ref): """RULE-DAS-TO-DP désactivée → pas de promotion.""" def _rule_enabled_selective(rule_id): if rule_id == "RULE-DAS-TO-DP": return False return True with patch("src.quality.decision_engine.rule_enabled", side_effect=_rule_enabled_selective): with patch("src.quality.decision_engine.cim10_validate", return_value=(True, "label")): das1 = _make_diag("Pancréatite aiguë", "K85.9", confidence="high") dossier = _make_dossier(dp=None, das_list=[das1]) apply_decisions(dossier) assert dossier.diagnostic_principal is None # --- Summary handler --- class TestDecisionSummaryPromoteDP: def test_promote_dp_summary(self): dp = _make_diag("Pancréatite aiguë", "K85.9", cim10_final="K85.9") dp.cim10_decision = CodeDecision( action="PROMOTE_DP", final_code="K85.9", applied_rules=["RULE-DAS-TO-DP"], ) dossier = _make_dossier(dp=dp) lines = decision_summaries(dossier) assert any("PROMOTE_DP" in line or "promu en DP" in line for line in lines) assert any("K85.9" in line for line in lines)