iints-sdk-python35 0.0.18__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- iints/__init__.py +183 -0
- iints/analysis/__init__.py +12 -0
- iints/analysis/algorithm_xray.py +387 -0
- iints/analysis/baseline.py +92 -0
- iints/analysis/clinical_benchmark.py +198 -0
- iints/analysis/clinical_metrics.py +551 -0
- iints/analysis/clinical_tir_analyzer.py +136 -0
- iints/analysis/diabetes_metrics.py +43 -0
- iints/analysis/edge_efficiency.py +33 -0
- iints/analysis/edge_performance_monitor.py +315 -0
- iints/analysis/explainability.py +94 -0
- iints/analysis/explainable_ai.py +232 -0
- iints/analysis/hardware_benchmark.py +221 -0
- iints/analysis/metrics.py +117 -0
- iints/analysis/population_report.py +188 -0
- iints/analysis/reporting.py +345 -0
- iints/analysis/safety_index.py +311 -0
- iints/analysis/sensor_filtering.py +54 -0
- iints/analysis/validator.py +273 -0
- iints/api/__init__.py +0 -0
- iints/api/base_algorithm.py +307 -0
- iints/api/registry.py +103 -0
- iints/api/template_algorithm.py +195 -0
- iints/assets/iints_logo.png +0 -0
- iints/cli/__init__.py +0 -0
- iints/cli/cli.py +2598 -0
- iints/core/__init__.py +1 -0
- iints/core/algorithms/__init__.py +0 -0
- iints/core/algorithms/battle_runner.py +138 -0
- iints/core/algorithms/correction_bolus.py +95 -0
- iints/core/algorithms/discovery.py +92 -0
- iints/core/algorithms/fixed_basal_bolus.py +58 -0
- iints/core/algorithms/hybrid_algorithm.py +92 -0
- iints/core/algorithms/lstm_algorithm.py +138 -0
- iints/core/algorithms/mock_algorithms.py +162 -0
- iints/core/algorithms/pid_controller.py +88 -0
- iints/core/algorithms/standard_pump_algo.py +64 -0
- iints/core/device.py +0 -0
- iints/core/device_manager.py +64 -0
- iints/core/devices/__init__.py +3 -0
- iints/core/devices/models.py +160 -0
- iints/core/patient/__init__.py +9 -0
- iints/core/patient/bergman_model.py +341 -0
- iints/core/patient/models.py +285 -0
- iints/core/patient/patient_factory.py +117 -0
- iints/core/patient/profile.py +41 -0
- iints/core/safety/__init__.py +12 -0
- iints/core/safety/config.py +37 -0
- iints/core/safety/input_validator.py +95 -0
- iints/core/safety/supervisor.py +39 -0
- iints/core/simulation/__init__.py +0 -0
- iints/core/simulation/scenario_parser.py +61 -0
- iints/core/simulator.py +874 -0
- iints/core/supervisor.py +367 -0
- iints/data/__init__.py +53 -0
- iints/data/adapter.py +142 -0
- iints/data/column_mapper.py +398 -0
- iints/data/datasets.json +132 -0
- iints/data/demo/__init__.py +1 -0
- iints/data/demo/demo_cgm.csv +289 -0
- iints/data/importer.py +275 -0
- iints/data/ingestor.py +162 -0
- iints/data/nightscout.py +128 -0
- iints/data/quality_checker.py +550 -0
- iints/data/registry.py +166 -0
- iints/data/tidepool.py +38 -0
- iints/data/universal_parser.py +813 -0
- iints/data/virtual_patients/clinic_safe_baseline.yaml +9 -0
- iints/data/virtual_patients/clinic_safe_hyper_challenge.yaml +9 -0
- iints/data/virtual_patients/clinic_safe_hypo_prone.yaml +9 -0
- iints/data/virtual_patients/clinic_safe_midnight.yaml +9 -0
- iints/data/virtual_patients/clinic_safe_pizza.yaml +9 -0
- iints/data/virtual_patients/clinic_safe_stress_meal.yaml +9 -0
- iints/data/virtual_patients/default_patient.yaml +11 -0
- iints/data/virtual_patients/patient_559_config.yaml +11 -0
- iints/emulation/__init__.py +80 -0
- iints/emulation/legacy_base.py +414 -0
- iints/emulation/medtronic_780g.py +337 -0
- iints/emulation/omnipod_5.py +367 -0
- iints/emulation/tandem_controliq.py +393 -0
- iints/highlevel.py +451 -0
- iints/learning/__init__.py +3 -0
- iints/learning/autonomous_optimizer.py +194 -0
- iints/learning/learning_system.py +122 -0
- iints/metrics.py +34 -0
- iints/population/__init__.py +11 -0
- iints/population/generator.py +131 -0
- iints/population/runner.py +327 -0
- iints/presets/__init__.py +28 -0
- iints/presets/presets.json +114 -0
- iints/research/__init__.py +30 -0
- iints/research/config.py +68 -0
- iints/research/dataset.py +319 -0
- iints/research/losses.py +73 -0
- iints/research/predictor.py +329 -0
- iints/scenarios/__init__.py +3 -0
- iints/scenarios/generator.py +92 -0
- iints/templates/__init__.py +0 -0
- iints/templates/default_algorithm.py +91 -0
- iints/templates/scenarios/__init__.py +0 -0
- iints/templates/scenarios/chaos_insulin_stacking.json +29 -0
- iints/templates/scenarios/chaos_runaway_ai.json +25 -0
- iints/templates/scenarios/example_scenario.json +35 -0
- iints/templates/scenarios/exercise_stress.json +30 -0
- iints/utils/__init__.py +3 -0
- iints/utils/plotting.py +50 -0
- iints/utils/run_io.py +152 -0
- iints/validation/__init__.py +133 -0
- iints/validation/schemas.py +94 -0
- iints/visualization/__init__.py +34 -0
- iints/visualization/cockpit.py +691 -0
- iints/visualization/uncertainty_cloud.py +612 -0
- iints_sdk_python35-0.0.18.dist-info/METADATA +225 -0
- iints_sdk_python35-0.0.18.dist-info/RECORD +118 -0
- iints_sdk_python35-0.0.18.dist-info/WHEEL +5 -0
- iints_sdk_python35-0.0.18.dist-info/entry_points.txt +10 -0
- iints_sdk_python35-0.0.18.dist-info/licenses/LICENSE +28 -0
- iints_sdk_python35-0.0.18.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from typing import Dict, Any, Optional
|
|
3
|
+
from .models import CustomPatientModel
|
|
4
|
+
|
|
5
|
+
try:
|
|
6
|
+
from simglucose.simulation.env import T1DSimEnv
|
|
7
|
+
from simglucose.patient.t1dpatient import T1DPatient
|
|
8
|
+
from simglucose.sensor.cgm import CGMSensor
|
|
9
|
+
from simglucose.actuator.pump import InsulinPump
|
|
10
|
+
from simglucose.controller.base import Action
|
|
11
|
+
SIMGLUCOSE_AVAILABLE = True
|
|
12
|
+
except ImportError:
|
|
13
|
+
SIMGLUCOSE_AVAILABLE = False
|
|
14
|
+
|
|
15
|
+
class PatientFactory:
|
|
16
|
+
"""Factory for creating different types of patient models."""
|
|
17
|
+
|
|
18
|
+
SIMGLUCOSE_PATIENTS = [
|
|
19
|
+
'adolescent#001', 'adolescent#002', 'adolescent#003', 'adolescent#004', 'adolescent#005',
|
|
20
|
+
'adolescent#006', 'adolescent#007', 'adolescent#008', 'adolescent#009', 'adolescent#010',
|
|
21
|
+
'adult#001', 'adult#002', 'adult#003', 'adult#004', 'adult#005',
|
|
22
|
+
'adult#006', 'adult#007', 'adult#008', 'adult#009', 'adult#010',
|
|
23
|
+
'child#001', 'child#002', 'child#003', 'child#004', 'child#005',
|
|
24
|
+
'child#006', 'child#007', 'child#008', 'child#009', 'child#010'
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
@staticmethod
|
|
28
|
+
def create_patient(patient_type='custom', patient_id=None, initial_glucose=120.0, **kwargs):
|
|
29
|
+
"""Create a patient model based on type."""
|
|
30
|
+
if patient_type == 'custom':
|
|
31
|
+
return CustomPatientModel(initial_glucose=initial_glucose, **kwargs)
|
|
32
|
+
elif patient_type == 'simglucose':
|
|
33
|
+
if not SIMGLUCOSE_AVAILABLE:
|
|
34
|
+
print("Warning: Simglucose not available, falling back to custom model")
|
|
35
|
+
return CustomPatientModel(initial_glucose=initial_glucose, **kwargs)
|
|
36
|
+
|
|
37
|
+
patient_name = patient_id or PatientFactory.SIMGLUCOSE_PATIENTS[0]
|
|
38
|
+
return SimglucosePatientWrapper(patient_name, initial_glucose)
|
|
39
|
+
else:
|
|
40
|
+
raise ValueError(f"Unknown patient type: {patient_type}")
|
|
41
|
+
|
|
42
|
+
@staticmethod
|
|
43
|
+
def get_patient_diversity_set():
|
|
44
|
+
"""Get a diverse set of patients for population studies."""
|
|
45
|
+
if not SIMGLUCOSE_AVAILABLE:
|
|
46
|
+
# Create diverse custom patients with different parameters
|
|
47
|
+
return [
|
|
48
|
+
CustomPatientModel(initial_glucose=120, insulin_sensitivity=40), # High sensitivity
|
|
49
|
+
CustomPatientModel(initial_glucose=120, insulin_sensitivity=60), # Low sensitivity
|
|
50
|
+
CustomPatientModel(initial_glucose=120, carb_factor=8), # Fast carb absorption
|
|
51
|
+
CustomPatientModel(initial_glucose=120, carb_factor=12), # Slow carb absorption
|
|
52
|
+
CustomPatientModel(initial_glucose=120, glucose_decay_rate=0.0015), # Slow metabolism
|
|
53
|
+
CustomPatientModel(initial_glucose=120, glucose_decay_rate=0.0035), # Fast metabolism
|
|
54
|
+
]
|
|
55
|
+
else:
|
|
56
|
+
# Use FDA-approved virtual patients
|
|
57
|
+
selected_patients = [
|
|
58
|
+
'adolescent#001', 'adolescent#005', 'adult#001',
|
|
59
|
+
'adult#005', 'child#001', 'child#005'
|
|
60
|
+
]
|
|
61
|
+
return [SimglucosePatientWrapper(name) for name in selected_patients]
|
|
62
|
+
|
|
63
|
+
class SimglucosePatientWrapper:
|
|
64
|
+
"""Wrapper for simglucose patients to match CustomPatientModel interface."""
|
|
65
|
+
|
|
66
|
+
def __init__(self, patient_name='adolescent#001', initial_glucose=120.0):
|
|
67
|
+
if not SIMGLUCOSE_AVAILABLE:
|
|
68
|
+
raise ImportError("Simglucose not available")
|
|
69
|
+
|
|
70
|
+
self.patient = T1DPatient.make(patient_name)
|
|
71
|
+
self.sensor = CGMSensor.make()
|
|
72
|
+
self.pump = InsulinPump.make()
|
|
73
|
+
self.env = T1DSimEnv(patient=self.patient, sensor=self.sensor, pump=self.pump)
|
|
74
|
+
self.patient_name = patient_name
|
|
75
|
+
self.reset()
|
|
76
|
+
|
|
77
|
+
def reset(self):
|
|
78
|
+
"""Reset the simglucose environment."""
|
|
79
|
+
self.env.reset()
|
|
80
|
+
|
|
81
|
+
def get_current_glucose(self):
|
|
82
|
+
"""Get current glucose in mg/dL."""
|
|
83
|
+
return self.env.patient.BG * 18.0156
|
|
84
|
+
|
|
85
|
+
def update(self, time_step, delivered_insulin, carb_intake=0.0, **kwargs):
|
|
86
|
+
"""Update patient state."""
|
|
87
|
+
action = Action(basal=0.0, bolus=delivered_insulin, carb=carb_intake)
|
|
88
|
+
obs, reward, done, info = self.env.step(action)
|
|
89
|
+
self._last_info = info
|
|
90
|
+
return obs[0] * 18.0156
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def insulin_on_board(self):
|
|
94
|
+
"""Get insulin on board."""
|
|
95
|
+
if hasattr(self, '_last_info') and 'IOB' in self._last_info:
|
|
96
|
+
return self._last_info['IOB']
|
|
97
|
+
return getattr(self.env.patient, 'IOB', 0.0)
|
|
98
|
+
|
|
99
|
+
@property
|
|
100
|
+
def carbs_on_board(self):
|
|
101
|
+
"""Get carbs on board."""
|
|
102
|
+
if hasattr(self, '_last_info') and 'COB' in self._last_info:
|
|
103
|
+
return self._last_info['COB']
|
|
104
|
+
return getattr(self.env.patient, 'COB', 0.0)
|
|
105
|
+
|
|
106
|
+
def trigger_event(self, event_type, value):
|
|
107
|
+
"""Trigger stress events."""
|
|
108
|
+
print(f"SimglucosePatient {self.patient_name}: {event_type} = {value}")
|
|
109
|
+
|
|
110
|
+
def get_patient_state(self):
|
|
111
|
+
"""Get patient state for logging."""
|
|
112
|
+
return {
|
|
113
|
+
"current_glucose": self.get_current_glucose(),
|
|
114
|
+
"insulin_on_board": self.insulin_on_board,
|
|
115
|
+
"carbs_on_board": self.carbs_on_board,
|
|
116
|
+
"patient_name": self.patient_name
|
|
117
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Dict, Any, Optional
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class PatientProfile:
|
|
9
|
+
"""
|
|
10
|
+
User-facing patient profile that maps to the simulator config.
|
|
11
|
+
"""
|
|
12
|
+
isf: float = 50.0 # Insulin Sensitivity Factor (mg/dL per unit)
|
|
13
|
+
icr: float = 10.0 # Insulin-to-carb ratio (grams per unit)
|
|
14
|
+
basal_rate: float = 0.8 # U/hr
|
|
15
|
+
initial_glucose: float = 120.0
|
|
16
|
+
dawn_phenomenon_strength: float = 0.0 # mg/dL per hour
|
|
17
|
+
dawn_start_hour: float = 4.0
|
|
18
|
+
dawn_end_hour: float = 8.0
|
|
19
|
+
|
|
20
|
+
# Advanced knobs (optional)
|
|
21
|
+
glucose_decay_rate: float = 0.002
|
|
22
|
+
glucose_absorption_rate: float = 0.03
|
|
23
|
+
insulin_action_duration: float = 300.0
|
|
24
|
+
insulin_peak_time: float = 75.0
|
|
25
|
+
meal_mismatch_epsilon: float = 1.0
|
|
26
|
+
|
|
27
|
+
def to_patient_config(self) -> Dict[str, Any]:
|
|
28
|
+
return {
|
|
29
|
+
"basal_insulin_rate": self.basal_rate,
|
|
30
|
+
"insulin_sensitivity": self.isf,
|
|
31
|
+
"carb_factor": self.icr,
|
|
32
|
+
"initial_glucose": self.initial_glucose,
|
|
33
|
+
"dawn_phenomenon_strength": self.dawn_phenomenon_strength,
|
|
34
|
+
"dawn_start_hour": self.dawn_start_hour,
|
|
35
|
+
"dawn_end_hour": self.dawn_end_hour,
|
|
36
|
+
"glucose_decay_rate": self.glucose_decay_rate,
|
|
37
|
+
"glucose_absorption_rate": self.glucose_absorption_rate,
|
|
38
|
+
"insulin_action_duration": self.insulin_action_duration,
|
|
39
|
+
"insulin_peak_time": self.insulin_peak_time,
|
|
40
|
+
"meal_mismatch_epsilon": self.meal_mismatch_epsilon,
|
|
41
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from .config import SafetyConfig
|
|
2
|
+
from .input_validator import InputValidator
|
|
3
|
+
|
|
4
|
+
__all__ = ["SafetyConfig", "SafetySupervisor", "InputValidator"]
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def __getattr__(name: str):
|
|
8
|
+
if name == "SafetySupervisor":
|
|
9
|
+
from .supervisor import SafetySupervisor
|
|
10
|
+
|
|
11
|
+
return SafetySupervisor
|
|
12
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass
|
|
7
|
+
class SafetyConfig:
|
|
8
|
+
"""
|
|
9
|
+
Central safety configuration for simulator, input validation, and supervisor.
|
|
10
|
+
"""
|
|
11
|
+
# Input validation limits
|
|
12
|
+
min_glucose: float = 20.0
|
|
13
|
+
max_glucose: float = 600.0
|
|
14
|
+
max_glucose_delta_per_5_min: float = 35.0
|
|
15
|
+
|
|
16
|
+
# Supervisor thresholds
|
|
17
|
+
hypoglycemia_threshold: float = 70.0
|
|
18
|
+
severe_hypoglycemia_threshold: float = 54.0
|
|
19
|
+
hyperglycemia_threshold: float = 250.0
|
|
20
|
+
max_insulin_per_bolus: float = 5.0
|
|
21
|
+
glucose_rate_alarm: float = 5.0
|
|
22
|
+
max_insulin_per_hour: float = 3.0
|
|
23
|
+
max_iob: float = 4.0
|
|
24
|
+
trend_stop: float = -2.0
|
|
25
|
+
hypo_cutoff: float = 70.0
|
|
26
|
+
max_basal_multiplier: float = 3.0
|
|
27
|
+
predicted_hypoglycemia_threshold: float = 60.0
|
|
28
|
+
predicted_hypoglycemia_horizon_minutes: int = 30
|
|
29
|
+
|
|
30
|
+
# Formal safety contract (logic validation)
|
|
31
|
+
contract_enabled: bool = True
|
|
32
|
+
contract_glucose_threshold: float = 90.0
|
|
33
|
+
contract_trend_threshold_mgdl_min: float = -1.0 # -5 mg/dL per 5 minutes
|
|
34
|
+
|
|
35
|
+
# Simulation termination limits
|
|
36
|
+
critical_glucose_threshold: float = 40.0
|
|
37
|
+
critical_glucose_duration_minutes: int = 30
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
from iints.core.safety.config import SafetyConfig
|
|
4
|
+
|
|
5
|
+
class InputValidator:
|
|
6
|
+
"""
|
|
7
|
+
A biological validation filter for sensor inputs to ensure they are
|
|
8
|
+
physiologically plausible before being used by an algorithm.
|
|
9
|
+
This component makes the system robust against common sensor errors.
|
|
10
|
+
"""
|
|
11
|
+
def __init__(self,
|
|
12
|
+
min_glucose: float = 20.0,
|
|
13
|
+
max_glucose: float = 600.0,
|
|
14
|
+
max_glucose_delta_per_5_min: float = 35.0,
|
|
15
|
+
safety_config: Optional[SafetyConfig] = None):
|
|
16
|
+
"""
|
|
17
|
+
Initializes the validator with plausible biological limits.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
min_glucose (float): The absolute minimum plausible glucose value (mg/dL).
|
|
21
|
+
max_glucose (float): The absolute maximum plausible glucose value (mg/dL).
|
|
22
|
+
max_glucose_delta_per_5_min (float): The maximum plausible change in glucose
|
|
23
|
+
over a 5-minute period (mg/dL).
|
|
24
|
+
"""
|
|
25
|
+
if safety_config is not None:
|
|
26
|
+
min_glucose = safety_config.min_glucose
|
|
27
|
+
max_glucose = safety_config.max_glucose
|
|
28
|
+
max_glucose_delta_per_5_min = safety_config.max_glucose_delta_per_5_min
|
|
29
|
+
|
|
30
|
+
self.min_glucose = min_glucose
|
|
31
|
+
self.max_glucose = max_glucose
|
|
32
|
+
self.max_glucose_delta_per_5_min = max_glucose_delta_per_5_min
|
|
33
|
+
self.last_valid_glucose: Optional[float] = None
|
|
34
|
+
self.last_validation_time: Optional[float] = None
|
|
35
|
+
|
|
36
|
+
def reset(self):
|
|
37
|
+
"""Resets the state of the validator for a new simulation."""
|
|
38
|
+
self.last_valid_glucose = None
|
|
39
|
+
self.last_validation_time = None
|
|
40
|
+
|
|
41
|
+
def get_state(self) -> dict:
|
|
42
|
+
return {
|
|
43
|
+
"last_valid_glucose": self.last_valid_glucose,
|
|
44
|
+
"last_validation_time": self.last_validation_time,
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
def set_state(self, state: dict) -> None:
|
|
48
|
+
self.last_valid_glucose = state.get("last_valid_glucose")
|
|
49
|
+
self.last_validation_time = state.get("last_validation_time")
|
|
50
|
+
|
|
51
|
+
def validate_glucose(self, glucose_value: float, current_time: float) -> float:
|
|
52
|
+
"""
|
|
53
|
+
Validates a glucose reading against absolute and rate-of-change limits.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
glucose_value (float): The incoming glucose reading from the sensor.
|
|
57
|
+
current_time (float): The current simulation time in minutes.
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
float: The validated glucose value.
|
|
61
|
+
|
|
62
|
+
Raises:
|
|
63
|
+
ValueError: If the value is outside biological plausibility limits.
|
|
64
|
+
"""
|
|
65
|
+
# 1. Absolute biological plausibility check
|
|
66
|
+
if not (self.min_glucose <= glucose_value <= self.max_glucose):
|
|
67
|
+
raise ValueError(
|
|
68
|
+
f"BIOLOGICAL_PLAUSIBILITY_ERROR: Glucose {glucose_value} mg/dL is outside the "
|
|
69
|
+
f"valid range [{self.min_glucose}, {self.max_glucose}]."
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
# 2. Rate-of-change check for unrealistic jumps
|
|
73
|
+
if self.last_valid_glucose is not None and self.last_validation_time is not None:
|
|
74
|
+
time_delta = current_time - self.last_validation_time
|
|
75
|
+
if time_delta > 0:
|
|
76
|
+
# Normalize the max allowed delta to the actual time step
|
|
77
|
+
allowed_delta = self.max_glucose_delta_per_5_min * (time_delta / 5.0)
|
|
78
|
+
glucose_delta = abs(glucose_value - self.last_valid_glucose)
|
|
79
|
+
|
|
80
|
+
if glucose_delta > allowed_delta:
|
|
81
|
+
raise ValueError(
|
|
82
|
+
f"RATE_OF_CHANGE_ERROR: Glucose jump of {glucose_delta:.1f} mg/dL over "
|
|
83
|
+
f"{time_delta:.1f} min is unrealistic (max allowed: {allowed_delta:.1f} mg/dL)."
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
# If all checks pass, update state and return the value
|
|
87
|
+
self.last_valid_glucose = glucose_value
|
|
88
|
+
self.last_validation_time = current_time
|
|
89
|
+
return glucose_value
|
|
90
|
+
|
|
91
|
+
def validate_insulin(self, dose: float) -> float:
|
|
92
|
+
"""Validates that a proposed insulin dose is non-negative."""
|
|
93
|
+
if dose < 0:
|
|
94
|
+
return 0.0
|
|
95
|
+
return dose
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
from iints.core.safety.config import SafetyConfig
|
|
4
|
+
from iints.core.supervisor import IndependentSupervisor as FullSupervisor
|
|
5
|
+
|
|
6
|
+
class IndependentSupervisor(FullSupervisor):
|
|
7
|
+
"""
|
|
8
|
+
Safety supervisor that operates independently to validate insulin delivery.
|
|
9
|
+
Enforces hard constraints on insulin delivery to prevent hypoglycemia and other hazards.
|
|
10
|
+
"""
|
|
11
|
+
def __init__(self, safety_config: Optional[SafetyConfig] = None):
|
|
12
|
+
super().__init__(safety_config=safety_config)
|
|
13
|
+
|
|
14
|
+
def validate_insulin_dose(
|
|
15
|
+
self,
|
|
16
|
+
proposed_dose: float,
|
|
17
|
+
current_glucose: float,
|
|
18
|
+
active_insulin: float,
|
|
19
|
+
time_since_last_dose: float,
|
|
20
|
+
) -> float:
|
|
21
|
+
"""
|
|
22
|
+
Backward-compatible API that routes through the full supervisor.
|
|
23
|
+
"""
|
|
24
|
+
result = self.evaluate_safety(
|
|
25
|
+
current_glucose=current_glucose,
|
|
26
|
+
proposed_insulin=proposed_dose,
|
|
27
|
+
current_time=0.0,
|
|
28
|
+
current_iob=active_insulin,
|
|
29
|
+
)
|
|
30
|
+
return result["approved_insulin"]
|
|
31
|
+
|
|
32
|
+
# Alias for backward compatibility as the codebase migrates
|
|
33
|
+
SafetySupervisor = IndependentSupervisor
|
|
34
|
+
|
|
35
|
+
class InputValidator:
|
|
36
|
+
"""
|
|
37
|
+
Validates simulation inputs.
|
|
38
|
+
"""
|
|
39
|
+
pass
|
|
File without changes
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# src/simulation/scenario_parser.py
|
|
2
|
+
|
|
3
|
+
from typing import List, Dict, Any, Tuple
|
|
4
|
+
|
|
5
|
+
from iints.core.simulator import StressEvent
|
|
6
|
+
from iints.validation import load_scenario, scenario_to_payloads, build_stress_events
|
|
7
|
+
|
|
8
|
+
def parse_scenario(file_path: str) -> Tuple[Dict[str, Any], List[StressEvent]]:
|
|
9
|
+
"""
|
|
10
|
+
Parses a scenario from a JSON file.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
file_path: The path to the scenario JSON file.
|
|
14
|
+
|
|
15
|
+
Returns:
|
|
16
|
+
A tuple containing:
|
|
17
|
+
- A dictionary with scenario metadata (name, description).
|
|
18
|
+
- A list of StressEvent objects.
|
|
19
|
+
|
|
20
|
+
Raises:
|
|
21
|
+
ValueError: If the file format or content is invalid.
|
|
22
|
+
"""
|
|
23
|
+
try:
|
|
24
|
+
scenario = load_scenario(file_path)
|
|
25
|
+
except Exception as e:
|
|
26
|
+
raise ValueError(f"Invalid scenario file {file_path}: {e}")
|
|
27
|
+
|
|
28
|
+
metadata = {
|
|
29
|
+
"name": scenario.scenario_name,
|
|
30
|
+
"description": scenario.description or "",
|
|
31
|
+
"version": scenario.scenario_version,
|
|
32
|
+
"source_file": file_path,
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
payloads = scenario_to_payloads(scenario)
|
|
36
|
+
events: List[StressEvent] = build_stress_events(payloads)
|
|
37
|
+
return metadata, events
|
|
38
|
+
|
|
39
|
+
if __name__ == '__main__':
|
|
40
|
+
# A simple test to demonstrate the scenario parser
|
|
41
|
+
print("--- Testing Scenario Parser ---")
|
|
42
|
+
|
|
43
|
+
# Use the example scenario created earlier
|
|
44
|
+
example_path = "scenarios/example_scenario.json"
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
print(f"Parsing scenario file: {example_path}")
|
|
48
|
+
scenario_metadata, scenario_events = parse_scenario(example_path)
|
|
49
|
+
|
|
50
|
+
print("\nSuccessfully parsed scenario:")
|
|
51
|
+
print(f" Name: {scenario_metadata['name']}")
|
|
52
|
+
print(f" Description: {scenario_metadata['description']}")
|
|
53
|
+
|
|
54
|
+
print("\nEvents:")
|
|
55
|
+
for ev in scenario_events:
|
|
56
|
+
print(f" - {ev}")
|
|
57
|
+
|
|
58
|
+
except ValueError as e:
|
|
59
|
+
print(f"\nError parsing scenario: {e}")
|
|
60
|
+
except Exception as e:
|
|
61
|
+
print(f"\nAn unexpected error occurred: {e}")
|