feat: fix extraction DP Trackare + 5 règles ATIH (veto engine)
- Fix DP : les diagnostics Trackare marqués "principal" ne sont plus filtrés par is_valid_diagnostic_text() (3 dossiers récupérés) - VETO-20 : Z code interdit en DP (sauf whitelist Z09/Z51/Z54/Z75...) - VETO-21 : Code R (symptôme) en DP → alerte CMD 23 - VETO-22 : Même catégorie 3 chars en DP+DAS (redondance) - VETO-23 : Exclusions mutuelles (E10↔E11, I10↔I11-I13) - VETO-24 : Lésion traumatique (S/T) sans cause externe (V/W/X/Y) - 24 tests unitaires, 699 tests passent sans régression Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -315,14 +315,17 @@ def _extract_diagnostics(
|
||||
# Diagnostics codés depuis Trackare (prioritaires)
|
||||
for diag in parsed.get("diagnostics", []):
|
||||
texte = clean_diagnostic_text(diag.get("libelle", ""))
|
||||
if not is_valid_diagnostic_text(texte):
|
||||
is_principal = diag.get("type", "").lower() == "principal"
|
||||
# Le DP Trackare est toujours accepté (pré-codé avec CIM-10 validé).
|
||||
# Seuls les DAS passent le filtre anti-bruit.
|
||||
if not is_principal and not is_valid_diagnostic_text(texte):
|
||||
continue
|
||||
d = Diagnostic(
|
||||
texte=texte,
|
||||
cim10_suggestion=diag.get("code_cim10"),
|
||||
source="trackare",
|
||||
)
|
||||
if diag.get("type", "").lower() == "principal":
|
||||
if is_principal:
|
||||
dossier.diagnostic_principal = d
|
||||
else:
|
||||
dossier.diagnostics_associes.append(d)
|
||||
|
||||
@@ -372,6 +372,84 @@ def apply_vetos(dossier: DossierMedical) -> VetoReport:
|
||||
if dp and dp.cim10_suggestion and _overconf(dp):
|
||||
add("VETO-12", "HARD", "diagnostic_principal", f"DP {dp.cim10_suggestion} en high sans preuve")
|
||||
|
||||
# -------------------------------------------------
|
||||
# VETO-20 : Z code interdit en DP (sauf whitelist ATIH)
|
||||
# Règle PMSI : les codes Z ne sont autorisés en DP que pour un
|
||||
# nombre limité de motifs (chimiothérapie, suivi post-traitement, etc.)
|
||||
# -------------------------------------------------
|
||||
_Z_DP_WHITELIST = {"Z09", "Z51", "Z54", "Z75", "Z03", "Z04", "Z38", "Z50", "Z08"}
|
||||
if dp and dp.cim10_suggestion and str(dp.cim10_suggestion).startswith("Z"):
|
||||
z3 = str(dp.cim10_suggestion)[:3]
|
||||
if z3 not in _Z_DP_WHITELIST:
|
||||
add("VETO-20", "MEDIUM", "diagnostic_principal",
|
||||
f"DP {dp.cim10_suggestion} est un code Z interdit en DP (catégorie {z3}). "
|
||||
"Les codes Z ne sont autorisés en DP que pour certains motifs (Z51 chimio, Z09 suivi, etc.).")
|
||||
|
||||
# -------------------------------------------------
|
||||
# VETO-21 : Code R (symptôme) en DP → CMD 23, tarification faible
|
||||
# Règle PMSI : un symptôme en DP indique un bilan incomplet.
|
||||
# -------------------------------------------------
|
||||
if dp and dp.cim10_suggestion and str(dp.cim10_suggestion).startswith("R"):
|
||||
# Vérifier si un diagnostic précis existe dans les DAS
|
||||
has_precise = any(
|
||||
das.cim10_suggestion and not str(das.cim10_suggestion).startswith(("R", "Z"))
|
||||
and not _is_ruled_out(das)
|
||||
for das in dossier.diagnostics_associes
|
||||
)
|
||||
severity = "LOW" if has_precise else "MEDIUM"
|
||||
add("VETO-21", severity, "diagnostic_principal",
|
||||
f"DP {dp.cim10_suggestion} est un code symptôme (chapitre R) → CMD 23. "
|
||||
"Un diagnostic étiologique précis devrait être recherché comme DP.")
|
||||
|
||||
# -------------------------------------------------
|
||||
# VETO-22 : Même catégorie CIM-10 3 chars en DP + DAS
|
||||
# Règle PMSI : redondance de codage suspecte.
|
||||
# -------------------------------------------------
|
||||
if dp and dp.cim10_suggestion and len(str(dp.cim10_suggestion)) >= 3:
|
||||
dp_cat = str(dp.cim10_suggestion)[:3]
|
||||
for i, das in enumerate(dossier.diagnostics_associes):
|
||||
if _is_ruled_out(das):
|
||||
continue
|
||||
if das.cim10_suggestion and len(str(das.cim10_suggestion)) >= 3:
|
||||
das_cat = str(das.cim10_suggestion)[:3]
|
||||
if das_cat == dp_cat and das.cim10_suggestion != dp.cim10_suggestion:
|
||||
add("VETO-22", "LOW", f"diagnostics_associes[{i}]",
|
||||
f"DAS {das.cim10_suggestion} même catégorie que DP {dp.cim10_suggestion} "
|
||||
f"({dp_cat}). Vérifier si la sous-catégorie DAS est pertinente ou redondante.")
|
||||
|
||||
# -------------------------------------------------
|
||||
# VETO-23 : Exclusions mutuelles (diabète type 1 vs type 2, HTA)
|
||||
# Règle PMSI : codes incompatibles dans le même séjour.
|
||||
# -------------------------------------------------
|
||||
all_codes = set()
|
||||
if dp and dp.cim10_suggestion:
|
||||
all_codes.add(str(dp.cim10_suggestion)[:3])
|
||||
for das in dossier.diagnostics_associes:
|
||||
if not _is_ruled_out(das) and das.cim10_suggestion:
|
||||
all_codes.add(str(das.cim10_suggestion)[:3])
|
||||
|
||||
_MUTUAL_EXCLUSIONS = [
|
||||
({"E10"}, {"E11"}, "Diabète type 1 (E10) et type 2 (E11) mutuellement exclusifs"),
|
||||
({"I10"}, {"I11", "I12", "I13"}, "HTA essentielle (I10) incompatible avec HTA secondaire (I11/I12/I13)"),
|
||||
]
|
||||
for group_a, group_b, msg in _MUTUAL_EXCLUSIONS:
|
||||
if (all_codes & group_a) and (all_codes & group_b):
|
||||
add("VETO-23", "MEDIUM", "diagnostics_associes", msg)
|
||||
|
||||
# -------------------------------------------------
|
||||
# VETO-24 : Lésion traumatique (S/T) sans cause externe (V/W/X/Y)
|
||||
# Règle PMSI : les codes de lésion doivent être associés
|
||||
# à un code de cause externe.
|
||||
# -------------------------------------------------
|
||||
has_injury = any(
|
||||
str(c).startswith(("S", "T")) and not str(c).startswith(("T80", "T81", "T82", "T83", "T84", "T85", "T86", "T87", "T88"))
|
||||
for c in all_codes
|
||||
)
|
||||
has_external = any(str(c).startswith(("V", "W", "X", "Y")) for c in all_codes)
|
||||
if has_injury and not has_external:
|
||||
add("VETO-24", "LOW", "diagnostics_associes",
|
||||
"Lésion traumatique (S/T) sans code de cause externe (V/W/X/Y). "
|
||||
"La réglementation PMSI exige un code de circonstance pour les traumatismes.")
|
||||
|
||||
# -------------------------------------------------
|
||||
# Post-traitement : si un veto HARD existe pour un même 'where',
|
||||
|
||||
Reference in New Issue
Block a user