tests: CRH sections + DP diag bonus + case 74 regression + fusion propagation

- test_extraction: +21 tests (sections diag_sortie/diag_principal/synthese,
  variantes titres, terminaisons, faux positifs mid-sentence, biosynthèse)
- test_dp_selector: +55 tests (flags, candidates, scoring, hardening DIM,
  bonus +4/+2, evidence excerpt, cas 74 D50→I25.1 corrigé)
- test_fusion: +39 tests (propagation dp_selection evidence/reason/verdict,
  source 2e dossier, pas de crash si aucun DP)
- fixtures: case_74_min.json + 3 fixtures DP existantes

Aucun mock utilisé — données synthétiques uniquement.
Le test cas 74 passe : I25.1 gagne sur D50 grâce au bonus diag_sortie +4.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
dom
2026-02-24 13:28:54 +01:00
parent 2701efb1d2
commit 07c267539c
7 changed files with 1383 additions and 0 deletions

View File

@@ -6,6 +6,8 @@ from src.config import (
ActeCCAM,
Diagnostic,
DossierMedical,
DPCandidate,
DPSelection,
Sejour,
Traitement,
BiologieCle,
@@ -491,3 +493,161 @@ class TestSemanticDedup:
das_codes = {d.cim10_suggestion for d in result.diagnostics_associes}
assert "I10" not in das_codes
assert "I11.9" in das_codes
class TestDPSelectionPropagation:
"""Vérifie que dp_selection est propagée depuis le dossier source du DP retenu."""
def test_dp_selection_propagated_multi_dossier(self):
"""Fusion 2 dossiers : dp_selection vient du dossier dont le DP est retenu."""
sel = DPSelection(
chosen_index=0,
chosen_term="Pancréatite aiguë biliaire",
chosen_code="K85.1",
verdict="CONFIRMED",
confidence="high",
evidence=["Score 8.0 — source: regex (section forte)"],
reason="Écart net",
candidates=[DPCandidate(index=0, term="Pancréatite", code="K85.1",
section_strength=3, confidence="high")],
)
d1 = DossierMedical(
document_type="crh",
diagnostic_principal=Diagnostic(texte="Pancréatite aiguë biliaire",
cim10_suggestion="K85.1"),
dp_selection=sel,
)
d2 = DossierMedical(
document_type="trackare",
diagnostic_principal=Diagnostic(texte="Lithiase vésiculaire",
cim10_suggestion="K80.2"),
)
result = merge_dossiers([d1, d2])
# DP = K85.1 (plus spécifique) → dp_selection propagée depuis d1
assert result.diagnostic_principal.cim10_suggestion == "K85.1"
assert result.dp_selection is not None
assert result.dp_selection.chosen_code == "K85.1"
assert result.dp_selection.verdict == "CONFIRMED"
def test_dp_selection_none_when_no_source(self):
"""Si aucun dossier n'a de dp_selection, le fusionné non plus."""
d1 = DossierMedical(
diagnostic_principal=Diagnostic(texte="HTA", cim10_suggestion="I10"),
)
d2 = DossierMedical(
diagnostic_principal=Diagnostic(texte="HTA", cim10_suggestion="I10"),
)
result = merge_dossiers([d1, d2])
assert result.dp_selection is None
def test_dp_selection_single_dossier(self):
"""Dossier unique : dp_selection est conservée via model_copy."""
sel = DPSelection(
chosen_index=0,
chosen_term="Pneumopathie",
chosen_code="J18.9",
verdict="REVIEW",
confidence="medium",
)
d1 = DossierMedical(
diagnostic_principal=Diagnostic(texte="Pneumopathie", cim10_suggestion="J18.9"),
dp_selection=sel,
)
result = merge_dossiers([d1])
assert result.dp_selection is not None
assert result.dp_selection.verdict == "REVIEW"
def test_dp_selection_preserves_evidence_reason_verdict(self):
"""Fusion multi-docs : evidence, reason et verdict sont préservés intégralement."""
sel = DPSelection(
chosen_index=0,
chosen_term="Embolie pulmonaire",
chosen_code="I26.9",
verdict="CONFIRMED",
confidence="high",
evidence=[
"Score 9.0 — source: edsnlp",
"Diagnostic de sortie: «EP massive bilatérale»",
"Delta +5.0 vs Thrombose (I80.2)",
],
reason="Écart score 5.0 >= seuil 3.0",
candidates=[
DPCandidate(index=0, term="Embolie pulmonaire", code="I26.9",
section_strength=2, confidence="high", score=9.0,
score_details={"section": 2, "confidence": 3, "diag_section_bonus": 4}),
DPCandidate(index=1, term="Thrombose veineuse", code="I80.2",
section_strength=1, confidence="high", score=4.0),
],
debug_scores={"top1": 9.0, "top2": 4.0, "delta": 5.0},
)
d1 = DossierMedical(
document_type="crh",
diagnostic_principal=Diagnostic(texte="Embolie pulmonaire", cim10_suggestion="I26.9"),
dp_selection=sel,
)
d2 = DossierMedical(
document_type="trackare",
diagnostic_principal=Diagnostic(texte="TVP", cim10_suggestion="I80.2"),
)
result = merge_dossiers([d1, d2])
assert result.dp_selection is not None
rs = result.dp_selection
# Verdict/confidence/reason intacts
assert rs.verdict == "CONFIRMED"
assert rs.confidence == "high"
assert "5.0" in rs.reason
# Evidence complète (3 éléments)
assert len(rs.evidence) == 3
assert any("Diagnostic de sortie" in e for e in rs.evidence)
assert any("Delta" in e for e in rs.evidence)
# Candidates préservés avec score_details
assert len(rs.candidates) == 2
assert rs.candidates[0].score_details.get("diag_section_bonus") == 4
# Debug scores
assert rs.debug_scores["delta"] == 5.0
def test_dp_selection_from_second_dossier(self):
"""Si le DP retenu vient du 2e dossier, sa dp_selection est prise."""
sel_d2 = DPSelection(
chosen_index=0,
chosen_term="Sepsis",
chosen_code="A41.9",
verdict="CONFIRMED",
confidence="high",
evidence=["Score 7.0"],
reason="Candidat unique",
)
d1 = DossierMedical(
document_type="trackare",
diagnostic_principal=Diagnostic(texte="HTA", cim10_suggestion="I10"),
# Pas de dp_selection
)
d2 = DossierMedical(
document_type="crh",
diagnostic_principal=Diagnostic(texte="Sepsis à staphylocoque",
cim10_suggestion="A41.9"),
dp_selection=sel_d2,
)
result = merge_dossiers([d1, d2])
# A41.9 (5 chars) > I10 (3 chars) → DP = A41.9 venant de d2
assert result.diagnostic_principal.cim10_suggestion == "A41.9"
assert result.dp_selection is not None
assert result.dp_selection.chosen_code == "A41.9"
assert result.dp_selection.verdict == "CONFIRMED"
def test_dp_selection_no_crash_empty_dossiers(self):
"""Fusion de dossiers sans DP et sans dp_selection → pas de crash."""
d1 = DossierMedical(
diagnostics_associes=[
Diagnostic(texte="HTA", cim10_suggestion="I10"),
],
)
d2 = DossierMedical(
diagnostics_associes=[
Diagnostic(texte="Diabète", cim10_suggestion="E11.9"),
],
)
result = merge_dossiers([d1, d2])
assert result.dp_selection is None
assert result.diagnostic_principal is None