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.
Files changed (118) hide show
  1. iints/__init__.py +183 -0
  2. iints/analysis/__init__.py +12 -0
  3. iints/analysis/algorithm_xray.py +387 -0
  4. iints/analysis/baseline.py +92 -0
  5. iints/analysis/clinical_benchmark.py +198 -0
  6. iints/analysis/clinical_metrics.py +551 -0
  7. iints/analysis/clinical_tir_analyzer.py +136 -0
  8. iints/analysis/diabetes_metrics.py +43 -0
  9. iints/analysis/edge_efficiency.py +33 -0
  10. iints/analysis/edge_performance_monitor.py +315 -0
  11. iints/analysis/explainability.py +94 -0
  12. iints/analysis/explainable_ai.py +232 -0
  13. iints/analysis/hardware_benchmark.py +221 -0
  14. iints/analysis/metrics.py +117 -0
  15. iints/analysis/population_report.py +188 -0
  16. iints/analysis/reporting.py +345 -0
  17. iints/analysis/safety_index.py +311 -0
  18. iints/analysis/sensor_filtering.py +54 -0
  19. iints/analysis/validator.py +273 -0
  20. iints/api/__init__.py +0 -0
  21. iints/api/base_algorithm.py +307 -0
  22. iints/api/registry.py +103 -0
  23. iints/api/template_algorithm.py +195 -0
  24. iints/assets/iints_logo.png +0 -0
  25. iints/cli/__init__.py +0 -0
  26. iints/cli/cli.py +2598 -0
  27. iints/core/__init__.py +1 -0
  28. iints/core/algorithms/__init__.py +0 -0
  29. iints/core/algorithms/battle_runner.py +138 -0
  30. iints/core/algorithms/correction_bolus.py +95 -0
  31. iints/core/algorithms/discovery.py +92 -0
  32. iints/core/algorithms/fixed_basal_bolus.py +58 -0
  33. iints/core/algorithms/hybrid_algorithm.py +92 -0
  34. iints/core/algorithms/lstm_algorithm.py +138 -0
  35. iints/core/algorithms/mock_algorithms.py +162 -0
  36. iints/core/algorithms/pid_controller.py +88 -0
  37. iints/core/algorithms/standard_pump_algo.py +64 -0
  38. iints/core/device.py +0 -0
  39. iints/core/device_manager.py +64 -0
  40. iints/core/devices/__init__.py +3 -0
  41. iints/core/devices/models.py +160 -0
  42. iints/core/patient/__init__.py +9 -0
  43. iints/core/patient/bergman_model.py +341 -0
  44. iints/core/patient/models.py +285 -0
  45. iints/core/patient/patient_factory.py +117 -0
  46. iints/core/patient/profile.py +41 -0
  47. iints/core/safety/__init__.py +12 -0
  48. iints/core/safety/config.py +37 -0
  49. iints/core/safety/input_validator.py +95 -0
  50. iints/core/safety/supervisor.py +39 -0
  51. iints/core/simulation/__init__.py +0 -0
  52. iints/core/simulation/scenario_parser.py +61 -0
  53. iints/core/simulator.py +874 -0
  54. iints/core/supervisor.py +367 -0
  55. iints/data/__init__.py +53 -0
  56. iints/data/adapter.py +142 -0
  57. iints/data/column_mapper.py +398 -0
  58. iints/data/datasets.json +132 -0
  59. iints/data/demo/__init__.py +1 -0
  60. iints/data/demo/demo_cgm.csv +289 -0
  61. iints/data/importer.py +275 -0
  62. iints/data/ingestor.py +162 -0
  63. iints/data/nightscout.py +128 -0
  64. iints/data/quality_checker.py +550 -0
  65. iints/data/registry.py +166 -0
  66. iints/data/tidepool.py +38 -0
  67. iints/data/universal_parser.py +813 -0
  68. iints/data/virtual_patients/clinic_safe_baseline.yaml +9 -0
  69. iints/data/virtual_patients/clinic_safe_hyper_challenge.yaml +9 -0
  70. iints/data/virtual_patients/clinic_safe_hypo_prone.yaml +9 -0
  71. iints/data/virtual_patients/clinic_safe_midnight.yaml +9 -0
  72. iints/data/virtual_patients/clinic_safe_pizza.yaml +9 -0
  73. iints/data/virtual_patients/clinic_safe_stress_meal.yaml +9 -0
  74. iints/data/virtual_patients/default_patient.yaml +11 -0
  75. iints/data/virtual_patients/patient_559_config.yaml +11 -0
  76. iints/emulation/__init__.py +80 -0
  77. iints/emulation/legacy_base.py +414 -0
  78. iints/emulation/medtronic_780g.py +337 -0
  79. iints/emulation/omnipod_5.py +367 -0
  80. iints/emulation/tandem_controliq.py +393 -0
  81. iints/highlevel.py +451 -0
  82. iints/learning/__init__.py +3 -0
  83. iints/learning/autonomous_optimizer.py +194 -0
  84. iints/learning/learning_system.py +122 -0
  85. iints/metrics.py +34 -0
  86. iints/population/__init__.py +11 -0
  87. iints/population/generator.py +131 -0
  88. iints/population/runner.py +327 -0
  89. iints/presets/__init__.py +28 -0
  90. iints/presets/presets.json +114 -0
  91. iints/research/__init__.py +30 -0
  92. iints/research/config.py +68 -0
  93. iints/research/dataset.py +319 -0
  94. iints/research/losses.py +73 -0
  95. iints/research/predictor.py +329 -0
  96. iints/scenarios/__init__.py +3 -0
  97. iints/scenarios/generator.py +92 -0
  98. iints/templates/__init__.py +0 -0
  99. iints/templates/default_algorithm.py +91 -0
  100. iints/templates/scenarios/__init__.py +0 -0
  101. iints/templates/scenarios/chaos_insulin_stacking.json +29 -0
  102. iints/templates/scenarios/chaos_runaway_ai.json +25 -0
  103. iints/templates/scenarios/example_scenario.json +35 -0
  104. iints/templates/scenarios/exercise_stress.json +30 -0
  105. iints/utils/__init__.py +3 -0
  106. iints/utils/plotting.py +50 -0
  107. iints/utils/run_io.py +152 -0
  108. iints/validation/__init__.py +133 -0
  109. iints/validation/schemas.py +94 -0
  110. iints/visualization/__init__.py +34 -0
  111. iints/visualization/cockpit.py +691 -0
  112. iints/visualization/uncertainty_cloud.py +612 -0
  113. iints_sdk_python35-0.0.18.dist-info/METADATA +225 -0
  114. iints_sdk_python35-0.0.18.dist-info/RECORD +118 -0
  115. iints_sdk_python35-0.0.18.dist-info/WHEEL +5 -0
  116. iints_sdk_python35-0.0.18.dist-info/entry_points.txt +10 -0
  117. iints_sdk_python35-0.0.18.dist-info/licenses/LICENSE +28 -0
  118. iints_sdk_python35-0.0.18.dist-info/top_level.txt +1 -0
@@ -0,0 +1,9 @@
1
+ basal_insulin_rate: 0.5
2
+ insulin_sensitivity: 50.0
3
+ carb_factor: 10.0
4
+ glucose_decay_rate: 0.03
5
+ initial_glucose: 140.0
6
+ glucose_absorption_rate: 0.03
7
+ insulin_action_duration: 300.0
8
+ insulin_peak_time: 75.0
9
+ meal_mismatch_epsilon: 1.0
@@ -0,0 +1,9 @@
1
+ basal_insulin_rate: 0.55
2
+ insulin_sensitivity: 45.0
3
+ carb_factor: 9.0
4
+ glucose_decay_rate: 0.03
5
+ initial_glucose: 150.0
6
+ glucose_absorption_rate: 0.03
7
+ insulin_action_duration: 300.0
8
+ insulin_peak_time: 75.0
9
+ meal_mismatch_epsilon: 1.0
@@ -0,0 +1,9 @@
1
+ basal_insulin_rate: 0.35
2
+ insulin_sensitivity: 55.0
3
+ carb_factor: 12.0
4
+ glucose_decay_rate: 0.03
5
+ initial_glucose: 130.0
6
+ glucose_absorption_rate: 0.03
7
+ insulin_action_duration: 300.0
8
+ insulin_peak_time: 75.0
9
+ meal_mismatch_epsilon: 1.0
@@ -0,0 +1,9 @@
1
+ basal_insulin_rate: 0.45
2
+ insulin_sensitivity: 65.0
3
+ carb_factor: 11.0
4
+ glucose_decay_rate: 0.03
5
+ initial_glucose: 125.0
6
+ glucose_absorption_rate: 0.03
7
+ insulin_action_duration: 300.0
8
+ insulin_peak_time: 75.0
9
+ meal_mismatch_epsilon: 1.0
@@ -0,0 +1,9 @@
1
+ basal_insulin_rate: 0.5
2
+ insulin_sensitivity: 50.0
3
+ carb_factor: 10.0
4
+ glucose_decay_rate: 0.03
5
+ initial_glucose: 135.0
6
+ glucose_absorption_rate: 0.03
7
+ insulin_action_duration: 300.0
8
+ insulin_peak_time: 75.0
9
+ meal_mismatch_epsilon: 1.0
@@ -0,0 +1,9 @@
1
+ basal_insulin_rate: 0.4
2
+ insulin_sensitivity: 55.0
3
+ carb_factor: 11.0
4
+ glucose_decay_rate: 0.03
5
+ initial_glucose: 120.0
6
+ glucose_absorption_rate: 0.03
7
+ insulin_action_duration: 300.0
8
+ insulin_peak_time: 75.0
9
+ meal_mismatch_epsilon: 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
+