Pulse_ER_env / server /pulse_engine_adapter.py
KChad's picture
Add all docs_assets image assets to Hugging Face Space snapshot
9b1756a
"""Server-side adapter around the locally built Pulse physiology engine."""
from __future__ import annotations
import math
import os
import re
import sys
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any, Iterable, Sequence
from pulse_physiology_env.patient_state import (
ArterialBloodGasResult,
BasicMetabolicPanelResult,
CompleteBloodCountResult,
MentalStatus,
PatientState,
ScenarioDifficulty,
)
_REPO_ROOT = Path(__file__).resolve().parents[2]
_DEFAULT_INSTALL_DIR = _REPO_ROOT / "engine-build" / "install"
def _bootstrap_pulse_paths(explicit_install_dir: Path | None = None) -> Path:
candidates: list[Path] = []
if explicit_install_dir is not None:
candidates.append(Path(explicit_install_dir))
env_install_dir = os.getenv("PULSE_INSTALL_DIR")
if env_install_dir:
candidates.append(Path(env_install_dir))
candidates.append(_DEFAULT_INSTALL_DIR)
checked: list[str] = []
for candidate in candidates:
resolved = candidate.expanduser().resolve()
checked.append(str(resolved))
bin_dir = resolved / "bin"
python_dir = resolved / "python"
if not (bin_dir.exists() and python_dir.exists()):
continue
for path in (str(bin_dir), str(python_dir)):
if path not in sys.path:
sys.path.insert(0, path)
return resolved
raise RuntimeError(
"Could not locate a Pulse install with both 'bin' and 'python' folders. "
f"Checked: {', '.join(checked)}"
)
_PULSE_IMPORT_ERROR: Exception | None = None
_BOOTSTRAP_INSTALL_DIR: Path | None = None
try:
_BOOTSTRAP_INSTALL_DIR = _bootstrap_pulse_paths()
from pulse.engine.PulseEngine import PulseEngine
from pulse.cdm.bag_valve_mask_actions import SEBagValveMaskAutomated, SEBagValveMaskConfiguration
from pulse.cdm.engine import SEAction, SEDataRequest, SEDataRequestManager, eGate, eSide, eSwitch
from pulse.cdm.mechanical_ventilator_actions import (
SEMechanicalVentilatorContinuousPositiveAirwayPressure,
SEMechanicalVentilatorPressureControl,
SEMechanicalVentilatorVolumeControl,
)
from pulse.cdm.patient_actions import (
SEArrhythmia,
SEChestCompressionAutomated,
SEHemorrhage,
SEIntubation,
SENeedleDecompression,
SEPericardialEffusion,
SESubstanceBolus,
SESubstanceCompoundInfusion,
SESubstanceInfusion,
SESupplementalOxygen,
SETensionPneumothorax,
eDevice,
eHemorrhage_Compartment,
eHemorrhage_Type,
eHeartRhythm,
eIntubationType,
)
from pulse.cdm.scalars import (
AmountPerVolumeUnit,
FrequencyUnit,
LengthUnit,
MassPerVolumeUnit,
MassUnit,
PressureTimePerVolumeUnit,
PressureUnit,
TemperatureUnit,
TimeUnit,
VolumePerPressureUnit,
VolumePerTimeUnit,
VolumeUnit,
)
except Exception as exc: # pragma: no cover - exercised only on misconfigured hosts
_PULSE_IMPORT_ERROR = exc
class _MissingPulseEnum:
name = "missing"
def __getattr__(self, _name: str):
return self
_missing_enum = _MissingPulseEnum()
PulseEngine = None
SEBagValveMaskAutomated = None
SEBagValveMaskConfiguration = None
SEAction = None
SEDataRequest = None
SEDataRequestManager = None
eGate = _missing_enum
eSide = _missing_enum
eSwitch = _missing_enum
SEMechanicalVentilatorContinuousPositiveAirwayPressure = None
SEMechanicalVentilatorPressureControl = None
SEMechanicalVentilatorVolumeControl = None
SEArrhythmia = None
SEChestCompressionAutomated = None
SEHemorrhage = None
SEIntubation = None
SENeedleDecompression = None
SEPericardialEffusion = None
SESubstanceBolus = None
SESubstanceCompoundInfusion = None
SESubstanceInfusion = None
SESupplementalOxygen = None
SETensionPneumothorax = None
eDevice = _missing_enum
eHemorrhage_Compartment = _missing_enum
eHemorrhage_Type = _missing_enum
eHeartRhythm = _missing_enum
eIntubationType = _missing_enum
AmountPerVolumeUnit = _missing_enum
FrequencyUnit = _missing_enum
LengthUnit = _missing_enum
MassPerVolumeUnit = _missing_enum
MassUnit = _missing_enum
PressureTimePerVolumeUnit = _missing_enum
PressureUnit = _missing_enum
TemperatureUnit = _missing_enum
TimeUnit = _missing_enum
VolumePerPressureUnit = _missing_enum
VolumePerTimeUnit = _missing_enum
VolumeUnit = _missing_enum
def _snake_case(value: str) -> str:
compact = value.split(".")[-1].strip()
first_pass = re.sub(r"(.)([A-Z][a-z]+)", r"\1_\2", compact)
return re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", first_pass).replace("__", "_").lower()
@dataclass
class _RuntimeContext:
state_file: Path | None = None
scenario_id: str = "baseline"
scenario_difficulty: ScenarioDifficulty = "medium"
patient_id: str = "standard_male"
action_budget_remaining: int | None = None
pending_diagnostics: dict[str, int] = field(default_factory=dict)
ready_diagnostics: set[str] = field(default_factory=set)
active_infusions: dict[str, float] = field(default_factory=dict)
active_hemorrhages: dict[str, float] = field(default_factory=dict)
active_tension_pneumothorax_sides: set[str] = field(default_factory=set)
pericardial_effusion_rate_ml_per_min: float | None = None
pain_sources: dict[str, float] = field(default_factory=dict)
intubation_type: str = "Off"
patient_position: str = "supine"
oxygen_device: str | None = None
oxygen_flow_lpm: float | None = None
airway_support_mode: str | None = None
fio2: float | None = None
peep_cmh2o: float | None = None
ventilator_respiration_rate_bpm: float | None = None
ventilator_tidal_volume_ml: float | None = None
ventilator_inspiratory_pressure_cmh2o: float | None = None
baseline_blood_volume_ml: float | None = None
class PulseEngineAdapter:
"""Loads Pulse states, advances simulation time, and synthesizes PatientState."""
DEFAULT_STATE_FILENAME = "StandardMale@0s.json"
DEFAULT_OXYGEN_SUPPLY_L = 1000.0
DEFAULT_PATIENT_POSITION = "supine"
DEFAULT_BVM_RESPIRATION_RATE_BPM = 12.0
DEFAULT_BVM_IE_RATIO = 0.5
DEFAULT_BVM_SQUEEZE_PRESSURE_CMH2O = 12.0
DEFAULT_BVM_FIO2 = 0.6
DEFAULT_CPAP_FIO2 = 0.6
DEFAULT_CPAP_PEEP_CMH2O = 5.0
DEFAULT_CPAP_PRESSURE_SUPPORT_CMH2O = 5.0
DEFAULT_PC_FIO2 = 0.7
DEFAULT_PC_PEEP_CMH2O = 5.0
DEFAULT_PC_RESPIRATION_RATE_BPM = 14.0
DEFAULT_PC_INSPIRATORY_PRESSURE_CMH2O = 16.0
DEFAULT_PC_INSPIRATORY_PERIOD_S = 1.0
DEFAULT_VC_FIO2 = 0.7
DEFAULT_VC_PEEP_CMH2O = 5.0
DEFAULT_VC_RESPIRATION_RATE_BPM = 14.0
DEFAULT_VC_TIDAL_VOLUME_ML = 450.0
DEFAULT_CPR_RATE_BPM = 100.0
DEFAULT_CPR_DEPTH_CM = 5.0
DEFAULT_PRESSOR_MONITOR_SECONDS = 180.0
DEFAULT_PERICARDIOCENTESIS_DRAIN_RATE_ML_PER_MIN = 150.0
DIAGNOSTIC_DELAYS_S = {
"get_blood_gas": 120,
"get_cbc": 240,
"get_bmp": 300,
"order_arterial_blood_gas": 120,
"order_complete_blood_count": 240,
"order_basic_metabolic_panel": 300,
"order_point_of_care_ultrasound": 120,
}
_AIRWAY_SUPPORT_DISPLAY = {
"bag_valve_mask": "bag_valve_mask",
"cpap": "cpap",
"pressure_control_ventilation": "pressure_control_ventilation",
"volume_control_ventilation": "volume_control_ventilation",
}
_FLUID_COMPOUND_MAP = {
"saline": "Saline",
"blood": "Blood",
"packed_rbc": "PackedRBC",
"packed_rbcs": "PackedRBC",
"prbc": "PackedRBC",
"prbcs": "PackedRBC",
}
_PRESSOR_CONFIG = {
"norepinephrine": {
"substance": "Norepinephrine",
"default_concentration_ug_per_ml": 1.0,
"default_rate_ml_per_min": 7.8,
},
"phenylephrine": {
"substance": "Phenylephrine",
"default_concentration_ug_per_ml": 10.0,
"default_rate_ml_per_min": 7.68,
},
}
_SUPPORTED_COMPOUNDS = frozenset({"Saline", "Blood", "PackedRBC"})
_BOLUS_DRUG_CONFIG: dict[str, dict[str, Any]] = {
"epinephrine": {
"substance": "Epinephrine",
"dose_argument": "mg",
"concentration_value": 1.0,
"concentration_unit": MassPerVolumeUnit.mg_Per_mL,
"duration_s": 30.0,
},
"morphine": {
"substance": "Morphine",
"dose_argument": "mg",
"concentration_value": 10.0,
"concentration_unit": MassPerVolumeUnit.mg_Per_mL,
"duration_s": 60.0,
},
"ketamine": {
"substance": "Ketamine",
"dose_argument": "mg_per_kg",
"concentration_value": 50.0,
"concentration_unit": MassPerVolumeUnit.mg_Per_mL,
"duration_s": 45.0,
},
"midazolam": {
"substance": "Midazolam",
"dose_argument": "mg",
"concentration_value": 1.0,
"concentration_unit": MassPerVolumeUnit.mg_Per_mL,
"duration_s": 60.0,
},
"lorazepam": {
"substance": "Lorazepam",
"dose_argument": "mg",
"concentration_value": 2.0,
"concentration_unit": MassPerVolumeUnit.mg_Per_mL,
"duration_s": 90.0,
},
"succinylcholine": {
"substance": "Succinylcholine",
"dose_argument": "mg_per_kg",
"concentration_value": 20.0,
"concentration_unit": MassPerVolumeUnit.mg_Per_mL,
"duration_s": 30.0,
},
"phenylephrine": {
"substance": "Phenylephrine",
"dose_argument": "mcg",
"concentration_value": 100.0,
"concentration_unit": MassPerVolumeUnit.ug_Per_mL,
"duration_s": 30.0,
},
}
_SODIUM_MOLAR_MASS = 22.98976928
_POTASSIUM_MOLAR_MASS = 39.0983
_CALCIUM_MOLAR_MASS = 40.078
_OXYGEN_DEVICE_MAP = {
"nasal_cannula": eDevice.NasalCannula,
"simple_mask": eDevice.SimpleMask,
"non_rebreather_mask": eDevice.NonRebreatherMask,
}
_DEFAULT_OXYGEN_FLOW_LPM = {
"nasal_cannula": 4.0,
"simple_mask": 8.0,
"non_rebreather_mask": 15.0,
}
_SIDE_MAP = {
"left": eSide.Left,
"right": eSide.Right,
}
_PNEUMOTHORAX_TYPE_MAP = {
"open": eGate.Open,
"closed": eGate.Closed,
}
_HEMORRHAGE_TYPE_MAP = {
"external": eHemorrhage_Type.External,
"internal": eHemorrhage_Type.Internal,
}
_INTUBATION_TYPE_MAP = {
"off": eIntubationType.Off,
"oropharyngeal": eIntubationType.Oropharyngeal,
"oropharyngeal_airway": eIntubationType.Oropharyngeal,
"nasopharyngeal": eIntubationType.Nasopharyngeal,
"nasopharyngeal_airway": eIntubationType.Nasopharyngeal,
"tracheal": eIntubationType.Tracheal,
"endotracheal": eIntubationType.Tracheal,
"endotracheal_tube": eIntubationType.Tracheal,
"esophageal": eIntubationType.Esophageal,
"left_mainstem": eIntubationType.LeftMainstem,
"right_mainstem": eIntubationType.RightMainstem,
}
_AIRWAY_SUPPORT_NAMES = {
eIntubationType.Off.name: None,
eIntubationType.Oropharyngeal.name: "oropharyngeal_airway",
eIntubationType.Nasopharyngeal.name: "nasopharyngeal_airway",
eIntubationType.Tracheal.name: "endotracheal_tube",
eIntubationType.Esophageal.name: "esophageal_intubation",
eIntubationType.LeftMainstem.name: "left_mainstem_intubation",
eIntubationType.RightMainstem.name: "right_mainstem_intubation",
}
_VALID_POSITIONS = {
"supine",
"upright",
"trendelenburg",
"recovery",
"semi_fowler",
}
_HEMORRHAGE_COMPARTMENT_MAP = {
"aorta": eHemorrhage_Compartment.Aorta,
"brain": eHemorrhage_Compartment.Brain,
"muscle": eHemorrhage_Compartment.Muscle,
"large_intestine": eHemorrhage_Compartment.LargeIntestine,
"left_arm": eHemorrhage_Compartment.LeftArm,
"left_kidney": eHemorrhage_Compartment.LeftKidney,
"left_leg": eHemorrhage_Compartment.LeftLeg,
"liver": eHemorrhage_Compartment.Liver,
"right_arm": eHemorrhage_Compartment.RightArm,
"right_kidney": eHemorrhage_Compartment.RightKidney,
"right_leg": eHemorrhage_Compartment.RightLeg,
"skin": eHemorrhage_Compartment.Skin,
"small_intestine": eHemorrhage_Compartment.SmallIntestine,
"splanchnic": eHemorrhage_Compartment.Splanchnic,
"spleen": eHemorrhage_Compartment.Spleen,
"vena_cava": eHemorrhage_Compartment.VenaCava,
}
def __init__(
self,
install_dir: str | Path | None = None,
default_state_file: str | Path | None = None,
*,
log_to_console: bool = False,
action_budget_remaining: int | None = None,
) -> None:
if _PULSE_IMPORT_ERROR is not None:
raise RuntimeError(
"Pulse Python bindings could not be imported. "
"Make sure the local Pulse build exists under engine-build/install "
"or set PULSE_INSTALL_DIR."
) from _PULSE_IMPORT_ERROR
self._install_dir = _bootstrap_pulse_paths(
Path(install_dir) if install_dir is not None else _BOOTSTRAP_INSTALL_DIR
)
self._bin_dir = self._install_dir / "bin"
self._states_dir = self._bin_dir / "states"
self._default_state_file = self._resolve_state_file(default_state_file)
self._log_to_console = log_to_console
self._default_action_budget_remaining = action_budget_remaining
self._engine: PulseEngine | None = None
self._data_request_keys, requests = self._build_data_requests()
self._data_request_mgr = SEDataRequestManager(requests)
self._latest_raw_metrics: dict[str, float | None] = {}
self._last_lactate_mg_per_dl: float | None = None
self._runtime = _RuntimeContext(action_budget_remaining=action_budget_remaining)
def load_patient(
self,
state_file: str | Path | None = None,
*,
scenario_id: str = "baseline",
scenario_difficulty: ScenarioDifficulty = "medium",
patient_id: str | None = None,
initial_actions: Sequence[SEAction] | None = None,
action_budget_remaining: int | None = None,
) -> PatientState:
"""Load a baseline Pulse state into a fresh engine instance."""
resolved_state_file = (
self._default_state_file if state_file is None else self._resolve_state_file(state_file)
)
self.close()
self._engine = PulseEngine(data_root_dir=str(self._bin_dir))
self._engine.log_to_console(self._log_to_console)
if not self._engine.serialize_from_file(str(resolved_state_file), self._data_request_mgr):
raise RuntimeError(f"Pulse failed to load state file: {resolved_state_file}")
resolved_patient_id = patient_id or self._default_patient_id_from_state_file(resolved_state_file)
self._runtime = _RuntimeContext(
state_file=resolved_state_file,
scenario_id=scenario_id,
scenario_difficulty=scenario_difficulty,
patient_id=resolved_patient_id,
action_budget_remaining=(
self._default_action_budget_remaining
if action_budget_remaining is None
else action_budget_remaining
),
patient_position=self.DEFAULT_PATIENT_POSITION,
)
self._refresh_raw_metrics()
self._runtime.baseline_blood_volume_ml = self._latest_raw_metrics.get("blood_volume_ml")
self._last_lactate_mg_per_dl = self._latest_raw_metrics.get("lactate_mg_per_dl")
if initial_actions:
return self.apply_actions(initial_actions)
return self.get_full_state()
def set_scenario_context(
self,
*,
scenario_id: str,
scenario_difficulty: ScenarioDifficulty | None = None,
patient_id: str | None = None,
) -> PatientState:
"""Update scenario metadata without reloading the engine."""
self._runtime.scenario_id = scenario_id
if scenario_difficulty is not None:
self._runtime.scenario_difficulty = scenario_difficulty
if patient_id is not None:
self._runtime.patient_id = patient_id
return self.get_full_state()
def set_patient_position(self, position: str) -> PatientState:
"""Update the tracked patient position for downstream decision support."""
canonical = position.strip().lower().replace("-", "_").replace(" ", "_")
if canonical not in self._VALID_POSITIONS:
valid = ", ".join(sorted(self._VALID_POSITIONS))
raise ValueError(f"Unsupported patient position '{position}'. Expected one of: {valid}")
self._runtime.patient_position = canonical
return self.get_full_state()
def apply_supplemental_oxygen(
self,
*,
device: str,
flow_lpm: float | None = None,
supply_volume_l: float = DEFAULT_OXYGEN_SUPPLY_L,
advance_time_seconds: float | None = 60.0,
) -> PatientState:
"""Apply supplemental oxygen using a Pulse native oxygen action."""
device_key = device.strip().lower().replace("-", "_").replace(" ", "_")
if device_key not in self._OXYGEN_DEVICE_MAP:
valid = ", ".join(sorted(self._OXYGEN_DEVICE_MAP))
raise ValueError(f"Unsupported oxygen device '{device}'. Expected one of: {valid}")
resolved_flow_lpm = flow_lpm or self._DEFAULT_OXYGEN_FLOW_LPM[device_key]
if resolved_flow_lpm <= 0:
raise ValueError("flow_lpm must be greater than zero.")
if supply_volume_l <= 0:
raise ValueError("supply_volume_l must be greater than zero.")
action = SESupplementalOxygen()
action.set_device(self._OXYGEN_DEVICE_MAP[device_key])
action.get_flow().set_value(resolved_flow_lpm, VolumePerTimeUnit.L_Per_min)
action.get_volume().set_value(supply_volume_l, VolumeUnit.L)
return self.apply_actions([action], advance_time_seconds=advance_time_seconds)
def resolve_fluid_compound(self, fluid_type: str) -> str:
fluid_key = fluid_type.strip().lower().replace("-", "_").replace(" ", "_")
if fluid_key not in self._FLUID_COMPOUND_MAP:
valid = ", ".join(sorted(self._FLUID_COMPOUND_MAP))
raise ValueError(f"Unsupported fluid_type '{fluid_type}'. Expected one of: {valid}")
return self._FLUID_COMPOUND_MAP[fluid_key]
def infuse_compound(
self,
*,
compound: str,
bag_volume_ml: float,
rate_ml_per_min: float,
advance_time_seconds: float | None = None,
) -> PatientState:
"""Administer a Pulse compound infusion such as saline or blood."""
if not compound or not compound.strip():
raise ValueError("compound must be a non-empty string.")
if bag_volume_ml <= 0:
raise ValueError("bag_volume_ml must be greater than zero.")
if rate_ml_per_min <= 0:
raise ValueError("rate_ml_per_min must be greater than zero.")
action = SESubstanceCompoundInfusion()
action.set_compound(compound.strip())
action.get_bag_volume().set_value(bag_volume_ml, VolumeUnit.mL)
action.get_rate().set_value(rate_ml_per_min, VolumePerTimeUnit.mL_Per_min)
delivery_duration_s = (bag_volume_ml / rate_ml_per_min) * 60.0
monitor_window_s = delivery_duration_s if advance_time_seconds is None else advance_time_seconds
state = self.apply_actions([action], advance_time_seconds=monitor_window_s)
if monitor_window_s >= delivery_duration_s:
self._runtime.active_infusions.pop(_snake_case(compound), None)
state = self.get_full_state()
return state
def set_pressor(
self,
*,
pressor: str,
concentration_ug_per_ml: float | None = None,
rate_ml_per_min: float | None = None,
advance_time_seconds: float | None = DEFAULT_PRESSOR_MONITOR_SECONDS,
) -> PatientState:
"""Start, titrate, or stop a vasoactive infusion using Pulse's native drug action."""
pressor_key = pressor.strip().lower().replace("-", "_").replace(" ", "_")
if pressor_key not in self._PRESSOR_CONFIG:
valid = ", ".join(sorted(self._PRESSOR_CONFIG))
raise ValueError(f"Unsupported pressor '{pressor}'. Expected one of: {valid}")
config = self._PRESSOR_CONFIG[pressor_key]
resolved_concentration = (
config["default_concentration_ug_per_ml"]
if concentration_ug_per_ml is None
else concentration_ug_per_ml
)
resolved_rate = config["default_rate_ml_per_min"] if rate_ml_per_min is None else rate_ml_per_min
if resolved_concentration <= 0:
raise ValueError("concentration_ug_per_ml must be greater than zero.")
if resolved_rate < 0:
raise ValueError("rate_ml_per_min cannot be negative.")
action = SESubstanceInfusion()
action.set_substance(config["substance"])
action.get_concentration().set_value(resolved_concentration, MassPerVolumeUnit.ug_Per_mL)
action.get_rate().set_value(resolved_rate, VolumePerTimeUnit.mL_Per_min)
return self.apply_actions([action], advance_time_seconds=advance_time_seconds)
def set_pericardial_effusion(
self,
*,
effusion_rate_ml_per_min: float,
advance_time_seconds: float | None = 60.0,
) -> PatientState:
"""Create, stop, or drain pericardial fluid using Pulse's native effusion action."""
action = SEPericardialEffusion()
action.get_effusion_rate().set_value(effusion_rate_ml_per_min, VolumePerTimeUnit.mL_Per_min)
return self.apply_actions([action], advance_time_seconds=advance_time_seconds)
def perform_pericardiocentesis(
self,
*,
drain_rate_ml_per_min: float = DEFAULT_PERICARDIOCENTESIS_DRAIN_RATE_ML_PER_MIN,
advance_time_seconds: float | None = 180.0,
) -> PatientState:
"""Drain pericardial fluid using a negative effusion rate."""
if drain_rate_ml_per_min <= 0:
raise ValueError("drain_rate_ml_per_min must be greater than zero.")
return self.set_pericardial_effusion(
effusion_rate_ml_per_min=-drain_rate_ml_per_min,
advance_time_seconds=advance_time_seconds,
)
def apply_bag_valve_mask(
self,
*,
fio2: float | None = None,
peep_cmh2o: float | None = None,
respiration_rate_bpm: float | None = None,
inspiratory_expiratory_ratio: float | None = None,
squeeze_pressure_cmh2o: float | None = None,
squeeze_volume_ml: float | None = None,
airway_adjunct: str | None = None,
advance_time_seconds: float | None = 60.0,
) -> PatientState:
"""Provide bag-valve-mask support following Pulse's native equipment workflow."""
resolved_fio2 = self.DEFAULT_BVM_FIO2 if fio2 is None else fio2
resolved_rate = self.DEFAULT_BVM_RESPIRATION_RATE_BPM if respiration_rate_bpm is None else respiration_rate_bpm
resolved_ie_ratio = (
self.DEFAULT_BVM_IE_RATIO
if inspiratory_expiratory_ratio is None
else inspiratory_expiratory_ratio
)
resolved_pressure = (
self.DEFAULT_BVM_SQUEEZE_PRESSURE_CMH2O
if squeeze_pressure_cmh2o is None
else squeeze_pressure_cmh2o
)
if not 0.21 <= resolved_fio2 <= 1.0:
raise ValueError("fio2 must be between 0.21 and 1.0.")
if resolved_rate <= 0:
raise ValueError("respiration_rate_bpm must be greater than zero.")
if resolved_ie_ratio <= 0:
raise ValueError("inspiratory_expiratory_ratio must be greater than zero.")
if resolved_pressure <= 0 and squeeze_volume_ml is None:
raise ValueError("Provide a positive squeeze pressure or squeeze volume.")
if squeeze_volume_ml is not None and squeeze_volume_ml <= 0:
raise ValueError("squeeze_volume_ml must be greater than zero when provided.")
if peep_cmh2o is not None and peep_cmh2o < 0:
raise ValueError("peep_cmh2o cannot be negative.")
adjunct = airway_adjunct
if adjunct is None and not self._is_intubated():
adjunct = "oropharyngeal"
actions: list[SEAction] = []
if adjunct:
actions.append(self._build_intubation_action(adjunct))
configuration = SEBagValveMaskConfiguration()
bag = configuration.get_configuration()
bag.set_connection(eSwitch.On)
bag.get_fraction_inspired_gas("Oxygen").get_fraction_amount().set_value(resolved_fio2)
if peep_cmh2o is not None:
bag.get_valve_positive_end_expired_pressure().set_value(peep_cmh2o, PressureUnit.cmH2O)
actions.append(configuration)
automated = SEBagValveMaskAutomated()
automated.get_breath_frequency().set_value(resolved_rate, FrequencyUnit.Per_min)
automated.get_inspiratory_expiratory_ratio().set_value(resolved_ie_ratio)
if squeeze_volume_ml is not None:
automated.get_squeeze_volume().set_value(squeeze_volume_ml, VolumeUnit.mL)
else:
automated.get_squeeze_pressure().set_value(resolved_pressure, PressureUnit.cmH2O)
actions.append(automated)
return self.apply_actions(actions, advance_time_seconds=advance_time_seconds)
def apply_cpap(
self,
*,
fio2: float | None = None,
peep_cmh2o: float | None = None,
pressure_support_cmh2o: float | None = None,
advance_time_seconds: float | None = 120.0,
) -> PatientState:
"""Provide non-invasive CPAP support through Pulse's mechanical ventilator mode."""
resolved_fio2 = self.DEFAULT_CPAP_FIO2 if fio2 is None else fio2
resolved_peep = self.DEFAULT_CPAP_PEEP_CMH2O if peep_cmh2o is None else peep_cmh2o
resolved_support = (
self.DEFAULT_CPAP_PRESSURE_SUPPORT_CMH2O
if pressure_support_cmh2o is None
else pressure_support_cmh2o
)
if not 0.21 <= resolved_fio2 <= 1.0:
raise ValueError("fio2 must be between 0.21 and 1.0.")
if resolved_peep < 0:
raise ValueError("peep_cmh2o cannot be negative.")
if resolved_support < 0:
raise ValueError("pressure_support_cmh2o cannot be negative.")
action = SEMechanicalVentilatorContinuousPositiveAirwayPressure()
action.set_connection(eSwitch.On)
action.get_fraction_inspired_oxygen().set_value(resolved_fio2)
action.get_positive_end_expired_pressure().set_value(resolved_peep, PressureUnit.cmH2O)
action.get_delta_pressure_support().set_value(resolved_support, PressureUnit.cmH2O)
return self.apply_actions([action], advance_time_seconds=advance_time_seconds)
def apply_pressure_control_ventilation(
self,
*,
fio2: float | None = None,
peep_cmh2o: float | None = None,
inspiratory_pressure_cmh2o: float | None = None,
respiration_rate_bpm: float | None = None,
inspiratory_period_s: float | None = None,
advance_time_seconds: float | None = 120.0,
) -> PatientState:
"""Provide invasive pressure-control ventilation using Pulse's native ventilator action."""
resolved_fio2 = self.DEFAULT_PC_FIO2 if fio2 is None else fio2
resolved_peep = self.DEFAULT_PC_PEEP_CMH2O if peep_cmh2o is None else peep_cmh2o
resolved_pressure = (
self.DEFAULT_PC_INSPIRATORY_PRESSURE_CMH2O
if inspiratory_pressure_cmh2o is None
else inspiratory_pressure_cmh2o
)
resolved_rate = (
self.DEFAULT_PC_RESPIRATION_RATE_BPM
if respiration_rate_bpm is None
else respiration_rate_bpm
)
resolved_period = (
self.DEFAULT_PC_INSPIRATORY_PERIOD_S
if inspiratory_period_s is None
else inspiratory_period_s
)
if not 0.21 <= resolved_fio2 <= 1.0:
raise ValueError("fio2 must be between 0.21 and 1.0.")
if resolved_peep < 0:
raise ValueError("peep_cmh2o cannot be negative.")
if resolved_pressure <= 0:
raise ValueError("inspiratory_pressure_cmh2o must be greater than zero.")
if resolved_rate <= 0:
raise ValueError("respiration_rate_bpm must be greater than zero.")
if resolved_period <= 0:
raise ValueError("inspiratory_period_s must be greater than zero.")
actions: list[SEAction] = [self._build_intubation_action("tracheal")]
ventilator = SEMechanicalVentilatorPressureControl()
ventilator.set_connection(eSwitch.On)
ventilator.get_fraction_inspired_oxygen().set_value(resolved_fio2)
ventilator.get_positive_end_expired_pressure().set_value(resolved_peep, PressureUnit.cmH2O)
ventilator.get_inspiratory_pressure().set_value(resolved_pressure, PressureUnit.cmH2O)
ventilator.get_respiration_rate().set_value(resolved_rate, FrequencyUnit.Per_min)
ventilator.get_inspiratory_period().set_value(resolved_period, TimeUnit.s)
actions.append(ventilator)
return self.apply_actions(actions, advance_time_seconds=advance_time_seconds)
def apply_volume_control_ventilation(
self,
*,
fio2: float | None = None,
peep_cmh2o: float | None = None,
tidal_volume_ml: float | None = None,
respiration_rate_bpm: float | None = None,
inspiratory_period_s: float | None = None,
advance_time_seconds: float | None = 120.0,
) -> PatientState:
"""Provide invasive volume-control ventilation using Pulse's native ventilator action."""
resolved_fio2 = self.DEFAULT_VC_FIO2 if fio2 is None else fio2
resolved_peep = self.DEFAULT_VC_PEEP_CMH2O if peep_cmh2o is None else peep_cmh2o
resolved_tidal_volume = (
self.DEFAULT_VC_TIDAL_VOLUME_ML
if tidal_volume_ml is None
else tidal_volume_ml
)
resolved_rate = (
self.DEFAULT_VC_RESPIRATION_RATE_BPM
if respiration_rate_bpm is None
else respiration_rate_bpm
)
resolved_period = (
self.DEFAULT_PC_INSPIRATORY_PERIOD_S
if inspiratory_period_s is None
else inspiratory_period_s
)
if not 0.21 <= resolved_fio2 <= 1.0:
raise ValueError("fio2 must be between 0.21 and 1.0.")
if resolved_peep < 0:
raise ValueError("peep_cmh2o cannot be negative.")
if resolved_tidal_volume <= 0:
raise ValueError("tidal_volume_ml must be greater than zero.")
if resolved_rate <= 0:
raise ValueError("respiration_rate_bpm must be greater than zero.")
if resolved_period <= 0:
raise ValueError("inspiratory_period_s must be greater than zero.")
actions: list[SEAction] = [self._build_intubation_action("tracheal")]
ventilator = SEMechanicalVentilatorVolumeControl()
ventilator.set_connection(eSwitch.On)
ventilator.get_fraction_inspired_oxygen().set_value(resolved_fio2)
ventilator.get_positive_end_expired_pressure().set_value(resolved_peep, PressureUnit.cmH2O)
ventilator.get_tidal_volume().set_value(resolved_tidal_volume, VolumeUnit.mL)
ventilator.get_respiration_rate().set_value(resolved_rate, FrequencyUnit.Per_min)
ventilator.get_inspiratory_period().set_value(resolved_period, TimeUnit.s)
actions.append(ventilator)
return self.apply_actions(actions, advance_time_seconds=advance_time_seconds)
def administer_substance_bolus(
self,
*,
drug_name: str,
dose: float,
dose_unit: str,
concentration_value: float,
concentration_unit: Any,
duration_s: float,
advance_time_seconds: float | None = None,
) -> PatientState:
"""Deliver a supported substance bolus through Pulse's native bolus action."""
if not self.supports_substance(drug_name):
raise ValueError(f"Unsupported bolus substance '{drug_name}' for this local Pulse build.")
if dose <= 0:
raise ValueError("dose must be greater than zero.")
if concentration_value <= 0:
raise ValueError("concentration_value must be greater than zero.")
if duration_s <= 0:
raise ValueError("duration_s must be greater than zero.")
dose_volume_ml = dose / concentration_value
action = SESubstanceBolus()
action.set_substance(drug_name)
action.get_dose().set_value(dose_volume_ml, VolumeUnit.mL)
action.get_concentration().set_value(concentration_value, concentration_unit)
action.get_admin_duration().set_value(duration_s, TimeUnit.s)
# The local Pulse Python binding defines Intravenous with a tuple enum value.
# Leaving the route unset avoids the broken serializer while preserving a valid bolus action.
action._admin_route = None # type: ignore[attr-defined]
monitor_window = duration_s if advance_time_seconds is None else advance_time_seconds
return self.apply_actions([action], advance_time_seconds=monitor_window)
def perform_cpr(
self,
*,
compression_rate_bpm: float = DEFAULT_CPR_RATE_BPM,
depth_cm: float = DEFAULT_CPR_DEPTH_CM,
advance_time_seconds: float | None = 60.0,
) -> PatientState:
"""Perform automated CPR using Pulse's chest-compression action."""
if compression_rate_bpm <= 0:
raise ValueError("compression_rate_bpm must be greater than zero.")
if depth_cm <= 0:
raise ValueError("depth_cm must be greater than zero.")
action = SEChestCompressionAutomated()
action.get_compression_frequency().set_value(compression_rate_bpm, FrequencyUnit.Per_min)
action.get_depth().set_value(depth_cm, LengthUnit.cm)
return self.apply_actions([action], advance_time_seconds=advance_time_seconds)
def induce_cardiac_arrest(
self,
*,
advance_time_seconds: float | None = 30.0,
) -> PatientState:
"""Force an asystolic arrest for scenario-authoring workflows."""
action = SEArrhythmia()
action.set_rhythm(eHeartRhythm.Asystole)
return self.apply_actions([action], advance_time_seconds=advance_time_seconds)
def schedule_diagnostic(
self,
diagnostic_key: str,
*,
delay_seconds: int,
) -> PatientState:
"""Schedule a delayed diagnostic and return the updated state."""
if delay_seconds <= 0:
raise ValueError("delay_seconds must be greater than zero.")
self._runtime.ready_diagnostics.discard(diagnostic_key)
self._runtime.pending_diagnostics[diagnostic_key] = int(delay_seconds)
return self.get_full_state()
def order_diagnostic(self, tool_name: str, *, delay_seconds: int | None = None) -> PatientState:
"""Schedule a delayed diagnostic and return the updated state."""
if tool_name not in self.DIAGNOSTIC_DELAYS_S and delay_seconds is None:
valid = ", ".join(sorted(self.DIAGNOSTIC_DELAYS_S))
raise ValueError(f"Unsupported diagnostic '{tool_name}'. Expected one of: {valid}")
resolved_delay = int(delay_seconds if delay_seconds is not None else self.DIAGNOSTIC_DELAYS_S[tool_name])
return self.schedule_diagnostic(tool_name, delay_seconds=resolved_delay)
def set_hemorrhage(
self,
compartment: str,
*,
flow_rate_ml_per_min: float | None = None,
severity: float | None = None,
hemorrhage_type: str = "external",
advance_time_seconds: float | None = None,
) -> PatientState:
"""Start, modify, or stop a hemorrhage in a specific compartment."""
compartment_key = compartment.strip().lower().replace("-", "_").replace(" ", "_")
if compartment_key not in self._HEMORRHAGE_COMPARTMENT_MAP:
valid = ", ".join(sorted(self._HEMORRHAGE_COMPARTMENT_MAP))
raise ValueError(f"Unsupported hemorrhage compartment '{compartment}'. Expected one of: {valid}")
hemorrhage_type_key = hemorrhage_type.strip().lower()
if hemorrhage_type_key not in self._HEMORRHAGE_TYPE_MAP:
valid = ", ".join(sorted(self._HEMORRHAGE_TYPE_MAP))
raise ValueError(f"Unsupported hemorrhage type '{hemorrhage_type}'. Expected one of: {valid}")
if flow_rate_ml_per_min is None and severity is None:
flow_rate_ml_per_min = 0.0
if flow_rate_ml_per_min is not None and flow_rate_ml_per_min < 0:
raise ValueError("flow_rate_ml_per_min cannot be negative.")
if severity is not None and not 0.0 <= severity <= 1.0:
raise ValueError("severity must be between 0.0 and 1.0.")
action = SEHemorrhage()
action.set_type(self._HEMORRHAGE_TYPE_MAP[hemorrhage_type_key])
action.set_compartment(self._HEMORRHAGE_COMPARTMENT_MAP[compartment_key])
if flow_rate_ml_per_min is not None:
action.get_flow_rate().set_value(flow_rate_ml_per_min, VolumePerTimeUnit.mL_Per_min)
if severity is not None:
action.get_severity().set_value(severity)
return self.apply_actions([action], advance_time_seconds=advance_time_seconds)
def set_tension_pneumothorax(
self,
side: str,
*,
severity: float,
pneumothorax_type: str = "closed",
advance_time_seconds: float | None = None,
) -> PatientState:
"""Create a tension pneumothorax using Pulse's native action."""
side_key = side.strip().lower()
if side_key not in self._SIDE_MAP:
raise ValueError("side must be 'left' or 'right'.")
if not 0.0 <= severity <= 1.0:
raise ValueError("severity must be between 0.0 and 1.0.")
type_key = pneumothorax_type.strip().lower()
if type_key not in self._PNEUMOTHORAX_TYPE_MAP:
valid = ", ".join(sorted(self._PNEUMOTHORAX_TYPE_MAP))
raise ValueError(f"pneumothorax_type must be one of: {valid}")
action = SETensionPneumothorax()
action.set_side(self._SIDE_MAP[side_key])
action.set_type(self._PNEUMOTHORAX_TYPE_MAP[type_key])
action.get_severity().set_value(severity)
return self.apply_actions([action], advance_time_seconds=advance_time_seconds)
def apply_needle_decompression(
self,
side: str,
*,
advance_time_seconds: float | None = 90.0,
) -> PatientState:
"""Perform needle decompression on the specified hemithorax."""
side_key = side.strip().lower()
if side_key not in self._SIDE_MAP:
raise ValueError("side must be 'left' or 'right'.")
action = SENeedleDecompression()
action.set_side(self._SIDE_MAP[side_key])
action.set_state(eSwitch.On)
return self.apply_actions([action], advance_time_seconds=advance_time_seconds)
def set_intubation(
self,
kind: str,
*,
advance_time_seconds: float | None = 60.0,
) -> PatientState:
"""Apply or remove airway support using Pulse's intubation action."""
kind_key = kind.strip().lower().replace("-", "_").replace(" ", "_")
if kind_key not in self._INTUBATION_TYPE_MAP:
valid = ", ".join(sorted(self._INTUBATION_TYPE_MAP))
raise ValueError(f"Unsupported airway support '{kind}'. Expected one of: {valid}")
action = self._build_intubation_action(kind_key)
return self.apply_actions([action], advance_time_seconds=advance_time_seconds)
def apply_actions(
self,
actions: Sequence[SEAction] | Iterable[SEAction],
*,
advance_time_seconds: float | None = None,
) -> PatientState:
"""Submit native Pulse actions and optionally advance the sim immediately."""
engine = self._require_engine()
action_list = list(actions)
if not action_list:
return self.get_full_state()
engine.process_actions(action_list)
for action in action_list:
self._track_runtime_action(action)
if advance_time_seconds is not None and advance_time_seconds > 0:
return self.advance_time(advance_time_seconds)
self._refresh_raw_metrics()
return self.get_full_state()
def advance_time(self, seconds: float) -> PatientState:
"""Advance the simulation clock and return the updated state."""
if seconds < 0:
raise ValueError("Simulation time cannot be advanced by a negative duration.")
if seconds == 0:
return self.get_full_state()
engine = self._require_engine()
if not engine.advance_time_s(seconds):
raise RuntimeError(f"Pulse failed to advance by {seconds} simulated seconds.")
self._refresh_raw_metrics()
self._tick_pending_diagnostics(seconds)
return self.get_full_state()
def get_full_state(self) -> PatientState:
"""Return the current typed patient state."""
self._require_engine()
if not self._latest_raw_metrics:
self._refresh_raw_metrics()
metrics = self._latest_raw_metrics
blood_volume_ml = metrics.get("blood_volume_ml")
hemoglobin_content_g = metrics.get("hemoglobin_content_g")
blood_volume_dl = blood_volume_ml / 100.0 if blood_volume_ml and blood_volume_ml > 0 else None
hemoglobin_g_per_dl = (
hemoglobin_content_g / blood_volume_dl
if hemoglobin_content_g is not None and blood_volume_dl not in (None, 0)
else None
)
arterial_pco2 = metrics.get("arterial_carbon_dioxide_pressure_mmhg")
blood_ph = metrics.get("blood_ph")
bicarbonate = self._derive_bicarbonate_meq_per_l(blood_ph, arterial_pco2)
base_excess = metrics.get("base_excess_meq_per_l")
base_deficit = max(-base_excess, 0.0) if base_excess is not None else None
abg_result = ArterialBloodGasResult(
ph=blood_ph,
partial_pressure_of_oxygen_mmhg=metrics.get("arterial_oxygen_pressure_mmhg"),
partial_pressure_of_carbon_dioxide_mmhg=arterial_pco2,
oxygen_saturation=metrics.get("oxygen_saturation"),
bicarbonate_meq_per_l=bicarbonate,
lactate_mg_per_dl=metrics.get("lactate_mg_per_dl"),
base_excess_meq_per_l=base_excess,
base_deficit_meq_per_l=base_deficit,
)
cbc_result = CompleteBloodCountResult(
hemoglobin_g_per_dl=hemoglobin_g_per_dl,
hematocrit_fraction=metrics.get("hematocrit_fraction"),
white_blood_cell_count_per_u_l=metrics.get("white_blood_cell_count_per_u_l"),
platelet_count_per_u_l=None,
red_blood_cell_count_per_u_l=metrics.get("red_blood_cell_count_per_u_l"),
)
bmp_result = BasicMetabolicPanelResult(
sodium_mmol_per_l=self._convert_mg_per_dl_to_mmol_per_l(
metrics.get("sodium_mg_per_dl"),
self._SODIUM_MOLAR_MASS,
),
potassium_mmol_per_l=self._convert_mg_per_dl_to_mmol_per_l(
metrics.get("potassium_mg_per_dl"),
self._POTASSIUM_MOLAR_MASS,
),
calcium_mmol_per_l=self._convert_mg_per_dl_to_mmol_per_l(
metrics.get("calcium_mg_per_dl"),
self._CALCIUM_MOLAR_MASS,
),
creatinine_mg_per_dl=metrics.get("creatinine_mg_per_dl"),
glucose_mg_per_dl=metrics.get("glucose_mg_per_dl"),
)
is_alive = self._is_alive_from_metrics(metrics)
mental_status = self._derive_mental_status(metrics, is_alive)
shock_index = self._derive_shock_index(
metrics.get("heart_rate_bpm"),
metrics.get("systolic_bp_mmhg"),
)
state = PatientState(
scenario_id=self._runtime.scenario_id,
scenario_difficulty=self._runtime.scenario_difficulty,
patient_id=self._runtime.patient_id,
sim_time_s=metrics.get("simulated_time_seconds") or 0.0,
heart_rate_bpm=metrics.get("heart_rate_bpm"),
systolic_bp_mmhg=metrics.get("systolic_bp_mmhg"),
diastolic_bp_mmhg=metrics.get("diastolic_bp_mmhg"),
mean_arterial_pressure_mmhg=metrics.get("mean_arterial_pressure_mmhg"),
cardiac_output_l_per_min=metrics.get("cardiac_output_l_per_min"),
spo2=metrics.get("oxygen_saturation"),
respiration_rate_bpm=metrics.get("respiration_rate_bpm"),
blood_volume_ml=blood_volume_ml,
mental_status=mental_status,
active_alerts=self._derive_alerts(metrics, is_alive, shock_index),
done=not is_alive,
etco2_mmhg=metrics.get("end_tidal_carbon_dioxide_pressure_mmhg"),
tidal_volume_ml=metrics.get("tidal_volume_ml"),
breath_sounds=self._derive_breath_sounds(),
core_temperature_c=metrics.get("core_temperature_c"),
shock_index=shock_index,
lactate_trend=self._derive_lactate_trend(metrics.get("lactate_mg_per_dl")),
position=self._runtime.patient_position,
oxygen_device=self._runtime.oxygen_device,
oxygen_flow_lpm=self._runtime.oxygen_flow_lpm,
airway_support=self._derive_airway_support_name(),
intubated=self._is_intubated(),
abg_result=abg_result,
cbc_result=cbc_result,
bmp_result=bmp_result,
pending_diagnostics=dict(self._runtime.pending_diagnostics),
ready_diagnostics=sorted(self._runtime.ready_diagnostics),
active_infusions=dict(self._runtime.active_infusions),
active_hemorrhages=dict(self._runtime.active_hemorrhages),
)
self._last_lactate_mg_per_dl = metrics.get("lactate_mg_per_dl")
return state
def get_raw_metrics(self) -> dict[str, float | None]:
"""Return the latest low-level Pulse metrics used to build PatientState."""
self._require_engine()
if not self._latest_raw_metrics:
self._refresh_raw_metrics()
return dict(self._latest_raw_metrics)
def supports_substance(self, drug_name: str) -> bool:
"""Return whether a bolus drug is supported by the local Pulse build."""
return drug_name.strip() in {config["substance"] for config in self._BOLUS_DRUG_CONFIG.values()}
def supports_compound(self, compound_name: str) -> bool:
"""Return whether a compound infusion is known to work in the local Pulse build."""
return compound_name.strip() in self._SUPPORTED_COMPOUNDS
def get_patient_weight_kg(self) -> float | None:
"""Return the current patient weight in kilograms when available."""
return self.get_raw_metrics().get("patient_weight_kg")
def compute_drug_dose(self, *, dose: float, dose_unit: str) -> float:
"""Convert a tool-facing dose input into a total delivered mass."""
dose_unit_key = dose_unit.strip().lower()
if dose_unit_key == "mg":
return dose
if dose_unit_key == "mcg":
return dose
if dose_unit_key == "mg_per_kg":
weight_kg = self.get_patient_weight_kg()
if weight_kg is None or weight_kg <= 0:
raise RuntimeError("Patient weight is unavailable, so mg/kg dosing cannot be computed.")
return dose * weight_kg
if dose_unit_key == "mcg_per_kg_per_min":
weight_kg = self.get_patient_weight_kg()
if weight_kg is None or weight_kg <= 0:
raise RuntimeError("Patient weight is unavailable, so mcg/kg/min dosing cannot be computed.")
return dose * weight_kg
raise ValueError(f"Unsupported dose_unit '{dose_unit}'.")
def get_hemodynamics_summary(self) -> dict[str, float | None]:
"""Return a concise hemodynamic snapshot used by higher-level tools."""
metrics = self.get_raw_metrics()
return {
"mean_arterial_pressure_mmhg": metrics.get("mean_arterial_pressure_mmhg"),
"cardiac_output_l_per_min": metrics.get("cardiac_output_l_per_min"),
"stroke_volume_ml": metrics.get("stroke_volume_ml"),
"systemic_vascular_resistance_mmhg_min_per_l": metrics.get("systemic_vascular_resistance_mmhg_min_per_l"),
}
def get_blood_chemistry_summary(self) -> dict[str, float | None]:
"""Return chemistry values commonly used in bedside trauma assessment."""
state = self.get_full_state()
return {
"ph": state.abg_result.ph,
"lactate_mg_per_dl": state.abg_result.lactate_mg_per_dl,
"hemoglobin_g_per_dl": state.cbc_result.hemoglobin_g_per_dl,
"base_deficit_meq_per_l": state.abg_result.base_deficit_meq_per_l,
}
def get_pain_score_0_to_10(self) -> float:
"""Return a derived bedside pain score from active injuries and sedation."""
metrics = self.get_raw_metrics()
injury_burden = max(self._runtime.pain_sources.values(), default=0.0)
sedation_level = metrics.get("sedation_level") or 0.0
analgesia_fraction = min(max(sedation_level * 0.4, 0.0), 0.4)
score = max(0.0, min((injury_burden * (1.0 - analgesia_fraction)) * 10.0, 10.0))
return score
def get_consciousness_summary(self) -> dict[str, float | str]:
"""Return consciousness and an approximate GCS-equivalent score."""
state = self.get_full_state()
gcs_equivalent = {
"alert": 15.0,
"verbal": 13.0,
"pain": 8.0,
"unresponsive": 3.0,
}[state.mental_status]
return {
"mental_status": state.mental_status,
"gcs_equivalent": gcs_equivalent,
}
def get_shock_assessment(self) -> dict[str, float | str | None]:
"""Return a derived shock classification and perfusion summary."""
state = self.get_full_state()
metrics = self.get_raw_metrics()
shock_index = state.shock_index
urine_output_ml_per_hr = None
urine_rate_ml_per_min = metrics.get("urine_production_rate_ml_per_min")
if urine_rate_ml_per_min is not None:
urine_output_ml_per_hr = urine_rate_ml_per_min * 60.0
if shock_index is None:
shock_class = "unknown"
elif shock_index < 0.7:
shock_class = "none"
elif shock_index < 0.9:
shock_class = "compensated"
elif shock_index < 1.2:
shock_class = "class_ii"
elif shock_index < 1.5:
shock_class = "class_iii"
else:
shock_class = "class_iv"
map_mmhg = state.mean_arterial_pressure_mmhg
perfusion_index = metrics.get("peripheral_perfusion_index")
if map_mmhg is not None and map_mmhg < 55:
perfusion_status = "critical"
elif state.lactate_trend == "worsening" or (perfusion_index is not None and perfusion_index < 1.0):
perfusion_status = "poor"
elif map_mmhg is not None and map_mmhg < 65:
perfusion_status = "borderline"
else:
perfusion_status = "adequate"
return {
"shock_index": shock_index,
"shock_class": shock_class,
"perfusion_status": perfusion_status,
"urine_output_ml_per_hr": urine_output_ml_per_hr,
}
def get_ultrasound_summary(self, region: str) -> str:
"""Return a derived point-of-care ultrasound finding for a requested region."""
region_key = region.strip().lower().replace("-", "_").replace(" ", "_")
state = self.get_full_state()
if region_key in {"cardiac", "heart"}:
if "possible_cardiac_tamponade" in state.active_alerts:
return "Cardiac POCUS: pericardial effusion with tamponade physiology."
if "active_pericardial_effusion" in state.active_alerts:
return "Cardiac POCUS: pericardial effusion without current tamponade physiology."
return "Cardiac POCUS: no large pericardial effusion seen."
if region_key in {"lung", "chest", "thorax"}:
if "possible_tension_pneumothorax" in state.active_alerts:
side = "left" if "absent left" in state.breath_sounds else "right" if "absent right" in state.breath_sounds else "affected"
return f"Lung POCUS: absent pleural sliding on the {side} with pneumothorax concern."
return "Lung POCUS: bilateral pleural sliding present without large pneumothorax signs."
if region_key in {"fast", "abdomen", "efast"}:
internal_sites = {"spleen", "liver", "small_intestine", "large_intestine", "splanchnic", "vena_cava"}
if any(site in internal_sites for site in state.active_hemorrhages):
return "FAST exam: free fluid is present, concerning for internal hemorrhage."
return "FAST exam: no free intraperitoneal fluid detected."
if region_key == "ivc":
blood_volume_ml = state.blood_volume_ml
baseline = self._runtime.baseline_blood_volume_ml
if blood_volume_ml is not None and baseline is not None and blood_volume_ml < baseline * 0.8:
return "IVC POCUS: collapsible IVC suggesting volume depletion."
return "IVC POCUS: IVC caliber does not suggest severe volume depletion."
raise ValueError("region must be one of: cardiac, lung, fast, abdomen, efast, ivc")
def is_patient_alive(self) -> bool:
"""Check whether the patient is still in a survivable state."""
self._require_engine()
if not self._latest_raw_metrics:
self._refresh_raw_metrics()
return self._is_alive_from_metrics(self._latest_raw_metrics)
def close(self) -> None:
"""Release the active Pulse engine instance."""
if self._engine is None:
return
try:
self._engine.clear()
finally:
self._engine = None
self._latest_raw_metrics = {}
self._last_lactate_mg_per_dl = None
def __enter__(self) -> "PulseEngineAdapter":
return self
def __exit__(self, *_args: Any) -> None:
self.close()
def _require_engine(self) -> PulseEngine:
if self._engine is None:
raise RuntimeError("Pulse engine is not loaded. Call load_patient() first.")
return self._engine
def _resolve_state_file(self, state_file: str | Path | None) -> Path:
if state_file is None:
candidate = self._states_dir / self.DEFAULT_STATE_FILENAME
else:
raw = Path(state_file)
candidate = raw if raw.is_absolute() else self._states_dir / raw
resolved = candidate.resolve()
if not resolved.exists():
raise FileNotFoundError(f"Pulse state file was not found: {resolved}")
return resolved
def _build_data_requests(self) -> tuple[list[str], list[Any]]:
request_specs = [
("patient_weight_kg", SEDataRequest.create_patient_request("Weight", unit=MassUnit.kg)),
("heart_rate_bpm", SEDataRequest.create_physiology_request("HeartRate", unit=FrequencyUnit.Per_min)),
(
"systolic_bp_mmhg",
SEDataRequest.create_physiology_request("SystolicArterialPressure", unit=PressureUnit.mmHg),
),
(
"diastolic_bp_mmhg",
SEDataRequest.create_physiology_request("DiastolicArterialPressure", unit=PressureUnit.mmHg),
),
(
"mean_arterial_pressure_mmhg",
SEDataRequest.create_physiology_request("MeanArterialPressure", unit=PressureUnit.mmHg),
),
(
"cardiac_output_l_per_min",
SEDataRequest.create_physiology_request("CardiacOutput", unit=VolumePerTimeUnit.L_Per_min),
),
("stroke_volume_ml", SEDataRequest.create_physiology_request("HeartStrokeVolume", unit=VolumeUnit.mL)),
(
"systemic_vascular_resistance_mmhg_min_per_l",
SEDataRequest.create_physiology_request(
"SystemicVascularResistance",
unit=PressureTimePerVolumeUnit.mmHg_min_Per_L,
),
),
("oxygen_saturation", SEDataRequest.create_physiology_request("OxygenSaturation")),
(
"respiration_rate_bpm",
SEDataRequest.create_physiology_request("RespirationRate", unit=FrequencyUnit.Per_min),
),
(
"end_tidal_carbon_dioxide_pressure_mmhg",
SEDataRequest.create_physiology_request("EndTidalCarbonDioxidePressure", unit=PressureUnit.mmHg),
),
("tidal_volume_ml", SEDataRequest.create_physiology_request("TidalVolume", unit=VolumeUnit.mL)),
(
"total_pulmonary_ventilation_l_per_min",
SEDataRequest.create_physiology_request("TotalPulmonaryVentilation", unit=VolumePerTimeUnit.L_Per_min),
),
(
"respiratory_compliance_ml_per_cm_h2o",
SEDataRequest.create_physiology_request(
"RespiratoryCompliance",
unit=VolumePerPressureUnit.mL_Per_cmH2O,
),
),
(
"inspiratory_respiratory_resistance_cm_h2o_s_per_l",
SEDataRequest.create_physiology_request(
"InspiratoryRespiratoryResistance",
unit=PressureTimePerVolumeUnit.cmH2O_s_Per_L,
),
),
(
"expiratory_respiratory_resistance_cm_h2o_s_per_l",
SEDataRequest.create_physiology_request(
"ExpiratoryRespiratoryResistance",
unit=PressureTimePerVolumeUnit.cmH2O_s_Per_L,
),
),
("core_temperature_c", SEDataRequest.create_physiology_request("CoreTemperature", unit=TemperatureUnit.C)),
("skin_temperature_c", SEDataRequest.create_physiology_request("SkinTemperature", unit=TemperatureUnit.C)),
("peripheral_perfusion_index", SEDataRequest.create_physiology_request("PeripheralPerfusionIndex")),
("sedation_level", SEDataRequest.create_physiology_request("SedationLevel")),
("neuromuscular_block_level", SEDataRequest.create_physiology_request("NeuromuscularBlockLevel")),
("fatigue_level", SEDataRequest.create_physiology_request("FatigueLevel")),
("blood_ph", SEDataRequest.create_physiology_request("BloodPH")),
(
"base_excess_meq_per_l",
SEDataRequest.create_physiology_request("BaseExcess", unit=AmountPerVolumeUnit.mEq_Per_L),
),
("hematocrit_fraction", SEDataRequest.create_physiology_request("Hematocrit")),
("hemoglobin_content_g", SEDataRequest.create_physiology_request("HemoglobinContent", unit=MassUnit.g)),
(
"white_blood_cell_count_per_u_l",
SEDataRequest.create_physiology_request("WhiteBloodCellCount", unit=AmountPerVolumeUnit.ct_Per_uL),
),
(
"red_blood_cell_count_per_u_l",
SEDataRequest.create_physiology_request("RedBloodCellCount", unit=AmountPerVolumeUnit.ct_Per_uL),
),
(
"arterial_oxygen_pressure_mmhg",
SEDataRequest.create_physiology_request("ArterialOxygenPressure", unit=PressureUnit.mmHg),
),
(
"arterial_carbon_dioxide_pressure_mmhg",
SEDataRequest.create_physiology_request("ArterialCarbonDioxidePressure", unit=PressureUnit.mmHg),
),
("blood_volume_ml", SEDataRequest.create_physiology_request("BloodVolume", unit=VolumeUnit.mL)),
(
"urine_production_rate_ml_per_min",
SEDataRequest.create_physiology_request("UrineProductionRate", unit=VolumePerTimeUnit.mL_Per_min),
),
(
"total_hemorrhage_rate_ml_per_min",
SEDataRequest.create_physiology_request("TotalHemorrhageRate", unit=VolumePerTimeUnit.mL_Per_min),
),
(
"lactate_mg_per_dl",
SEDataRequest.create_liquid_compartment_substance_request(
"Aorta",
"Lactate",
"Concentration",
unit=MassPerVolumeUnit.mg_Per_dL,
),
),
(
"sodium_mg_per_dl",
SEDataRequest.create_liquid_compartment_substance_request(
"Aorta",
"Sodium",
"Concentration",
unit=MassPerVolumeUnit.mg_Per_dL,
),
),
(
"potassium_mg_per_dl",
SEDataRequest.create_liquid_compartment_substance_request(
"Aorta",
"Potassium",
"Concentration",
unit=MassPerVolumeUnit.mg_Per_dL,
),
),
(
"calcium_mg_per_dl",
SEDataRequest.create_liquid_compartment_substance_request(
"Aorta",
"Calcium",
"Concentration",
unit=MassPerVolumeUnit.mg_Per_dL,
),
),
(
"glucose_mg_per_dl",
SEDataRequest.create_liquid_compartment_substance_request(
"Aorta",
"Glucose",
"Concentration",
unit=MassPerVolumeUnit.mg_Per_dL,
),
),
(
"creatinine_mg_per_dl",
SEDataRequest.create_liquid_compartment_substance_request(
"Aorta",
"Creatinine",
"Concentration",
unit=MassPerVolumeUnit.mg_Per_dL,
),
),
]
keys = [key for key, _ in request_specs]
requests = [request for _, request in request_specs]
return keys, requests
def _refresh_raw_metrics(self) -> None:
results = self._require_engine().pull_data()
if results is None or len(results) == 0:
raise RuntimeError("Pulse returned no data for the configured requests.")
metrics: dict[str, float | None] = {
"simulated_time_seconds": self._coerce_optional_float(results[0]),
}
for offset, key in enumerate(self._data_request_keys, start=1):
metrics[key] = self._coerce_optional_float(results[offset])
self._latest_raw_metrics = metrics
def _track_runtime_action(self, action: SEAction) -> None:
if isinstance(action, SEIntubation):
self._runtime.intubation_type = action.get_type().name
self._runtime.airway_support_mode = None
return
if isinstance(action, SESupplementalOxygen):
device_name = action.get_device().name if hasattr(action.get_device(), "name") else str(action.get_device())
self._runtime.oxygen_device = _snake_case(device_name) if device_name and "Null" not in device_name else None
flow_lpm = self._get_scalar_value(action, "has_flow", "get_flow", unit=VolumePerTimeUnit.L_Per_min)
self._runtime.oxygen_flow_lpm = flow_lpm
return
if isinstance(action, SEBagValveMaskConfiguration):
configuration = action.get_configuration()
self._runtime.airway_support_mode = "bag_valve_mask"
self._runtime.fio2 = self._extract_substance_fraction(configuration, "Oxygen")
self._runtime.peep_cmh2o = self._get_scalar_value(
configuration,
"has_valve_positive_end_expired_pressure",
"get_valve_positive_end_expired_pressure",
unit=PressureUnit.cmH2O,
)
return
if isinstance(action, SEBagValveMaskAutomated):
self._runtime.airway_support_mode = "bag_valve_mask"
self._runtime.ventilator_respiration_rate_bpm = self._get_scalar_value(
action,
"has_breath_frequency",
"get_breath_frequency",
unit=FrequencyUnit.Per_min,
)
self._runtime.ventilator_tidal_volume_ml = self._get_scalar_value(
action,
"has_squeeze_volume",
"get_squeeze_volume",
unit=VolumeUnit.mL,
)
return
if isinstance(action, SEMechanicalVentilatorContinuousPositiveAirwayPressure):
if action.get_connection() == eSwitch.On:
self._runtime.airway_support_mode = "cpap"
self._runtime.fio2 = self._get_scalar_value(
action,
"has_fraction_inspired_oxygen",
"get_fraction_inspired_oxygen",
)
self._runtime.peep_cmh2o = self._get_scalar_value(
action,
"has_positive_end_expired_pressure",
"get_positive_end_expired_pressure",
unit=PressureUnit.cmH2O,
)
return
if isinstance(action, SEMechanicalVentilatorPressureControl):
if action.get_connection() == eSwitch.On:
self._runtime.airway_support_mode = "pressure_control_ventilation"
self._runtime.fio2 = self._get_scalar_value(
action,
"has_fraction_inspired_oxygen",
"get_fraction_inspired_oxygen",
)
self._runtime.peep_cmh2o = self._get_scalar_value(
action,
"has_positive_end_expired_pressure",
"get_positive_end_expired_pressure",
unit=PressureUnit.cmH2O,
)
self._runtime.ventilator_respiration_rate_bpm = self._get_scalar_value(
action,
"has_respiration_rate",
"get_respiration_rate",
unit=FrequencyUnit.Per_min,
)
self._runtime.ventilator_inspiratory_pressure_cmh2o = self._get_scalar_value(
action,
"has_inspiratory_pressure",
"get_inspiratory_pressure",
unit=PressureUnit.cmH2O,
)
return
if isinstance(action, SEMechanicalVentilatorVolumeControl):
if action.get_connection() == eSwitch.On:
self._runtime.airway_support_mode = "volume_control_ventilation"
self._runtime.fio2 = self._get_scalar_value(
action,
"has_fraction_inspired_oxygen",
"get_fraction_inspired_oxygen",
)
self._runtime.peep_cmh2o = self._get_scalar_value(
action,
"has_positive_end_expired_pressure",
"get_positive_end_expired_pressure",
unit=PressureUnit.cmH2O,
)
self._runtime.ventilator_respiration_rate_bpm = self._get_scalar_value(
action,
"has_respiration_rate",
"get_respiration_rate",
unit=FrequencyUnit.Per_min,
)
self._runtime.ventilator_tidal_volume_ml = self._get_scalar_value(
action,
"has_tidal_volume",
"get_tidal_volume",
unit=VolumeUnit.mL,
)
return
if isinstance(action, SENeedleDecompression) and action.has_side():
side = _snake_case(action.get_side().name)
self._runtime.active_tension_pneumothorax_sides.discard(side)
self._runtime.pain_sources.pop(f"tension_pneumothorax:{side}", None)
return
if isinstance(action, SETensionPneumothorax) and action.has_side():
side = _snake_case(action.get_side().name)
severity = self._get_scalar_value(action, "has_severity", "get_severity")
self._runtime.active_tension_pneumothorax_sides.add(side)
self._runtime.pain_sources[f"tension_pneumothorax:{side}"] = severity or 0.8
return
if isinstance(action, SEPericardialEffusion):
rate_ml_per_min = self._get_scalar_value(
action,
"has_effusion_rate",
"get_effusion_rate",
unit=VolumePerTimeUnit.mL_Per_min,
)
if rate_ml_per_min is None or abs(rate_ml_per_min) < 1e-6:
self._runtime.pericardial_effusion_rate_ml_per_min = None
self._runtime.pain_sources.pop("pericardial_effusion", None)
else:
self._runtime.pericardial_effusion_rate_ml_per_min = rate_ml_per_min
self._runtime.pain_sources["pericardial_effusion"] = min(abs(rate_ml_per_min) / 150.0, 1.0)
return
if isinstance(action, SEHemorrhage) and action.has_compartment():
compartment = self._normalize_compartment_name(action.get_compartment())
rate_ml_per_min = self._get_scalar_value(
action,
"has_flow_rate",
"get_flow_rate",
unit=VolumePerTimeUnit.mL_Per_min,
)
severity = self._get_scalar_value(action, "has_severity", "get_severity")
if rate_ml_per_min is not None and rate_ml_per_min > 0:
self._runtime.active_hemorrhages[compartment] = rate_ml_per_min
else:
self._runtime.active_hemorrhages.pop(compartment, None)
estimated_pain = severity if severity is not None else self._estimate_hemorrhage_severity(rate_ml_per_min)
self._runtime.pain_sources[f"hemorrhage:{compartment}"] = estimated_pain
return
if isinstance(action, SESubstanceCompoundInfusion):
compound = action.get_compound()
if compound:
rate_ml_per_min = self._get_scalar_value(
action,
"has_rate",
"get_rate",
unit=VolumePerTimeUnit.mL_Per_min,
)
name = _snake_case(str(compound))
if rate_ml_per_min is not None and rate_ml_per_min > 0:
self._runtime.active_infusions[name] = rate_ml_per_min
else:
self._runtime.active_infusions.pop(name, None)
return
if isinstance(action, SESubstanceInfusion) and action.has_substance():
substance = str(action.get_substance())
rate_ml_per_min = self._get_scalar_value(
action,
"has_rate",
"get_rate",
unit=VolumePerTimeUnit.mL_Per_min,
)
name = _snake_case(substance)
if rate_ml_per_min is not None and rate_ml_per_min > 0:
self._runtime.active_infusions[name] = rate_ml_per_min
else:
self._runtime.active_infusions.pop(name, None)
def _is_alive_from_metrics(self, metrics: dict[str, float | None]) -> bool:
if self._has_terminal_event():
return False
heart_rate = metrics.get("heart_rate_bpm")
map_mmhg = metrics.get("mean_arterial_pressure_mmhg")
spo2 = metrics.get("oxygen_saturation")
etco2 = metrics.get("end_tidal_carbon_dioxide_pressure_mmhg")
if heart_rate is not None and heart_rate <= 0.1:
return False
if map_mmhg is not None and map_mmhg <= 10.0:
return False
if spo2 is not None and spo2 <= 0.01 and etco2 is not None and etco2 <= 1.0:
return False
return True
def _has_terminal_event(self) -> bool:
active_events = self._require_engine().pull_active_events() or {}
terminal_events = {"CardiacArrest", "IrreversibleState", "Asystole"}
for event in active_events.keys():
normalized = self._normalize_event_name(event)
if normalized in terminal_events:
return True
return False
def _derive_breath_sounds(self) -> str:
left_absent = False
right_absent = False
if self._runtime.intubation_type == eIntubationType.LeftMainstem.name:
right_absent = True
elif self._runtime.intubation_type == eIntubationType.RightMainstem.name:
left_absent = True
elif self._runtime.intubation_type == eIntubationType.Esophageal.name:
left_absent = True
right_absent = True
if "left" in self._runtime.active_tension_pneumothorax_sides:
left_absent = True
if "right" in self._runtime.active_tension_pneumothorax_sides:
right_absent = True
if left_absent and right_absent:
return "absent bilateral"
if left_absent:
return "absent left"
if right_absent:
return "absent right"
return "present bilateral"
def _derive_airway_support_name(self) -> str | None:
if self._runtime.airway_support_mode:
return self._AIRWAY_SUPPORT_DISPLAY.get(
self._runtime.airway_support_mode,
self._runtime.airway_support_mode,
)
return self._AIRWAY_SUPPORT_NAMES.get(self._runtime.intubation_type)
def _is_intubated(self) -> bool:
return self._runtime.intubation_type in {
eIntubationType.Esophageal.name,
eIntubationType.LeftMainstem.name,
eIntubationType.RightMainstem.name,
eIntubationType.Tracheal.name,
}
def _tick_pending_diagnostics(self, elapsed_seconds: float) -> None:
if not self._runtime.pending_diagnostics:
return
completed: list[str] = []
for tool_name, remaining in list(self._runtime.pending_diagnostics.items()):
updated_remaining = max(0, int(math.ceil(remaining - elapsed_seconds)))
if updated_remaining == 0:
completed.append(tool_name)
self._runtime.pending_diagnostics.pop(tool_name, None)
else:
self._runtime.pending_diagnostics[tool_name] = updated_remaining
for tool_name in completed:
self._runtime.ready_diagnostics.add(tool_name)
def _derive_shock_index(self, heart_rate_bpm: float | None, systolic_bp_mmhg: float | None) -> float | None:
if heart_rate_bpm is None or systolic_bp_mmhg is None or systolic_bp_mmhg <= 0:
return None
return heart_rate_bpm / systolic_bp_mmhg
def _derive_lactate_trend(self, current_lactate_mg_per_dl: float | None) -> str:
previous = self._last_lactate_mg_per_dl
if current_lactate_mg_per_dl is None or previous is None:
return "stable"
delta = current_lactate_mg_per_dl - previous
if abs(delta) < 0.25:
return "stable"
return "improving" if delta < 0 else "worsening"
def _derive_mental_status(
self,
metrics: dict[str, float | None],
is_alive: bool,
) -> MentalStatus:
if not is_alive:
return "unresponsive"
score = 1.0 - (metrics.get("sedation_level") or 0.0)
map_mmhg = metrics.get("mean_arterial_pressure_mmhg")
spo2 = metrics.get("oxygen_saturation")
if map_mmhg is not None:
if map_mmhg < 45:
score = min(score, 0.1)
elif map_mmhg < 55:
score = min(score, 0.25)
elif map_mmhg < 65:
score = min(score, 0.55)
elif map_mmhg < 75:
score = min(score, 0.8)
if spo2 is not None:
if spo2 < 0.75:
score = min(score, 0.1)
elif spo2 < 0.85:
score = min(score, 0.25)
elif spo2 < 0.9:
score = min(score, 0.55)
elif spo2 < 0.94:
score = min(score, 0.8)
if score >= 0.85:
return "alert"
if score >= 0.6:
return "verbal"
if score >= 0.3:
return "pain"
return "unresponsive"
def _derive_alerts(
self,
metrics: dict[str, float | None],
is_alive: bool,
shock_index: float | None,
) -> list[str]:
if not is_alive:
return ["cardiac_arrest"]
alerts: list[str] = []
heart_rate = metrics.get("heart_rate_bpm")
systolic = metrics.get("systolic_bp_mmhg")
map_mmhg = metrics.get("mean_arterial_pressure_mmhg")
spo2 = metrics.get("oxygen_saturation")
respiration_rate = metrics.get("respiration_rate_bpm")
blood_ph = metrics.get("blood_ph")
if heart_rate is not None and heart_rate >= 100:
alerts.append("tachycardia")
if heart_rate is not None and heart_rate <= 50:
alerts.append("bradycardia")
if systolic is not None and systolic < 90 or map_mmhg is not None and map_mmhg < 65:
alerts.append("hypotension")
if spo2 is not None and spo2 < 0.92:
alerts.append("hypoxemia")
if spo2 is not None and spo2 < 0.85:
alerts.append("severe_hypoxemia")
if respiration_rate is not None and respiration_rate >= 24:
alerts.append("tachypnea")
if respiration_rate is not None and respiration_rate <= 8:
alerts.append("bradypnea")
if shock_index is not None and shock_index >= 0.9:
alerts.append("shock_index_elevated")
if self._runtime.active_hemorrhages:
alerts.append("active_hemorrhage")
if self._runtime.active_tension_pneumothorax_sides:
alerts.append("possible_tension_pneumothorax")
if self._runtime.intubation_type == eIntubationType.Esophageal.name:
alerts.append("misplaced_airway")
if self._runtime.pending_diagnostics:
alerts.append("diagnostics_pending")
effusion_rate = self._runtime.pericardial_effusion_rate_ml_per_min
if effusion_rate is not None:
if effusion_rate > 0:
alerts.append("active_pericardial_effusion")
if map_mmhg is not None and map_mmhg < 65:
alerts.append("possible_cardiac_tamponade")
elif effusion_rate < 0:
alerts.append("pericardiocentesis_in_progress")
breath_sounds = self._derive_breath_sounds()
if breath_sounds == "absent bilateral":
alerts.append("bilateral_absent_breath_sounds")
elif breath_sounds != "present bilateral":
alerts.append("unilateral_absent_breath_sounds")
if blood_ph is not None and blood_ph < 7.35:
alerts.append("acidosis")
return list(dict.fromkeys(alerts))
@staticmethod
def _derive_bicarbonate_meq_per_l(ph: float | None, arterial_pco2_mmhg: float | None) -> float | None:
if ph is None or arterial_pco2_mmhg is None:
return None
return 0.03 * arterial_pco2_mmhg * (10 ** (ph - 6.1))
@staticmethod
def _convert_mg_per_dl_to_mmol_per_l(value_mg_per_dl: float | None, molar_mass_g_per_mol: float) -> float | None:
if value_mg_per_dl is None:
return None
return (value_mg_per_dl * 10.0) / molar_mass_g_per_mol
@staticmethod
def _estimate_hemorrhage_severity(rate_ml_per_min: float | None) -> float:
if rate_ml_per_min is None:
return 0.0
return max(0.0, min(rate_ml_per_min / 250.0, 1.0))
@staticmethod
def _coerce_optional_float(value: Any) -> float | None:
if value is None:
return None
coerced = float(value)
return None if math.isnan(coerced) else coerced
@staticmethod
def _normalize_event_name(event: Any) -> str:
name = getattr(event, "name", str(event))
return name.split(".")[-1]
@staticmethod
def _get_scalar_value(
action: Any,
has_method_name: str,
get_method_name: str,
*,
unit: Any | None = None,
) -> float | None:
has_method = getattr(action, has_method_name, None)
if callable(has_method) and not has_method():
return None
getter = getattr(action, get_method_name)
scalar = getter()
try:
return float(scalar.get_value(unit) if unit is not None else scalar.get_value())
except Exception:
return None
def _build_intubation_action(self, kind: str) -> SEIntubation:
kind_key = kind.strip().lower().replace("-", "_").replace(" ", "_")
if kind_key not in self._INTUBATION_TYPE_MAP:
valid = ", ".join(sorted(self._INTUBATION_TYPE_MAP))
raise ValueError(f"Unsupported airway support '{kind}'. Expected one of: {valid}")
action = SEIntubation()
action.set_type(self._INTUBATION_TYPE_MAP[kind_key])
return action
@staticmethod
def _extract_substance_fraction(configuration: Any, substance_name: str) -> float | None:
try:
fraction = configuration.get_fraction_inspired_gas(substance_name)
amount = fraction.get_fraction_amount()
if amount is None:
return None
value = amount.get_value()
return None if value is None else float(value)
except Exception:
return None
@staticmethod
def _default_patient_id_from_state_file(state_file: Path) -> str:
stem = state_file.stem.split("@")[0]
return _snake_case(stem)
@staticmethod
def _normalize_compartment_name(compartment: Any) -> str:
name = getattr(compartment, "name", str(compartment))
return _snake_case(name)