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,11 @@
|
|
|
1
|
+
# Default patient configuration for CustomPatientModel
|
|
2
|
+
# Parameters based on src/iints/core/patient/models.py
|
|
3
|
+
|
|
4
|
+
basal_insulin_rate: 0.8 # U/hr
|
|
5
|
+
insulin_sensitivity: 50.0 # mg/dL per Unit
|
|
6
|
+
carb_factor: 10.0 # g/Unit
|
|
7
|
+
glucose_decay_rate: 0.05 # Rate at which glucose naturally decreases
|
|
8
|
+
initial_glucose: 120.0 # mg/dL
|
|
9
|
+
glucose_absorption_rate: 0.03 # Rate at which carbs are absorbed into glucose
|
|
10
|
+
insulin_action_duration: 300.0 # minutes, e.g., 5 hours
|
|
11
|
+
insulin_peak_time: 75.0 # minutes
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Configuration for a specific virtual patient (e.g., Patient 559)
|
|
2
|
+
# Parameters based on src/iints/core/patient/models.py
|
|
3
|
+
|
|
4
|
+
basal_insulin_rate: 0.9 # Slightly higher basal
|
|
5
|
+
insulin_sensitivity: 45.0 # Slightly less sensitive
|
|
6
|
+
carb_factor: 12.0 # Different carb factor
|
|
7
|
+
glucose_decay_rate: 0.04 # Slower glucose decay
|
|
8
|
+
initial_glucose: 130.0 # Slightly higher initial glucose
|
|
9
|
+
glucose_absorption_rate: 0.035 # Slightly faster carb absorption
|
|
10
|
+
insulin_action_duration: 330.0 # Longer insulin action
|
|
11
|
+
insulin_peak_time: 80.0 # Later insulin peak
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
from iints.api.base_algorithm import InsulinAlgorithm
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
IINTS-AF Legacy Emulation Module
|
|
5
|
+
Commercial insulin pump emulation for research and comparison.
|
|
6
|
+
|
|
7
|
+
This module provides emulators for major commercial insulin pumps,
|
|
8
|
+
allowing researchers to compare new algorithms against established
|
|
9
|
+
commercial systems and identify areas for improvement.
|
|
10
|
+
|
|
11
|
+
Part of the #WeAreNotWaiting movement for transparent diabetes tech.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from .legacy_base import (
|
|
15
|
+
LegacyEmulator,
|
|
16
|
+
PumpBehavior,
|
|
17
|
+
PIDParameters,
|
|
18
|
+
SafetyLimits,
|
|
19
|
+
SafetyLevel,
|
|
20
|
+
EmulatorDecision
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
from .medtronic_780g import Medtronic780GEmulator, Medtronic780GBehavior
|
|
24
|
+
|
|
25
|
+
from .tandem_controliq import TandemControlIQEmulator, TandemControlIQBehavior
|
|
26
|
+
|
|
27
|
+
from .omnipod_5 import Omnipod5Emulator, Omnipod5Behavior
|
|
28
|
+
|
|
29
|
+
__all__ = [
|
|
30
|
+
# Base classes
|
|
31
|
+
'LegacyEmulator',
|
|
32
|
+
'PumpBehavior',
|
|
33
|
+
'PIDParameters',
|
|
34
|
+
'SafetyLimits',
|
|
35
|
+
'SafetyLevel',
|
|
36
|
+
'EmulatorDecision',
|
|
37
|
+
|
|
38
|
+
# Emulators
|
|
39
|
+
'Medtronic780GEmulator',
|
|
40
|
+
'Medtronic780GBehavior',
|
|
41
|
+
'TandemControlIQEmulator',
|
|
42
|
+
'TandemControlIQBehavior',
|
|
43
|
+
'Omnipod5Emulator',
|
|
44
|
+
'Omnipod5Behavior',
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
# Quick access to all emulators
|
|
48
|
+
EMULATORS = {
|
|
49
|
+
'medtronic_780g': Medtronic780GEmulator,
|
|
50
|
+
'tandem_controliq': TandemControlIQEmulator,
|
|
51
|
+
'omnipod_5': Omnipod5Emulator,
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def get_emulator(pump_type: str) -> InsulinAlgorithm:
|
|
56
|
+
"""
|
|
57
|
+
Get an emulator instance by pump type.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
pump_type: One of 'medtronic_780g', 'tandem_controliq', 'omnipod_5'
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
LegacyEmulator instance
|
|
64
|
+
|
|
65
|
+
Raises:
|
|
66
|
+
ValueError: If pump_type is not recognized
|
|
67
|
+
"""
|
|
68
|
+
if pump_type.lower() in EMULATORS:
|
|
69
|
+
return EMULATORS[pump_type.lower()]() # type: ignore
|
|
70
|
+
else:
|
|
71
|
+
raise ValueError(
|
|
72
|
+
f"Unknown pump type: {pump_type}. "
|
|
73
|
+
f"Available: {list(EMULATORS.keys())}"
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def list_available_emulators() -> list:
|
|
78
|
+
"""List all available pump emulators"""
|
|
79
|
+
return list(EMULATORS.keys())
|
|
80
|
+
|
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Legacy Emulator Base Class - IINTS-AF
|
|
4
|
+
Base class for commercial insulin pump emulation.
|
|
5
|
+
|
|
6
|
+
This module provides the foundation for emulating commercial pump behaviors
|
|
7
|
+
based on published clinical studies and regulatory documentation.
|
|
8
|
+
|
|
9
|
+
Part of the #WeAreNotWaiting movement for transparent diabetes tech.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from abc import ABC, abstractmethod
|
|
13
|
+
from typing import Dict, List, Optional, Any
|
|
14
|
+
from dataclasses import dataclass, field
|
|
15
|
+
from dataclasses import dataclass
|
|
16
|
+
from enum import Enum
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class SafetyLevel(Enum):
|
|
20
|
+
"""Safety level classification for pump behaviors"""
|
|
21
|
+
CONSERVATIVE = "conservative"
|
|
22
|
+
MODERATE = "moderate"
|
|
23
|
+
AGGRESSIVE = "aggressive"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class SafetyLimits:
|
|
28
|
+
"""Pump-specific safety constraints"""
|
|
29
|
+
low_suspend_threshold: float = 70.0 # mg/dL
|
|
30
|
+
high_suspend_threshold: float = 250.0 # mg/dL
|
|
31
|
+
max_bolus: float = 10.0 # units
|
|
32
|
+
max_basal_rate: float = 5.0 # units/hour
|
|
33
|
+
max_daily_total: float = 100.0 # units
|
|
34
|
+
auto_off_duration: float = 60.0 # minutes
|
|
35
|
+
low_suspend_duration: float = 5.0 # minutes below threshold before suspend
|
|
36
|
+
target_glucose: float = 120.0 # mg/dL
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass
|
|
40
|
+
class PIDParameters:
|
|
41
|
+
"""PID controller parameters"""
|
|
42
|
+
kp: float # Proportional gain
|
|
43
|
+
ki: float # Integral gain
|
|
44
|
+
kd: float # Derivative gain
|
|
45
|
+
target_glucose: float # Target glucose setpoint
|
|
46
|
+
integral_limit: float = 10.0 # Integral windup protection
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dataclass
|
|
50
|
+
class PumpBehavior:
|
|
51
|
+
"""Complete pump behavior profile"""
|
|
52
|
+
pump_name: str
|
|
53
|
+
manufacturer: str
|
|
54
|
+
safety_limits: SafetyLimits
|
|
55
|
+
pid_parameters: Optional[PIDParameters] = None
|
|
56
|
+
correction_factor: float = 50.0 # mg/dL per unit
|
|
57
|
+
carb_ratio: float = 10.0 # grams per unit
|
|
58
|
+
insulin_sensitivity_factor: float = 50.0 # mg/dL per unit
|
|
59
|
+
active_insulin_duration: float = 4.0 # hours
|
|
60
|
+
safety_level: SafetyLevel = SafetyLevel.MODERATE
|
|
61
|
+
|
|
62
|
+
def to_dict(self) -> Dict:
|
|
63
|
+
return {
|
|
64
|
+
'pump_name': self.pump_name,
|
|
65
|
+
'manufacturer': self.manufacturer,
|
|
66
|
+
'safety_limits': {
|
|
67
|
+
'low_suspend_threshold': self.safety_limits.low_suspend_threshold,
|
|
68
|
+
'high_suspend_threshold': self.safety_limits.high_suspend_threshold,
|
|
69
|
+
'max_bolus': self.safety_limits.max_bolus,
|
|
70
|
+
'max_basal_rate': self.safety_limits.max_basal_rate,
|
|
71
|
+
'max_daily_total': self.safety_limits.max_daily_total,
|
|
72
|
+
'auto_off_duration': self.safety_limits.auto_off_duration,
|
|
73
|
+
'target_glucose': self.safety_limits.target_glucose
|
|
74
|
+
},
|
|
75
|
+
'correction_factor': self.correction_factor,
|
|
76
|
+
'carb_ratio': self.carb_ratio,
|
|
77
|
+
'insulin_sensitivity_factor': self.insulin_sensitivity_factor,
|
|
78
|
+
'safety_level': self.safety_level.value
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@dataclass
|
|
83
|
+
class EmulatorDecision:
|
|
84
|
+
"""Decision output from a pump emulator"""
|
|
85
|
+
insulin_delivered: float
|
|
86
|
+
action: str # 'deliver', 'suspend', 'reduce_basal', 'no_action'
|
|
87
|
+
reasoning: List[str]
|
|
88
|
+
safety_overrides: List[str]
|
|
89
|
+
predicted_glucose: Optional[float] = None
|
|
90
|
+
confidence: float = 0.9
|
|
91
|
+
|
|
92
|
+
def to_dict(self) -> Dict:
|
|
93
|
+
return {
|
|
94
|
+
'insulin_delivered': self.insulin_delivered,
|
|
95
|
+
'action': self.action,
|
|
96
|
+
'reasoning': self.reasoning,
|
|
97
|
+
'safety_overrides': self.safety_overrides,
|
|
98
|
+
'predicted_glucose': self.predicted_glucose,
|
|
99
|
+
'confidence': self.confidence
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
from iints.api.base_algorithm import InsulinAlgorithm, AlgorithmInput
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class LegacyEmulator(InsulinAlgorithm):
|
|
107
|
+
"""
|
|
108
|
+
Abstract base class for commercial insulin pump emulation.
|
|
109
|
+
|
|
110
|
+
This class provides the interface for implementing emulators that
|
|
111
|
+
replicate the behavior of commercial insulin pumps based on:
|
|
112
|
+
- Published clinical studies
|
|
113
|
+
- Regulatory documentation
|
|
114
|
+
- User manuals and technical specifications
|
|
115
|
+
|
|
116
|
+
Implementations should cite their sources in the get_sources() method.
|
|
117
|
+
"""
|
|
118
|
+
|
|
119
|
+
def __init__(self):
|
|
120
|
+
"""Initialize the emulator with default settings"""
|
|
121
|
+
self.behavior = self._get_default_behavior()
|
|
122
|
+
self.state = self._create_initial_state()
|
|
123
|
+
self._decision_history = []
|
|
124
|
+
|
|
125
|
+
@abstractmethod
|
|
126
|
+
def _get_default_behavior(self) -> PumpBehavior:
|
|
127
|
+
"""
|
|
128
|
+
Get the default behavior profile for this pump.
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
PumpBehavior: Complete behavior profile
|
|
132
|
+
"""
|
|
133
|
+
pass
|
|
134
|
+
|
|
135
|
+
@abstractmethod
|
|
136
|
+
def get_sources(self) -> List[Dict[str, str]]:
|
|
137
|
+
"""
|
|
138
|
+
Get citation information for the emulation logic.
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
List of dictionaries with 'title', 'url', and 'type' keys
|
|
142
|
+
"""
|
|
143
|
+
pass
|
|
144
|
+
|
|
145
|
+
def _create_initial_state(self) -> Dict[str, Any]:
|
|
146
|
+
"""Create initial emulator state"""
|
|
147
|
+
return {
|
|
148
|
+
'cumulative_insulin': 0.0,
|
|
149
|
+
'suspended_until': 0, # timestamp
|
|
150
|
+
'mode': 'auto', # 'auto', 'manual', 'safe_mode'
|
|
151
|
+
'last_decision_time': 0,
|
|
152
|
+
'integral_term': 0.0,
|
|
153
|
+
'previous_glucose': None
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
def reset(self):
|
|
157
|
+
"""Reset emulator state for new simulation"""
|
|
158
|
+
self.state = self._create_initial_state()
|
|
159
|
+
self._decision_history = []
|
|
160
|
+
print(f" {self.behavior.pump_name} emulator reset")
|
|
161
|
+
|
|
162
|
+
def set_safety_mode(self, level: SafetyLevel):
|
|
163
|
+
"""Adjust safety level for testing"""
|
|
164
|
+
self.behavior.safety_level = level
|
|
165
|
+
print(f" {self.behavior.pump_name} safety level set to {level.value}")
|
|
166
|
+
|
|
167
|
+
def get_behavior_profile(self) -> PumpBehavior:
|
|
168
|
+
"""Get the complete behavior profile"""
|
|
169
|
+
return self.behavior
|
|
170
|
+
|
|
171
|
+
def _check_safety_constraints(self,
|
|
172
|
+
glucose: float,
|
|
173
|
+
velocity: float,
|
|
174
|
+
insulin_on_board: float,
|
|
175
|
+
carbs: float,
|
|
176
|
+
current_time: float) -> tuple:
|
|
177
|
+
"""
|
|
178
|
+
Check safety constraints and return overrides.
|
|
179
|
+
|
|
180
|
+
Returns:
|
|
181
|
+
tuple: (should_suspend, reasoning_list, insulin_adjustment)
|
|
182
|
+
"""
|
|
183
|
+
reasoning = []
|
|
184
|
+
should_suspend = False
|
|
185
|
+
insulin_adjustment = 1.0
|
|
186
|
+
safety_limits = self.behavior.safety_limits
|
|
187
|
+
|
|
188
|
+
# Low glucose suspend check
|
|
189
|
+
if glucose < safety_limits.low_suspend_threshold:
|
|
190
|
+
should_suspend = True
|
|
191
|
+
reasoning.append(
|
|
192
|
+
f"Low glucose suspend: glucose {glucose:.0f} < {safety_limits.low_suspend_threshold:.0f} mg/dL"
|
|
193
|
+
)
|
|
194
|
+
insulin_adjustment = 0.0
|
|
195
|
+
|
|
196
|
+
# Rapid fall check
|
|
197
|
+
elif velocity < -2.0 and glucose < 100:
|
|
198
|
+
should_suspend = True
|
|
199
|
+
reasoning.append(
|
|
200
|
+
f"Rapid fall detected: {velocity:.1f} mg/dL/min with glucose {glucose:.0f}"
|
|
201
|
+
)
|
|
202
|
+
insulin_adjustment = 0.0
|
|
203
|
+
|
|
204
|
+
# High glucose check (may increase insulin)
|
|
205
|
+
elif glucose > safety_limits.high_suspend_threshold:
|
|
206
|
+
reasoning.append(
|
|
207
|
+
f"High glucose detected: {glucose:.0f} > {safety_limits.high_suspend_threshold:.0f} mg/dL"
|
|
208
|
+
)
|
|
209
|
+
# Some pumps increase delivery for high glucose
|
|
210
|
+
|
|
211
|
+
# Insulin stacking check
|
|
212
|
+
if insulin_on_board > 3.0:
|
|
213
|
+
insulin_adjustment *= 0.5
|
|
214
|
+
reasoning.append(
|
|
215
|
+
f"Insulin stacking reduction: {insulin_on_board:.1f} U IOB"
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
return should_suspend, reasoning, insulin_adjustment
|
|
219
|
+
|
|
220
|
+
def _calculate_pid_correction(self,
|
|
221
|
+
glucose: float,
|
|
222
|
+
velocity: float,
|
|
223
|
+
target: float) -> float:
|
|
224
|
+
"""
|
|
225
|
+
Calculate insulin correction using PID.
|
|
226
|
+
|
|
227
|
+
Args:
|
|
228
|
+
glucose: Current glucose
|
|
229
|
+
velocity: Rate of change
|
|
230
|
+
target: Target glucose
|
|
231
|
+
|
|
232
|
+
Returns:
|
|
233
|
+
Insulin correction in units
|
|
234
|
+
"""
|
|
235
|
+
pid = self.behavior.pid_parameters
|
|
236
|
+
if pid is None:
|
|
237
|
+
# Fallback to proportional only
|
|
238
|
+
error = glucose - target
|
|
239
|
+
return max(0, error / self.behavior.correction_factor)
|
|
240
|
+
|
|
241
|
+
# PID calculation
|
|
242
|
+
error = glucose - target
|
|
243
|
+
self.state['integral_term'] += error * 0.1 # Simplified integral
|
|
244
|
+
self.state['integral_term'] = max(-pid.integral_limit,
|
|
245
|
+
min(pid.integral_limit,
|
|
246
|
+
self.state['integral_term']))
|
|
247
|
+
derivative = velocity
|
|
248
|
+
|
|
249
|
+
# PID formula
|
|
250
|
+
correction = (
|
|
251
|
+
pid.kp * error +
|
|
252
|
+
pid.ki * self.state['integral_term'] +
|
|
253
|
+
pid.kd * derivative
|
|
254
|
+
) / self.behavior.correction_factor
|
|
255
|
+
|
|
256
|
+
return max(0, correction)
|
|
257
|
+
|
|
258
|
+
@abstractmethod
|
|
259
|
+
def emulate_decision(self,
|
|
260
|
+
glucose: float,
|
|
261
|
+
velocity: float,
|
|
262
|
+
insulin_on_board: float,
|
|
263
|
+
carbs: float,
|
|
264
|
+
current_time: float = 0) -> EmulatorDecision:
|
|
265
|
+
"""
|
|
266
|
+
Emulate a pump decision for the current conditions.
|
|
267
|
+
|
|
268
|
+
This is the main method to implement for each pump type.
|
|
269
|
+
|
|
270
|
+
Args:
|
|
271
|
+
glucose: Current glucose reading (mg/dL)
|
|
272
|
+
velocity: Rate of glucose change (mg/dL/min)
|
|
273
|
+
insulin_on_board: Current insulin on board (units)
|
|
274
|
+
carbs: Carbs consumed in last period (grams)
|
|
275
|
+
current_time: Current simulation time (minutes)
|
|
276
|
+
|
|
277
|
+
Returns:
|
|
278
|
+
EmulatorDecision: The pump's decision
|
|
279
|
+
"""
|
|
280
|
+
pass
|
|
281
|
+
|
|
282
|
+
def predict_insulin(self, algo_input: AlgorithmInput) -> Dict[str, Any]:
|
|
283
|
+
"""
|
|
284
|
+
Adapts the InsulinAlgorithm interface to the LegacyEmulator's emulate_decision.
|
|
285
|
+
"""
|
|
286
|
+
self.why_log = [] # Clear why_log for this prediction cycle
|
|
287
|
+
|
|
288
|
+
# Velocity calculation, considering previous glucose state
|
|
289
|
+
previous_glucose = self.state.get('previous_glucose', algo_input.current_glucose)
|
|
290
|
+
|
|
291
|
+
# Avoid division by zero if time_step is 0 or if called at current_time=0 with no prev glucose
|
|
292
|
+
if algo_input.time_step > 0:
|
|
293
|
+
velocity_calc = (algo_input.current_glucose - previous_glucose) / algo_input.time_step
|
|
294
|
+
else:
|
|
295
|
+
velocity_calc = 0.0 # Or handle as error
|
|
296
|
+
|
|
297
|
+
# If the first step, velocity might be zero, or need external init
|
|
298
|
+
# Use initial velocity as 0 if it's the very first step of the simulation
|
|
299
|
+
if self.state.get('last_decision_time') is None:
|
|
300
|
+
velocity_for_emulator = 0.0
|
|
301
|
+
else:
|
|
302
|
+
velocity_for_emulator = velocity_calc
|
|
303
|
+
|
|
304
|
+
emulator_decision = self.emulate_decision(
|
|
305
|
+
glucose=algo_input.current_glucose,
|
|
306
|
+
velocity=velocity_for_emulator,
|
|
307
|
+
insulin_on_board=algo_input.insulin_on_board,
|
|
308
|
+
carbs=algo_input.carb_intake,
|
|
309
|
+
current_time=algo_input.current_time # Use the correct current_time
|
|
310
|
+
)
|
|
311
|
+
self.state['previous_glucose'] = algo_input.current_glucose # Update state
|
|
312
|
+
self.state['last_decision_time'] = algo_input.current_time # Update last decision time
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
# Transfer emulator_decision.reasoning to why_log
|
|
316
|
+
for reason_str in emulator_decision.reasoning:
|
|
317
|
+
self._log_reason(reason_str, "emulator_logic")
|
|
318
|
+
for override_str in emulator_decision.safety_overrides:
|
|
319
|
+
self._log_reason(override_str, "emulator_safety", clinical_impact="Safety Override")
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
# Convert EmulatorDecision to the format expected by Simulator
|
|
323
|
+
return {
|
|
324
|
+
"total_insulin_delivered": emulator_decision.insulin_delivered,
|
|
325
|
+
"basal_insulin": emulator_decision.insulin_delivered, # Simplified, assume all is basal/correction
|
|
326
|
+
"bolus_insulin": 0.0,
|
|
327
|
+
"correction_bolus": emulator_decision.insulin_delivered, # Simplified
|
|
328
|
+
"uncertainty": 1.0 - emulator_decision.confidence, # Higher uncertainty for lower confidence
|
|
329
|
+
"fallback_triggered": True if emulator_decision.safety_overrides else False, # Re-using this flag
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
def get_decision_history(self) -> List[EmulatorDecision]:
|
|
333
|
+
"""Get history of all decisions"""
|
|
334
|
+
return self._decision_history
|
|
335
|
+
|
|
336
|
+
def export_behavior_report(self) -> Dict:
|
|
337
|
+
"""Export complete behavior profile for analysis"""
|
|
338
|
+
return {
|
|
339
|
+
'pump_name': self.behavior.pump_name,
|
|
340
|
+
'manufacturer': self.behavior.manufacturer,
|
|
341
|
+
'behavior': self.behavior.to_dict(),
|
|
342
|
+
'sources': self.get_sources(),
|
|
343
|
+
'decision_count': len(self._decision_history)
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
def compare_with_new_ai(self, new_ai_decisions: List[Dict]) -> Dict:
|
|
347
|
+
"""
|
|
348
|
+
Compare this pump's behavior with a new AI algorithm.
|
|
349
|
+
|
|
350
|
+
Args:
|
|
351
|
+
new_ai_decisions: List of decisions from new algorithm
|
|
352
|
+
|
|
353
|
+
Returns:
|
|
354
|
+
Comparison analysis
|
|
355
|
+
"""
|
|
356
|
+
if not self._decision_history:
|
|
357
|
+
return {'error': 'No decisions to compare'}
|
|
358
|
+
|
|
359
|
+
pump_insulin = [d.insulin_delivered for d in self._decision_history]
|
|
360
|
+
ai_insulin = [d.get('total_insulin_delivered', 0) for d in new_ai_decisions]
|
|
361
|
+
|
|
362
|
+
# Basic comparison
|
|
363
|
+
comparison = {
|
|
364
|
+
'pump_name': self.behavior.pump_name,
|
|
365
|
+
'pump_total_insulin': sum(pump_insulin),
|
|
366
|
+
'ai_total_insulin': sum(ai_insulin) if ai_insulin else 0,
|
|
367
|
+
'insulin_difference_percent': (
|
|
368
|
+
(sum(pump_insulin) - sum(ai_insulin)) / sum(pump_insulin) * 100
|
|
369
|
+
) if sum(pump_insulin) > 0 else 0,
|
|
370
|
+
'pump_suspend_count': len([
|
|
371
|
+
d for d in self._decision_history if d.action == 'suspend'
|
|
372
|
+
]),
|
|
373
|
+
'ai_suspend_count': len([
|
|
374
|
+
d for d in new_ai_decisions
|
|
375
|
+
if d.get('safety_override', False)
|
|
376
|
+
])
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
return comparison
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def demo_legacy_emulator():
|
|
383
|
+
"""Demonstrate legacy emulator functionality"""
|
|
384
|
+
print("=" * 70)
|
|
385
|
+
print("LEGACY EMULATOR BASE CLASS DEMONSTRATION")
|
|
386
|
+
print("=" * 70)
|
|
387
|
+
|
|
388
|
+
# This shows the interface - actual emulators inherit from this
|
|
389
|
+
print("\n Legacy Emulator Features:")
|
|
390
|
+
print(" - Abstract base class for pump emulation")
|
|
391
|
+
print(" - PID parameter support")
|
|
392
|
+
print(" - Safety constraint checking")
|
|
393
|
+
print(" - Decision history tracking")
|
|
394
|
+
print(" - Behavior profile export")
|
|
395
|
+
print(" - Comparison with new AI algorithms")
|
|
396
|
+
|
|
397
|
+
print("\n Implementations Available:")
|
|
398
|
+
print(" - Medtronic 780G Emulator")
|
|
399
|
+
print(" - Tandem Control-IQ Emulator")
|
|
400
|
+
print(" - Omnipod 5 Emulator")
|
|
401
|
+
|
|
402
|
+
print("\n Sources:")
|
|
403
|
+
print(" - Regulatory documentation")
|
|
404
|
+
print(" - Clinical studies")
|
|
405
|
+
print(" - User manuals")
|
|
406
|
+
|
|
407
|
+
print("\n" + "=" * 70)
|
|
408
|
+
print("LEGACY EMULATOR BASE CLASS DEMONSTRATION COMPLETE")
|
|
409
|
+
print("=" * 70)
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
if __name__ == "__main__":
|
|
413
|
+
demo_legacy_emulator()
|
|
414
|
+
|