iints-sdk-python35 0.1.15__tar.gz → 0.1.16__tar.gz
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_sdk_python35-0.1.15/src/iints_sdk_python35.egg-info → iints_sdk_python35-0.1.16}/PKG-INFO +1 -1
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/pyproject.toml +1 -1
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/__init__.py +4 -1
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/api/base_algorithm.py +7 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/core/algorithms/correction_bolus.py +15 -6
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/core/algorithms/fixed_basal_bolus.py +8 -2
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/core/patient/models.py +41 -2
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/core/safety/config.py +3 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/core/simulator.py +161 -10
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/core/supervisor.py +38 -2
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/validation/__init__.py +4 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/validation/schemas.py +11 -1
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16/src/iints_sdk_python35.egg-info}/PKG-INFO +1 -1
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/LICENSE +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/README.md +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/setup.cfg +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/analysis/__init__.py +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/analysis/algorithm_xray.py +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/analysis/baseline.py +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/analysis/clinical_benchmark.py +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/analysis/clinical_metrics.py +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/analysis/clinical_tir_analyzer.py +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/analysis/diabetes_metrics.py +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/analysis/edge_performance_monitor.py +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/analysis/explainability.py +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/analysis/explainable_ai.py +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/analysis/hardware_benchmark.py +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/analysis/metrics.py +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/analysis/reporting.py +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/analysis/sensor_filtering.py +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/analysis/validator.py +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/api/__init__.py +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/api/template_algorithm.py +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/assets/iints_logo.png +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/cli/__init__.py +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/cli/cli.py +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/core/__init__.py +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/core/algorithms/__init__.py +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/core/algorithms/battle_runner.py +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/core/algorithms/discovery.py +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/core/algorithms/hybrid_algorithm.py +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/core/algorithms/lstm_algorithm.py +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/core/algorithms/mock_algorithms.py +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/core/algorithms/pid_controller.py +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/core/algorithms/standard_pump_algo.py +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/core/device.py +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/core/device_manager.py +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/core/devices/__init__.py +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/core/devices/models.py +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/core/patient/__init__.py +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/core/patient/patient_factory.py +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/core/patient/profile.py +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/core/safety/__init__.py +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/core/safety/input_validator.py +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/core/safety/supervisor.py +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/core/simulation/__init__.py +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/core/simulation/scenario_parser.py +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/data/__init__.py +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/data/adapter.py +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/data/column_mapper.py +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/data/datasets.json +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/data/demo/__init__.py +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/data/demo/demo_cgm.csv +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/data/importer.py +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/data/ingestor.py +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/data/nightscout.py +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/data/quality_checker.py +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/data/registry.py +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/data/tidepool.py +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/data/universal_parser.py +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/data/virtual_patients/clinic_safe_baseline.yaml +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/data/virtual_patients/clinic_safe_hyper_challenge.yaml +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/data/virtual_patients/clinic_safe_hypo_prone.yaml +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/data/virtual_patients/clinic_safe_midnight.yaml +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/data/virtual_patients/clinic_safe_pizza.yaml +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/data/virtual_patients/clinic_safe_stress_meal.yaml +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/data/virtual_patients/default_patient.yaml +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/data/virtual_patients/patient_559_config.yaml +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/emulation/__init__.py +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/emulation/legacy_base.py +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/emulation/medtronic_780g.py +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/emulation/omnipod_5.py +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/emulation/tandem_controliq.py +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/highlevel.py +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/learning/__init__.py +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/learning/autonomous_optimizer.py +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/learning/learning_system.py +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/metrics.py +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/presets/__init__.py +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/presets/presets.json +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/scenarios/__init__.py +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/scenarios/generator.py +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/templates/__init__.py +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/templates/default_algorithm.py +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/templates/scenarios/__init__.py +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/templates/scenarios/example_scenario.json +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/utils/__init__.py +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/utils/plotting.py +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/visualization/__init__.py +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/visualization/cockpit.py +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/visualization/uncertainty_cloud.py +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints_sdk_python35.egg-info/SOURCES.txt +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints_sdk_python35.egg-info/dependency_links.txt +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints_sdk_python35.egg-info/entry_points.txt +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints_sdk_python35.egg-info/requires.txt +0 -0
- {iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints_sdk_python35.egg-info/top_level.txt +0 -0
{iints_sdk_python35-0.1.15/src/iints_sdk_python35.egg-info → iints_sdk_python35-0.1.16}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: iints-sdk-python35
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.16
|
|
4
4
|
Summary: A pre-clinical Edge-AI SDK for diabetes management validation.
|
|
5
5
|
Author-email: Rune Bobbaers <rune.bobbaers@gmail.com>
|
|
6
6
|
Project-URL: Homepage, https://github.com/python35/IINTS-SDK
|
|
@@ -3,7 +3,10 @@
|
|
|
3
3
|
import pandas as pd # Required for type hints like pd.DataFrame
|
|
4
4
|
from typing import Optional
|
|
5
5
|
|
|
6
|
-
__version__ = "0.1.
|
|
6
|
+
__version__ = "0.1.16"
|
|
7
|
+
|
|
8
|
+
# Note to developers: this SDK is currently maintained by a single author.
|
|
9
|
+
# Please report bugs via GitHub issues and feel free to contribute fixes via PRs.
|
|
7
10
|
|
|
8
11
|
# API Components for Algorithm Development
|
|
9
12
|
from .api.base_algorithm import (
|
|
@@ -12,6 +12,13 @@ class AlgorithmInput:
|
|
|
12
12
|
carb_intake: float = 0.0
|
|
13
13
|
patient_state: Dict[str, Any] = field(default_factory=dict)
|
|
14
14
|
current_time: float = 0.0 # Added current_time
|
|
15
|
+
carbs_on_board: float = 0.0
|
|
16
|
+
isf: Optional[float] = None
|
|
17
|
+
icr: Optional[float] = None
|
|
18
|
+
dia_minutes: Optional[float] = None
|
|
19
|
+
basal_rate_u_per_hr: Optional[float] = None
|
|
20
|
+
glucose_trend_mgdl_min: Optional[float] = None
|
|
21
|
+
predicted_glucose_30min: Optional[float] = None
|
|
15
22
|
|
|
16
23
|
|
|
17
24
|
@dataclass
|
|
@@ -28,15 +28,21 @@ class CorrectionBolus(InsulinAlgorithm):
|
|
|
28
28
|
def predict_insulin(self, data: AlgorithmInput) -> Dict[str, Any]:
|
|
29
29
|
self.why_log = [] # Clear the log for this prediction cycle
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
basal_rate = self.settings["fixed_basal_rate"]
|
|
32
|
+
if data.basal_rate_u_per_hr is not None:
|
|
33
|
+
basal_rate = float(data.basal_rate_u_per_hr)
|
|
34
|
+
basal_rate_units_per_minute = basal_rate / 60.0
|
|
32
35
|
basal_insulin = basal_rate_units_per_minute * data.time_step
|
|
33
|
-
self._log_reason(f"Basal insulin calculated ({
|
|
36
|
+
self._log_reason(f"Basal insulin calculated ({basal_rate} U/hr)", "basal_delivery", basal_insulin)
|
|
34
37
|
|
|
35
38
|
carb_intake = data.carb_intake
|
|
36
39
|
meal_bolus = 0.0
|
|
37
40
|
if carb_intake > 0:
|
|
38
|
-
|
|
39
|
-
|
|
41
|
+
carb_ratio = self.settings["carb_ratio"]
|
|
42
|
+
if data.icr is not None:
|
|
43
|
+
carb_ratio = float(data.icr)
|
|
44
|
+
meal_bolus = carb_intake / carb_ratio
|
|
45
|
+
self._log_reason(f"Meal bolus calculated for {carb_intake:.0f}g carbs (CR: {carb_ratio})", "meal_response", meal_bolus)
|
|
40
46
|
else:
|
|
41
47
|
self._log_reason("No meal bolus needed (no carb intake)", "meal_response", 0.0)
|
|
42
48
|
|
|
@@ -44,8 +50,11 @@ class CorrectionBolus(InsulinAlgorithm):
|
|
|
44
50
|
correction_bolus = 0.0
|
|
45
51
|
if data.current_glucose > self.settings["target_glucose"]:
|
|
46
52
|
glucose_deviation = data.current_glucose - self.settings["target_glucose"]
|
|
47
|
-
|
|
48
|
-
|
|
53
|
+
isf = self.settings["insulin_sensitivity_factor"]
|
|
54
|
+
if data.isf is not None:
|
|
55
|
+
isf = float(data.isf)
|
|
56
|
+
correction_bolus = glucose_deviation / isf
|
|
57
|
+
self._log_reason(f"Correction bolus calculated for high glucose (Current: {data.current_glucose:.0f}mg/dL, Target: {self.settings['target_glucose']:.0f}mg/dL, ISF: {isf})", "glucose_correction", correction_bolus)
|
|
49
58
|
else:
|
|
50
59
|
self._log_reason(f"No correction bolus needed (glucose at or below target: {self.settings['target_glucose']:.0f}mg/dL)", "glucose_correction", 0.0)
|
|
51
60
|
|
|
@@ -32,13 +32,19 @@ class FixedBasalBolus(InsulinAlgorithm):
|
|
|
32
32
|
Returns:
|
|
33
33
|
Dict[str, Any]: Contains 'basal_insulin' and 'bolus_insulin' for the time step.
|
|
34
34
|
"""
|
|
35
|
-
|
|
35
|
+
basal_rate = self.settings["fixed_basal_rate"]
|
|
36
|
+
if data.basal_rate_u_per_hr is not None:
|
|
37
|
+
basal_rate = float(data.basal_rate_u_per_hr)
|
|
38
|
+
basal_rate_units_per_minute = basal_rate / 60.0
|
|
36
39
|
basal_insulin = basal_rate_units_per_minute * data.time_step
|
|
37
40
|
|
|
38
41
|
carb_intake = data.carb_intake
|
|
39
42
|
bolus_insulin = 0.0
|
|
40
43
|
if carb_intake > 0:
|
|
41
|
-
|
|
44
|
+
carb_ratio = self.settings["carb_ratio"]
|
|
45
|
+
if data.icr is not None:
|
|
46
|
+
carb_ratio = float(data.icr)
|
|
47
|
+
bolus_insulin = carb_intake / carb_ratio
|
|
42
48
|
|
|
43
49
|
return {
|
|
44
50
|
"basal_insulin": basal_insulin,
|
|
@@ -21,7 +21,8 @@ class CustomPatientModel:
|
|
|
21
21
|
meal_mismatch_epsilon: float = 1.0, # Factor for meal mismatch
|
|
22
22
|
dawn_phenomenon_strength: float = 0.0, # mg/dL per hour
|
|
23
23
|
dawn_start_hour: float = 4.0,
|
|
24
|
-
dawn_end_hour: float = 8.0
|
|
24
|
+
dawn_end_hour: float = 8.0,
|
|
25
|
+
carb_absorption_duration_minutes: float = 240.0):
|
|
25
26
|
"""
|
|
26
27
|
Initializes the patient model with simplified parameters.
|
|
27
28
|
|
|
@@ -48,6 +49,7 @@ class CustomPatientModel:
|
|
|
48
49
|
self.dawn_phenomenon_strength = dawn_phenomenon_strength
|
|
49
50
|
self.dawn_start_hour = dawn_start_hour
|
|
50
51
|
self.dawn_end_hour = dawn_end_hour
|
|
52
|
+
self.carb_absorption_duration_minutes = carb_absorption_duration_minutes
|
|
51
53
|
|
|
52
54
|
|
|
53
55
|
self.initial_glucose = initial_glucose
|
|
@@ -156,12 +158,21 @@ class CustomPatientModel:
|
|
|
156
158
|
# Simple model: carbs absorb over time, peaking around meal_effect_delay
|
|
157
159
|
# This is a very rough approximation
|
|
158
160
|
absorption_factor = 0.0
|
|
159
|
-
if carb_event['time_since_intake'] <=
|
|
161
|
+
if carb_event['time_since_intake'] <= self.carb_absorption_duration_minutes: # Carbs absorb for ~4 hours
|
|
160
162
|
absorption_factor = self.glucose_absorption_rate * (np.exp(-carb_event['time_since_intake'] / self.meal_effect_delay) - np.exp(-carb_event['time_since_intake'] / (self.meal_effect_delay * 0.5)))
|
|
161
163
|
carb_effect += carb_event['amount'] * absorption_factor
|
|
162
164
|
new_active_carb_intakes.append(carb_event)
|
|
163
165
|
# Carbs are "gone" after a while, or their effect is negligible
|
|
164
166
|
self.active_carb_intakes = new_active_carb_intakes
|
|
167
|
+
# Estimate carbs on board based on remaining absorption window
|
|
168
|
+
carb_remaining = 0.0
|
|
169
|
+
for carb_event in self.active_carb_intakes:
|
|
170
|
+
remaining_fraction = max(
|
|
171
|
+
0.0,
|
|
172
|
+
1.0 - (carb_event['time_since_intake'] / self.carb_absorption_duration_minutes),
|
|
173
|
+
)
|
|
174
|
+
carb_remaining += carb_event['amount'] * remaining_fraction
|
|
175
|
+
self.carbs_on_board = carb_remaining
|
|
165
176
|
|
|
166
177
|
|
|
167
178
|
# --- Exercise Effect ---
|
|
@@ -217,8 +228,36 @@ class CustomPatientModel:
|
|
|
217
228
|
"current_glucose": self.current_glucose,
|
|
218
229
|
"insulin_on_board": self.insulin_on_board,
|
|
219
230
|
"carbs_on_board": self.carbs_on_board,
|
|
231
|
+
"basal_rate_u_per_hr": self.basal_insulin_rate,
|
|
232
|
+
"isf": self.insulin_sensitivity,
|
|
233
|
+
"icr": self.carb_factor,
|
|
234
|
+
"dia_minutes": self.insulin_action_duration,
|
|
220
235
|
}
|
|
221
236
|
|
|
237
|
+
def get_ratio_state(self) -> Dict[str, float]:
|
|
238
|
+
return {
|
|
239
|
+
"basal_rate_u_per_hr": self.basal_insulin_rate,
|
|
240
|
+
"isf": self.insulin_sensitivity,
|
|
241
|
+
"icr": self.carb_factor,
|
|
242
|
+
"dia_minutes": self.insulin_action_duration,
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
def set_ratio_state(
|
|
246
|
+
self,
|
|
247
|
+
isf: Optional[float] = None,
|
|
248
|
+
icr: Optional[float] = None,
|
|
249
|
+
basal_rate: Optional[float] = None,
|
|
250
|
+
dia_minutes: Optional[float] = None,
|
|
251
|
+
) -> None:
|
|
252
|
+
if isf is not None:
|
|
253
|
+
self.insulin_sensitivity = float(isf)
|
|
254
|
+
if icr is not None:
|
|
255
|
+
self.carb_factor = float(icr)
|
|
256
|
+
if basal_rate is not None:
|
|
257
|
+
self.basal_insulin_rate = float(basal_rate)
|
|
258
|
+
if dia_minutes is not None:
|
|
259
|
+
self.insulin_action_duration = float(dia_minutes)
|
|
260
|
+
|
|
222
261
|
def get_state(self) -> Dict[str, Any]:
|
|
223
262
|
return {
|
|
224
263
|
"current_glucose": self.current_glucose,
|
|
@@ -23,6 +23,9 @@ class SafetyConfig:
|
|
|
23
23
|
max_iob: float = 4.0
|
|
24
24
|
trend_stop: float = -2.0
|
|
25
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
|
|
26
29
|
|
|
27
30
|
# Simulation termination limits
|
|
28
31
|
critical_glucose_threshold: float = 40.0
|
|
@@ -25,7 +25,19 @@ class SimulationLimitError(RuntimeError):
|
|
|
25
25
|
|
|
26
26
|
class StressEvent:
|
|
27
27
|
"""Represents a discrete event that can occur during a simulation for stress testing."""
|
|
28
|
-
def __init__(
|
|
28
|
+
def __init__(
|
|
29
|
+
self,
|
|
30
|
+
start_time: int,
|
|
31
|
+
event_type: str,
|
|
32
|
+
value: Any = None,
|
|
33
|
+
reported_value: Any = None,
|
|
34
|
+
absorption_delay_minutes: int = 0,
|
|
35
|
+
duration: int = 0,
|
|
36
|
+
isf: Optional[float] = None,
|
|
37
|
+
icr: Optional[float] = None,
|
|
38
|
+
basal_rate: Optional[float] = None,
|
|
39
|
+
dia_minutes: Optional[float] = None,
|
|
40
|
+
) -> None:
|
|
29
41
|
"""
|
|
30
42
|
Args:
|
|
31
43
|
start_time (int): The simulation time (in minutes) when the event should occur.
|
|
@@ -41,12 +53,29 @@ class StressEvent:
|
|
|
41
53
|
self.reported_value = reported_value # New attribute
|
|
42
54
|
self.absorption_delay_minutes = absorption_delay_minutes # New attribute
|
|
43
55
|
self.duration = duration
|
|
56
|
+
self.isf = isf
|
|
57
|
+
self.icr = icr
|
|
58
|
+
self.basal_rate = basal_rate
|
|
59
|
+
self.dia_minutes = dia_minutes
|
|
44
60
|
|
|
45
61
|
def __str__(self) -> str:
|
|
46
62
|
reported_str = f", Reported: {self.reported_value}" if self.reported_value is not None else ""
|
|
63
|
+
ratio_str = ""
|
|
64
|
+
if self.event_type == "ratio_change":
|
|
65
|
+
parts = []
|
|
66
|
+
if self.isf is not None:
|
|
67
|
+
parts.append(f"ISF={self.isf}")
|
|
68
|
+
if self.icr is not None:
|
|
69
|
+
parts.append(f"ICR={self.icr}")
|
|
70
|
+
if self.basal_rate is not None:
|
|
71
|
+
parts.append(f"Basal={self.basal_rate}")
|
|
72
|
+
if self.dia_minutes is not None:
|
|
73
|
+
parts.append(f"DIA={self.dia_minutes}")
|
|
74
|
+
if parts:
|
|
75
|
+
ratio_str = ", " + ", ".join(parts)
|
|
47
76
|
delay_str = f", Delay: {self.absorption_delay_minutes}m" if self.absorption_delay_minutes > 0 else ""
|
|
48
77
|
duration_str = f", Duration: {self.duration}m" if self.duration > 0 else ""
|
|
49
|
-
return f"Event(Time: {self.start_time}m, Type: {self.event_type}, Value: {self.value}{reported_str}{delay_str}{duration_str})"
|
|
78
|
+
return f"Event(Time: {self.start_time}m, Type: {self.event_type}, Value: {self.value}{reported_str}{ratio_str}{delay_str}{duration_str})"
|
|
50
79
|
|
|
51
80
|
class Simulator:
|
|
52
81
|
"""
|
|
@@ -87,25 +116,27 @@ class Simulator:
|
|
|
87
116
|
if self.seed is not None:
|
|
88
117
|
np.random.seed(self.seed) # Set numpy seed for reproducibility
|
|
89
118
|
# Potentially set other seeds here if other random modules are used (e.g., random.seed(self.seed))
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
self.
|
|
93
|
-
self.input_validator = InputValidator(safety_config=safety_config)
|
|
119
|
+
self.safety_config = safety_config or SafetyConfig()
|
|
120
|
+
self.supervisor = IndependentSupervisor(safety_config=self.safety_config)
|
|
121
|
+
self.input_validator = InputValidator(safety_config=self.safety_config)
|
|
94
122
|
self.sensor_model = sensor_model or SensorModel(seed=seed)
|
|
95
123
|
self.pump_model = pump_model or PumpModel(seed=seed)
|
|
96
124
|
self.on_step = on_step
|
|
97
125
|
self.meal_queue: List[Dict[str, Any]] = [] # Initialize meal queue for delayed absorption
|
|
98
126
|
self.audit_log_path = audit_log_path
|
|
99
127
|
self.enable_profiling = enable_profiling
|
|
100
|
-
if safety_config is not None:
|
|
101
|
-
critical_glucose_threshold = safety_config.critical_glucose_threshold
|
|
102
|
-
critical_glucose_duration_minutes = safety_config.critical_glucose_duration_minutes
|
|
128
|
+
if self.safety_config is not None:
|
|
129
|
+
critical_glucose_threshold = self.safety_config.critical_glucose_threshold
|
|
130
|
+
critical_glucose_duration_minutes = self.safety_config.critical_glucose_duration_minutes
|
|
103
131
|
self.critical_glucose_threshold = critical_glucose_threshold
|
|
104
132
|
self.critical_glucose_duration_minutes = critical_glucose_duration_minutes
|
|
105
133
|
self._critical_low_minutes = 0
|
|
106
134
|
self._current_time = 0
|
|
107
135
|
self._resume_state = False
|
|
108
136
|
self._termination_info: Optional[Dict[str, Any]] = None
|
|
137
|
+
self._ratio_overrides: List[Dict[str, Any]] = []
|
|
138
|
+
self._base_ratio_state: Optional[Dict[str, float]] = None
|
|
139
|
+
self._previous_glucose_for_trend: Optional[float] = None
|
|
109
140
|
self._profiling_samples: Dict[str, List[float]] = {
|
|
110
141
|
"algorithm_latency_ms": [],
|
|
111
142
|
"supervisor_latency_ms": [],
|
|
@@ -128,6 +159,67 @@ class Simulator:
|
|
|
128
159
|
except IOError as e:
|
|
129
160
|
logger.warning("Could not write to audit log file at %s. Error: %s", self.audit_log_path, e)
|
|
130
161
|
|
|
162
|
+
def _apply_ratio_overrides(self, current_time: float) -> Dict[str, float]:
|
|
163
|
+
if self._base_ratio_state is None:
|
|
164
|
+
self._base_ratio_state = self.patient_model.get_ratio_state()
|
|
165
|
+
effective = dict(self._base_ratio_state)
|
|
166
|
+
active = [
|
|
167
|
+
override
|
|
168
|
+
for override in self._ratio_overrides
|
|
169
|
+
if override["start_time"] <= current_time <= override["end_time"]
|
|
170
|
+
]
|
|
171
|
+
if active:
|
|
172
|
+
latest = active[-1]
|
|
173
|
+
if latest.get("isf") is not None:
|
|
174
|
+
effective["isf"] = latest["isf"]
|
|
175
|
+
if latest.get("icr") is not None:
|
|
176
|
+
effective["icr"] = latest["icr"]
|
|
177
|
+
if latest.get("basal_rate_u_per_hr") is not None:
|
|
178
|
+
effective["basal_rate_u_per_hr"] = latest["basal_rate_u_per_hr"]
|
|
179
|
+
if latest.get("dia_minutes") is not None:
|
|
180
|
+
effective["dia_minutes"] = latest["dia_minutes"]
|
|
181
|
+
|
|
182
|
+
self.patient_model.set_ratio_state(
|
|
183
|
+
isf=effective.get("isf"),
|
|
184
|
+
icr=effective.get("icr"),
|
|
185
|
+
basal_rate=effective.get("basal_rate_u_per_hr"),
|
|
186
|
+
dia_minutes=effective.get("dia_minutes"),
|
|
187
|
+
)
|
|
188
|
+
try:
|
|
189
|
+
if effective.get("isf") is not None:
|
|
190
|
+
self.algorithm.set_isf(float(effective["isf"]))
|
|
191
|
+
if effective.get("icr") is not None:
|
|
192
|
+
self.algorithm.set_icr(float(effective["icr"]))
|
|
193
|
+
except Exception:
|
|
194
|
+
# Algorithm may ignore dynamic ratio updates; no hard failure.
|
|
195
|
+
pass
|
|
196
|
+
return effective
|
|
197
|
+
|
|
198
|
+
def _predict_glucose(
|
|
199
|
+
self,
|
|
200
|
+
current_glucose: float,
|
|
201
|
+
trend_mgdl_min: float,
|
|
202
|
+
iob_units: float,
|
|
203
|
+
cob_grams: float,
|
|
204
|
+
isf: float,
|
|
205
|
+
icr: float,
|
|
206
|
+
dia_minutes: float,
|
|
207
|
+
horizon_minutes: int,
|
|
208
|
+
carb_absorption_minutes: float,
|
|
209
|
+
) -> float:
|
|
210
|
+
trend_component = trend_mgdl_min * horizon_minutes
|
|
211
|
+
|
|
212
|
+
insulin_component = 0.0
|
|
213
|
+
if dia_minutes > 0:
|
|
214
|
+
insulin_component = -iob_units * isf * min(horizon_minutes / dia_minutes, 1.0)
|
|
215
|
+
|
|
216
|
+
carb_component = 0.0
|
|
217
|
+
if icr > 0:
|
|
218
|
+
carb_effect_per_gram = isf / icr
|
|
219
|
+
carb_component = cob_grams * carb_effect_per_gram * min(horizon_minutes / carb_absorption_minutes, 1.0)
|
|
220
|
+
|
|
221
|
+
return current_glucose + trend_component + insulin_component + carb_component
|
|
222
|
+
|
|
131
223
|
def add_stress_event(self, event: StressEvent) -> None:
|
|
132
224
|
"""Adds a stress event to be triggered during the simulation."""
|
|
133
225
|
self.stress_events.append(event)
|
|
@@ -244,6 +336,9 @@ class Simulator:
|
|
|
244
336
|
self._critical_low_minutes = 0
|
|
245
337
|
self._current_time = 0
|
|
246
338
|
self._termination_info = None
|
|
339
|
+
self._ratio_overrides = []
|
|
340
|
+
self._base_ratio_state = self.patient_model.get_ratio_state()
|
|
341
|
+
self._previous_glucose_for_trend = None
|
|
247
342
|
else:
|
|
248
343
|
self._resume_state = False
|
|
249
344
|
if self.enable_profiling:
|
|
@@ -292,6 +387,18 @@ class Simulator:
|
|
|
292
387
|
self.add_stress_event(end_event)
|
|
293
388
|
elif event.event_type == 'exercise_end':
|
|
294
389
|
self.patient_model.stop_exercise()
|
|
390
|
+
elif event.event_type == 'ratio_change':
|
|
391
|
+
duration = event.duration if event.duration > 0 else float("inf")
|
|
392
|
+
self._ratio_overrides.append(
|
|
393
|
+
{
|
|
394
|
+
"start_time": current_time,
|
|
395
|
+
"end_time": current_time + duration,
|
|
396
|
+
"isf": event.isf,
|
|
397
|
+
"icr": event.icr,
|
|
398
|
+
"basal_rate_u_per_hr": event.basal_rate,
|
|
399
|
+
"dia_minutes": event.dia_minutes,
|
|
400
|
+
}
|
|
401
|
+
)
|
|
295
402
|
|
|
296
403
|
events_to_process_now.append(event) # Mark for removal from stress_events list
|
|
297
404
|
|
|
@@ -319,12 +426,44 @@ class Simulator:
|
|
|
319
426
|
# This ensures even sensor error stress events are validated
|
|
320
427
|
glucose_to_algorithm = self.input_validator.validate_glucose(glucose_to_algorithm, float(current_time))
|
|
321
428
|
|
|
429
|
+
# Apply dynamic ratio overrides (ISF/ICR/DIA/Basal) if any
|
|
430
|
+
ratio_state = self._apply_ratio_overrides(float(current_time))
|
|
431
|
+
effective_isf = float(ratio_state.get("isf", self.patient_model.insulin_sensitivity))
|
|
432
|
+
effective_icr = float(ratio_state.get("icr", self.patient_model.carb_factor))
|
|
433
|
+
effective_dia = float(ratio_state.get("dia_minutes", self.patient_model.insulin_action_duration))
|
|
434
|
+
effective_basal = float(ratio_state.get("basal_rate_u_per_hr", self.patient_model.basal_insulin_rate))
|
|
435
|
+
|
|
436
|
+
# Glucose trend (mg/dL per minute) based on sensor value
|
|
437
|
+
glucose_trend = 0.0
|
|
438
|
+
if self._previous_glucose_for_trend is not None:
|
|
439
|
+
glucose_trend = (glucose_to_algorithm - self._previous_glucose_for_trend) / float(self.time_step)
|
|
440
|
+
self._previous_glucose_for_trend = glucose_to_algorithm
|
|
441
|
+
|
|
442
|
+
predicted_glucose_30 = self._predict_glucose(
|
|
443
|
+
current_glucose=glucose_to_algorithm,
|
|
444
|
+
trend_mgdl_min=glucose_trend,
|
|
445
|
+
iob_units=self.patient_model.insulin_on_board,
|
|
446
|
+
cob_grams=self.patient_model.carbs_on_board,
|
|
447
|
+
isf=effective_isf,
|
|
448
|
+
icr=effective_icr,
|
|
449
|
+
dia_minutes=effective_dia,
|
|
450
|
+
horizon_minutes=self.safety_config.predicted_hypoglycemia_horizon_minutes,
|
|
451
|
+
carb_absorption_minutes=self.patient_model.carb_absorption_duration_minutes,
|
|
452
|
+
)
|
|
453
|
+
|
|
322
454
|
# --- Algorithm Input ---
|
|
323
455
|
algo_input = AlgorithmInput(
|
|
324
456
|
current_glucose=glucose_to_algorithm,
|
|
325
457
|
time_step=self.time_step,
|
|
326
458
|
insulin_on_board=self.patient_model.insulin_on_board,
|
|
327
459
|
carb_intake=algo_carb_intake_this_step, # Use algo's perspective of carbs
|
|
460
|
+
carbs_on_board=self.patient_model.carbs_on_board,
|
|
461
|
+
isf=effective_isf,
|
|
462
|
+
icr=effective_icr,
|
|
463
|
+
dia_minutes=effective_dia,
|
|
464
|
+
basal_rate_u_per_hr=effective_basal,
|
|
465
|
+
glucose_trend_mgdl_min=glucose_trend,
|
|
466
|
+
predicted_glucose_30min=predicted_glucose_30,
|
|
328
467
|
patient_state=self.patient_model.get_patient_state(),
|
|
329
468
|
current_time=float(current_time) # Pass current_time
|
|
330
469
|
)
|
|
@@ -346,11 +485,17 @@ class Simulator:
|
|
|
346
485
|
|
|
347
486
|
# --- Safety Supervision ---
|
|
348
487
|
start_perf_time = time.perf_counter()
|
|
488
|
+
proposed_basal_units = float(insulin_output.get("basal_insulin", 0.0))
|
|
489
|
+
basal_limit_u_per_hr = effective_basal * self.safety_config.max_basal_multiplier
|
|
490
|
+
basal_limit_units = (basal_limit_u_per_hr / 60.0) * float(self.time_step)
|
|
349
491
|
safety_result = self.supervisor.evaluate_safety(
|
|
350
492
|
current_glucose=glucose_to_algorithm,
|
|
351
493
|
proposed_insulin=algo_recommended_insulin,
|
|
352
494
|
current_time=float(current_time),
|
|
353
|
-
current_iob=self.patient_model.insulin_on_board
|
|
495
|
+
current_iob=self.patient_model.insulin_on_board,
|
|
496
|
+
predicted_glucose_30min=predicted_glucose_30,
|
|
497
|
+
basal_insulin_units=proposed_basal_units,
|
|
498
|
+
basal_limit_units=basal_limit_units,
|
|
354
499
|
)
|
|
355
500
|
supervisor_latency_ms = (time.perf_counter() - start_perf_time) * 1000
|
|
356
501
|
if self.enable_profiling:
|
|
@@ -415,6 +560,8 @@ class Simulator:
|
|
|
415
560
|
"time_minutes": current_time,
|
|
416
561
|
"glucose_actual_mgdl": actual_glucose_reading,
|
|
417
562
|
"glucose_to_algo_mgdl": glucose_to_algorithm,
|
|
563
|
+
"glucose_trend_mgdl_min": glucose_trend,
|
|
564
|
+
"predicted_glucose_30min": predicted_glucose_30,
|
|
418
565
|
"delivered_insulin_units": delivered_insulin,
|
|
419
566
|
"algo_recommended_insulin_units": algo_recommended_insulin,
|
|
420
567
|
"sensor_status": sensor_reading.status,
|
|
@@ -426,6 +573,10 @@ class Simulator:
|
|
|
426
573
|
"carb_intake_grams": patient_carb_intake_this_step,
|
|
427
574
|
"patient_iob_units": self.patient_model.insulin_on_board,
|
|
428
575
|
"patient_cob_grams": self.patient_model.carbs_on_board,
|
|
576
|
+
"effective_isf": effective_isf,
|
|
577
|
+
"effective_icr": effective_icr,
|
|
578
|
+
"effective_basal_rate_u_per_hr": effective_basal,
|
|
579
|
+
"effective_dia_minutes": effective_dia,
|
|
429
580
|
"uncertainty": insulin_output.get("uncertainty", 0.0),
|
|
430
581
|
"fallback_triggered": insulin_output.get("fallback_triggered", False),
|
|
431
582
|
"safety_level": safety_level.value,
|
|
@@ -45,6 +45,8 @@ class IndependentSupervisor:
|
|
|
45
45
|
max_iob=4.0, # Units
|
|
46
46
|
trend_stop=-2.0, # mg/dL per minute
|
|
47
47
|
hypo_cutoff=70.0, # mg/dL
|
|
48
|
+
predicted_hypoglycemia_threshold=60.0, # mg/dL
|
|
49
|
+
predicted_hypoglycemia_horizon_minutes=30, # minutes
|
|
48
50
|
safety_config: Optional["SafetyConfig"] = None):
|
|
49
51
|
|
|
50
52
|
if safety_config is not None:
|
|
@@ -57,6 +59,8 @@ class IndependentSupervisor:
|
|
|
57
59
|
max_iob = safety_config.max_iob
|
|
58
60
|
trend_stop = safety_config.trend_stop
|
|
59
61
|
hypo_cutoff = safety_config.hypo_cutoff
|
|
62
|
+
predicted_hypoglycemia_threshold = safety_config.predicted_hypoglycemia_threshold
|
|
63
|
+
predicted_hypoglycemia_horizon_minutes = safety_config.predicted_hypoglycemia_horizon_minutes
|
|
60
64
|
|
|
61
65
|
self.hypoglycemia_threshold = hypoglycemia_threshold
|
|
62
66
|
self.severe_hypoglycemia_threshold = severe_hypoglycemia_threshold
|
|
@@ -67,6 +71,8 @@ class IndependentSupervisor:
|
|
|
67
71
|
self.max_iob = max_iob
|
|
68
72
|
self.trend_stop = trend_stop
|
|
69
73
|
self.hypo_cutoff = hypo_cutoff
|
|
74
|
+
self.predicted_hypoglycemia_threshold = predicted_hypoglycemia_threshold
|
|
75
|
+
self.predicted_hypoglycemia_horizon_minutes = predicted_hypoglycemia_horizon_minutes
|
|
70
76
|
|
|
71
77
|
# State tracking
|
|
72
78
|
self.glucose_history: List[Tuple[float, float]] = []
|
|
@@ -75,8 +81,16 @@ class IndependentSupervisor:
|
|
|
75
81
|
self.last_iob = 0.0
|
|
76
82
|
self.dose_history: List[tuple] = []
|
|
77
83
|
|
|
78
|
-
def evaluate_safety(
|
|
79
|
-
|
|
84
|
+
def evaluate_safety(
|
|
85
|
+
self,
|
|
86
|
+
current_glucose: float,
|
|
87
|
+
proposed_insulin: float,
|
|
88
|
+
current_time: float,
|
|
89
|
+
current_iob: float = 0.0,
|
|
90
|
+
predicted_glucose_30min: Optional[float] = None,
|
|
91
|
+
basal_insulin_units: Optional[float] = None,
|
|
92
|
+
basal_limit_units: Optional[float] = None,
|
|
93
|
+
) -> Dict[str, Any]:
|
|
80
94
|
"""
|
|
81
95
|
Evaluate safety of proposed insulin dose based on current glucose and IOB.
|
|
82
96
|
Returns modified insulin dose and safety status.
|
|
@@ -90,6 +104,28 @@ class IndependentSupervisor:
|
|
|
90
104
|
self.glucose_history.append((current_time, current_glucose))
|
|
91
105
|
if len(self.glucose_history) > 20: # Keep last 20 readings
|
|
92
106
|
self.glucose_history.pop(0)
|
|
107
|
+
|
|
108
|
+
# Predictive hypo guard (30-min horizon)
|
|
109
|
+
if predicted_glucose_30min is not None:
|
|
110
|
+
if predicted_glucose_30min <= self.predicted_hypoglycemia_threshold:
|
|
111
|
+
safety_status = SafetyLevel.EMERGENCY
|
|
112
|
+
proposed_insulin = 0
|
|
113
|
+
actions_taken.append(
|
|
114
|
+
f"PREDICTED_HYPO: {predicted_glucose_30min:.1f} mg/dL in "
|
|
115
|
+
f"{self.predicted_hypoglycemia_horizon_minutes} min"
|
|
116
|
+
)
|
|
117
|
+
self.emergency_mode = True
|
|
118
|
+
|
|
119
|
+
# Basal rate limit (relative to patient basal)
|
|
120
|
+
if basal_insulin_units is not None and basal_limit_units is not None:
|
|
121
|
+
if basal_insulin_units > basal_limit_units:
|
|
122
|
+
excess = basal_insulin_units - basal_limit_units
|
|
123
|
+
proposed_insulin = max(0.0, proposed_insulin - excess)
|
|
124
|
+
actions_taken.append(
|
|
125
|
+
f"BASAL_LIMIT: basal {basal_insulin_units:.2f}U exceeds "
|
|
126
|
+
f"limit {basal_limit_units:.2f}U"
|
|
127
|
+
)
|
|
128
|
+
safety_status = max(safety_status, SafetyLevel.WARNING, key=lambda x: x.value)
|
|
93
129
|
|
|
94
130
|
# 1. Hard Hypo Cutoff (absolute stop)
|
|
95
131
|
if current_glucose <= self.hypo_cutoff:
|
|
@@ -77,6 +77,10 @@ def build_stress_events(payloads: List[Dict[str, Any]]) -> List[StressEvent]:
|
|
|
77
77
|
reported_value=event_data.get("reported_value"),
|
|
78
78
|
absorption_delay_minutes=event_data.get("absorption_delay_minutes", 0),
|
|
79
79
|
duration=event_data.get("duration", 0),
|
|
80
|
+
isf=event_data.get("isf"),
|
|
81
|
+
icr=event_data.get("icr"),
|
|
82
|
+
basal_rate=event_data.get("basal_rate"),
|
|
83
|
+
dia_minutes=event_data.get("dia_minutes"),
|
|
80
84
|
)
|
|
81
85
|
)
|
|
82
86
|
return events
|
|
@@ -9,11 +9,15 @@ class StressEventModel(BaseModel):
|
|
|
9
9
|
model_config = ConfigDict(extra="forbid")
|
|
10
10
|
|
|
11
11
|
start_time: int = Field(ge=0)
|
|
12
|
-
event_type: Literal["meal", "missed_meal", "sensor_error", "exercise", "exercise_end"]
|
|
12
|
+
event_type: Literal["meal", "missed_meal", "sensor_error", "exercise", "exercise_end", "ratio_change"]
|
|
13
13
|
value: Optional[float] = None
|
|
14
14
|
reported_value: Optional[float] = None
|
|
15
15
|
absorption_delay_minutes: int = Field(default=0, ge=0)
|
|
16
16
|
duration: int = Field(default=0, ge=0)
|
|
17
|
+
isf: Optional[float] = Field(default=None, gt=0)
|
|
18
|
+
icr: Optional[float] = Field(default=None, gt=0)
|
|
19
|
+
basal_rate: Optional[float] = Field(default=None, ge=0)
|
|
20
|
+
dia_minutes: Optional[float] = Field(default=None, gt=0)
|
|
17
21
|
|
|
18
22
|
@model_validator(mode="after")
|
|
19
23
|
def _check_required_fields(self) -> "StressEventModel":
|
|
@@ -26,6 +30,12 @@ class StressEventModel(BaseModel):
|
|
|
26
30
|
if self.event_type == "sensor_error":
|
|
27
31
|
if self.value is None:
|
|
28
32
|
raise ValueError("sensor_error requires a value")
|
|
33
|
+
if self.event_type == "ratio_change":
|
|
34
|
+
if all(
|
|
35
|
+
val is None
|
|
36
|
+
for val in (self.isf, self.icr, self.basal_rate, self.dia_minutes)
|
|
37
|
+
):
|
|
38
|
+
raise ValueError("ratio_change requires at least one ratio value (isf/icr/basal_rate/dia_minutes)")
|
|
29
39
|
return self
|
|
30
40
|
|
|
31
41
|
|
{iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16/src/iints_sdk_python35.egg-info}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: iints-sdk-python35
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.16
|
|
4
4
|
Summary: A pre-clinical Edge-AI SDK for diabetes management validation.
|
|
5
5
|
Author-email: Rune Bobbaers <rune.bobbaers@gmail.com>
|
|
6
6
|
Project-URL: Homepage, https://github.com/python35/IINTS-SDK
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/analysis/algorithm_xray.py
RENAMED
|
File without changes
|
|
File without changes
|
{iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/analysis/clinical_benchmark.py
RENAMED
|
File without changes
|
{iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/analysis/clinical_metrics.py
RENAMED
|
File without changes
|
{iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/analysis/clinical_tir_analyzer.py
RENAMED
|
File without changes
|
{iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/analysis/diabetes_metrics.py
RENAMED
|
File without changes
|
|
File without changes
|
{iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/analysis/explainability.py
RENAMED
|
File without changes
|
{iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/analysis/explainable_ai.py
RENAMED
|
File without changes
|
{iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/analysis/hardware_benchmark.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/analysis/sensor_filtering.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/core/algorithms/__init__.py
RENAMED
|
File without changes
|
{iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/core/algorithms/battle_runner.py
RENAMED
|
File without changes
|
{iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/core/algorithms/discovery.py
RENAMED
|
File without changes
|
|
File without changes
|
{iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/core/algorithms/lstm_algorithm.py
RENAMED
|
File without changes
|
{iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/core/algorithms/mock_algorithms.py
RENAMED
|
File without changes
|
{iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/core/algorithms/pid_controller.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/core/patient/patient_factory.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/core/safety/input_validator.py
RENAMED
|
File without changes
|
|
File without changes
|
{iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/core/simulation/__init__.py
RENAMED
|
File without changes
|
{iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/core/simulation/scenario_parser.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/emulation/medtronic_780g.py
RENAMED
|
File without changes
|
|
File without changes
|
{iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/emulation/tandem_controliq.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/learning/autonomous_optimizer.py
RENAMED
|
File without changes
|
{iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/learning/learning_system.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/templates/default_algorithm.py
RENAMED
|
File without changes
|
{iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/templates/scenarios/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints/visualization/uncertainty_cloud.py
RENAMED
|
File without changes
|
{iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints_sdk_python35.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{iints_sdk_python35-0.1.15 → iints_sdk_python35-0.1.16}/src/iints_sdk_python35.egg-info/requires.txt
RENAMED
|
File without changes
|
|
File without changes
|