Spaces:
Sleeping
Sleeping
File size: 3,538 Bytes
2043afa | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 | """Local DDI and guideline simulation using CSV lookup data."""
from __future__ import annotations
from dataclasses import dataclass
from typing import Dict, List, Optional, Tuple
from .data_loader import (
BeersCriterion,
DDIRule,
DrugMeta,
load_beers_criteria,
load_ddi_rules,
load_drug_metadata,
)
@dataclass(frozen=True)
class DDIResult:
severity: str
recommendation: str
base_risk_score: float
_NO_INTERACTION = DDIResult(severity="none", recommendation="no_action", base_risk_score=0.0)
class DDISimulator:
"""Provides drug–drug interaction and Beers-criteria lookups."""
def __init__(self) -> None:
self._ddi_rules: Dict[Tuple[str, str], DDIRule] = load_ddi_rules()
self._drug_meta: Dict[str, DrugMeta] = load_drug_metadata()
self._beers: List[BeersCriterion] = load_beers_criteria()
@staticmethod
def _normalise_pair(a: str, b: str) -> Tuple[str, str]:
return (a, b) if a < b else (b, a)
def lookup_ddi(self, drug_id_1: str, drug_id_2: str) -> DDIResult:
key = self._normalise_pair(drug_id_1, drug_id_2)
rule = self._ddi_rules.get(key)
if rule is None:
return _NO_INTERACTION
return DDIResult(
severity=rule.severity,
recommendation=rule.recommendation,
base_risk_score=rule.base_risk_score,
)
def get_beers_flags(
self,
drug_id: str,
patient_conditions: List[str],
) -> List[str]:
"""Return list of Beers flags applicable to *drug_id* given patient conditions."""
flags: List[str] = []
for bc in self._beers:
if bc.drug_id != drug_id:
continue
if bc.condition is None:
flags.append(bc.criterion_type)
elif bc.condition in patient_conditions:
flags.append(f"{bc.criterion_type}_{bc.condition}")
return flags
def get_drug_meta(self, drug_id: str) -> Optional[DrugMeta]:
return self._drug_meta.get(drug_id)
def find_substitute(
self,
drug_id: str,
current_drug_ids: List[str],
) -> Optional[str]:
"""Find a safer same-class substitute not already in the regimen."""
meta = self._drug_meta.get(drug_id)
if meta is None:
return None
candidates = [
dm
for dm in self._drug_meta.values()
if (
dm.atc_class == meta.atc_class
and dm.drug_id != drug_id
and dm.drug_id not in current_drug_ids
and not dm.is_high_risk_elderly
)
]
if not candidates:
return None
# Pick the candidate with fewest severe DDIs with current regimen
def _severe_count(cand: DrugMeta) -> int:
count = 0
for did in current_drug_ids:
if did == drug_id:
continue
r = self.lookup_ddi(cand.drug_id, did)
if r.severity == "severe":
count += 1
return count
candidates.sort(key=lambda c: (_severe_count(c), c.drug_id))
return candidates[0].drug_id
@property
def drug_metadata(self) -> Dict[str, DrugMeta]:
return self._drug_meta
@property
def ddi_rules(self) -> Dict[Tuple[str, str], DDIRule]:
return self._ddi_rules
@property
def beers_criteria(self) -> List[BeersCriterion]:
return self._beers
|