Initial commit

This commit is contained in:
Dom
2026-03-05 01:20:14 +01:00
commit 2163e574c1
184 changed files with 354881 additions and 0 deletions

View File

@@ -0,0 +1,382 @@
"""
Tests d'intégration pour le système de règles configurables.
Ce module teste l'intégration du RulesManager avec le PMSIValidator
et le Pipeline pour appliquer des règles spécifiques à l'établissement.
Exigences: 20.4
"""
import pytest
from datetime import datetime
from pathlib import Path
from unittest.mock import MagicMock
from pipeline_mco_pmsi.models.clinical import (
ClinicalDocument,
ClinicalFact,
Evidence,
Qualifier,
Span,
StructuredStay,
)
from pipeline_mco_pmsi.models.coding import Code, CodingProposal
from pipeline_mco_pmsi.models.metadata import ModelVersion, StayMetadata
from pipeline_mco_pmsi.rag.rag_engine import RAGEngine
from pipeline_mco_pmsi.rules.rules_manager import RulesManager
from pipeline_mco_pmsi.validators.pmsi_validator import PMSIValidator
@pytest.fixture
def mock_rag_engine():
"""Crée un mock du RAG Engine."""
engine = MagicMock(spec=RAGEngine)
engine.retrieve_eligibility_criteria.return_value = None
return engine
@pytest.fixture
def model_version():
"""Crée une version de modèle de test."""
return ModelVersion(
model_name="test-model",
model_tag="v1.0",
model_digest="a" * 64,
)
@pytest.fixture
def rules_manager_with_default_rules():
"""Crée un RulesManager avec les règles par défaut."""
manager = RulesManager()
manager.create_default_ruleset()
return manager
@pytest.fixture
def sample_stay():
"""Crée un séjour de test."""
doc = ClinicalDocument(
document_id="doc1",
document_type="cr_operatoire",
content="Patient avec diabète de type 2.",
metadata={},
creation_date=datetime.now(),
priority=1,
)
fact = ClinicalFact(
fact_id="fact1",
type="diagnostic",
text="diabète de type 2",
qualifier=Qualifier(
certainty="affirmé",
negation=False,
temporality="actuel",
confidence=0.9,
),
evidence=Evidence(
document_id="doc1",
span=Span(start=13, end=30),
text="diabète de type 2",
),
temporality="actuel",
confidence=0.9,
)
return StructuredStay(
stay_id="stay1",
documents=[doc],
sections=[],
facts=[fact],
)
@pytest.fixture
def sample_proposal(model_version):
"""Crée une proposition de codage de test."""
dp = Code(
code="E11.9",
type="dp",
label="Diabète sucré de type 2, sans complication",
confidence=0.9,
reasoning="Diabète de type 2 mentionné dans le CRO",
evidence=[
Evidence(
document_id="doc1",
span=Span(start=13, end=30),
text="diabète de type 2",
)
],
referentiel_version="2026",
)
return CodingProposal(
stay_id="stay1",
dp=dp,
dr=None,
das=[],
ccam=[],
reasoning="Proposition de test",
model_version=model_version,
prompt_version="test-prompt-1.0",
)
def test_pmsi_validator_without_rules_manager(mock_rag_engine, sample_proposal, sample_stay):
"""
Test que le PMSIValidator fonctionne sans RulesManager.
Exigences: 20.4
"""
validator = PMSIValidator(rag_engine=mock_rag_engine)
issues = validator.validate_proposal(sample_proposal, sample_stay)
# Devrait fonctionner sans erreur
assert isinstance(issues, list)
def test_pmsi_validator_with_rules_manager(
mock_rag_engine,
rules_manager_with_default_rules,
sample_proposal,
sample_stay,
):
"""
Test que le PMSIValidator applique les règles du RulesManager.
Exigences: 20.4
"""
validator = PMSIValidator(
rag_engine=mock_rag_engine,
rules_manager=rules_manager_with_default_rules,
)
issues = validator.validate_proposal(sample_proposal, sample_stay)
# Devrait fonctionner et appliquer les règles
assert isinstance(issues, list)
def test_rule_dp_with_evidence_applied(
mock_rag_engine,
rules_manager_with_default_rules,
model_version,
sample_stay,
):
"""
Test que la règle 'DP avec preuves' est appliquée.
Exigences: 20.4
"""
# Créer une proposition avec DP qui a une preuve minimale
# (Code requires at least 1 evidence)
dp_with_minimal_evidence = Code(
code="E11.9",
type="dp",
label="Diabète sucré de type 2",
confidence=0.9,
reasoning="Test",
evidence=[
Evidence(
document_id="doc1",
span=Span(start=0, end=1),
text="x", # Preuve minimale
)
],
referentiel_version="2026",
)
proposal = CodingProposal(
stay_id="stay1",
dp=dp_with_minimal_evidence,
dr=None,
das=[],
ccam=[],
reasoning="Test",
model_version=model_version,
prompt_version="test-prompt-1.0",
)
validator = PMSIValidator(
rag_engine=mock_rag_engine,
rules_manager=rules_manager_with_default_rules,
)
issues = validator.validate_proposal(proposal, sample_stay)
# Devrait valider sans erreur (a au moins une preuve)
assert isinstance(issues, list)
def test_rule_negated_fact_rejected(
mock_rag_engine,
rules_manager_with_default_rules,
model_version,
):
"""
Test que la règle 'Pas de codes pour faits niés' est appliquée.
Exigences: 20.4
"""
# Créer un fait nié
negated_fact = ClinicalFact(
fact_id="fact1",
type="diagnostic",
text="diabète",
qualifier=Qualifier(
certainty="nié",
negation=True,
temporality="actuel",
confidence=0.9,
),
evidence=Evidence(
document_id="doc1",
span=Span(start=0, end=7),
text="diabète",
),
temporality="actuel",
confidence=0.9,
)
stay = StructuredStay(
stay_id="stay1",
documents=[
ClinicalDocument(
document_id="doc1",
document_type="cr_operatoire",
content="Pas de diabète.",
metadata={},
creation_date=datetime.now(),
priority=1,
)
],
sections=[],
facts=[negated_fact],
)
# Créer une proposition qui code le fait nié
dp = Code(
code="E11.9",
type="dp",
label="Diabète",
confidence=0.9,
reasoning="Test",
evidence=[
Evidence(
document_id="doc1",
span=Span(start=0, end=7),
text="diabète",
)
],
referentiel_version="2026",
)
proposal = CodingProposal(
stay_id="stay1",
dp=dp,
dr=None,
das=[],
ccam=[],
reasoning="Test",
model_version=model_version,
prompt_version="test-prompt-1.0",
)
validator = PMSIValidator(
rag_engine=mock_rag_engine,
rules_manager=rules_manager_with_default_rules,
)
issues = validator.validate_proposal(proposal, stay)
# Devrait détecter l'erreur zéro-tolérance
blocking_issues = [i for i in issues if i.severity == "bloquant"]
assert len(blocking_issues) > 0
def test_conservative_mode_rules(
mock_rag_engine,
sample_stay,
):
"""
Test que le mode conservateur applique des règles strictes.
Exigences: 20.3
"""
# Créer un RulesManager en mode conservateur
manager = RulesManager()
ruleset = manager.create_default_ruleset()
assert ruleset.mode == "conservateur"
validator = PMSIValidator(
rag_engine=mock_rag_engine,
rules_manager=manager,
)
# Le mode conservateur devrait être actif
assert manager.is_conservative_mode()
assert not manager.is_aggressive_mode()
def test_rules_version_tracking(rules_manager_with_default_rules):
"""
Test que la version et le hash des règles sont trackés.
Exigences: 20.2
"""
version_info = rules_manager_with_default_rules.get_version_info()
assert "version" in version_info
assert "hash" in version_info
assert "mode" in version_info
assert "rules_count" in version_info
# Le hash doit être un SHA-256 (64 caractères hex)
assert len(version_info["hash"]) == 64
assert all(c in "0123456789abcdef" for c in version_info["hash"])
def test_rules_by_category(rules_manager_with_default_rules):
"""
Test la récupération de règles par catégorie.
Exigences: 20.1
"""
# Récupérer les règles DP
dp_rules = rules_manager_with_default_rules.get_rules_by_category("dp")
assert len(dp_rules) > 0
assert all(rule.category == "dp" for rule in dp_rules)
# Récupérer les règles de validation
validation_rules = rules_manager_with_default_rules.get_rules_by_category("validation")
assert len(validation_rules) > 0
assert all(rule.category == "validation" for rule in validation_rules)
# Récupérer les règles CCAM
ccam_rules = rules_manager_with_default_rules.get_rules_by_category("ccam")
assert len(ccam_rules) > 0
assert all(rule.category == "ccam" for rule in ccam_rules)
def test_rule_by_id(rules_manager_with_default_rules):
"""
Test la récupération d'une règle par son ID.
Exigences: 20.1
"""
# Récupérer une règle spécifique
rule = rules_manager_with_default_rules.get_rule_by_id("dp_001")
assert rule is not None
assert rule.rule_id == "dp_001"
assert rule.name == "DP obligatoire"
# Règle inexistante
nonexistent = rules_manager_with_default_rules.get_rule_by_id("nonexistent")
assert nonexistent is None
if __name__ == "__main__":
pytest.main([__file__, "-v"])