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,307 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import Dict, Any, List, Optional
3
+ from dataclasses import dataclass, field
4
+ from datetime import datetime
5
+
6
+ @dataclass
7
+ class AlgorithmInput:
8
+ """Dataclass for inputs to the insulin prediction algorithm."""
9
+ current_glucose: float
10
+ time_step: float
11
+ insulin_on_board: float = 0.0
12
+ carb_intake: float = 0.0
13
+ patient_state: Dict[str, Any] = field(default_factory=dict)
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
22
+
23
+
24
+ @dataclass
25
+ class AlgorithmMetadata:
26
+ """Metadata for algorithm registration and identification"""
27
+ name: str
28
+ version: str = "1.0.0"
29
+ author: str = "IINTS-AF Team"
30
+ paper_reference: Optional[str] = None
31
+ description: str = ""
32
+ algorithm_type: str = "rule_based" # 'rule_based', 'ml', 'hybrid'
33
+ requires_training: bool = False
34
+ supported_scenarios: List[str] = field(default_factory=lambda: [
35
+ 'standard_meal', 'unannounced_meal', 'exercise',
36
+ 'stress', 'hypoglycemia', 'hyperglycemia'
37
+ ])
38
+
39
+ def to_dict(self) -> Dict:
40
+ return {
41
+ 'name': self.name,
42
+ 'version': self.version,
43
+ 'author': self.author,
44
+ 'paper_reference': self.paper_reference,
45
+ 'description': self.description,
46
+ 'algorithm_type': self.algorithm_type,
47
+ 'requires_training': self.requires_training,
48
+ 'supported_scenarios': self.supported_scenarios
49
+ }
50
+
51
+
52
+ @dataclass
53
+ class AlgorithmResult:
54
+ """Result of an insulin prediction with uncertainty"""
55
+ total_insulin_delivered: float
56
+ bolus_insulin: float = 0.0
57
+ basal_insulin: float = 0.0
58
+ correction_bolus: float = 0.0
59
+ meal_bolus: float = 0.0
60
+
61
+ # Uncertainty quantification
62
+ uncertainty: float = 0.0 # 0.0 = certain, 1.0 = very uncertain
63
+ confidence_interval: tuple = (0.0, 0.0) # (lower, upper)
64
+
65
+ # Clinical reasoning
66
+ primary_reason: str = ""
67
+ secondary_reasons: List[str] = field(default_factory=list)
68
+ safety_constraints: List[str] = field(default_factory=list)
69
+
70
+ # Metadata
71
+ algorithm_name: str = ""
72
+ timestamp: datetime = field(default_factory=datetime.now)
73
+
74
+ def to_dict(self) -> Dict:
75
+ return {
76
+ 'total_insulin_delivered': self.total_insulin_delivered,
77
+ 'bolus_insulin': self.bolus_insulin,
78
+ 'basal_insulin': self.basal_insulin,
79
+ 'correction_bolus': self.correction_bolus,
80
+ 'meal_bolus': self.meal_bolus,
81
+ 'uncertainty': self.uncertainty,
82
+ 'confidence_interval': self.confidence_interval,
83
+ 'primary_reason': self.primary_reason,
84
+ 'secondary_reasons': self.secondary_reasons,
85
+ 'safety_constraints': self.safety_constraints,
86
+ 'algorithm_name': self.algorithm_name,
87
+ 'timestamp': self.timestamp.isoformat()
88
+ }
89
+
90
+
91
+ @dataclass
92
+ class WhyLogEntry:
93
+ """Single entry in the Why Log explaining a decision reason"""
94
+ reason: str
95
+ category: str # 'glucose_level', 'velocity', 'insulin_on_board', 'safety', 'context'
96
+ value: Any = None
97
+ clinical_impact: str = ""
98
+
99
+ def to_dict(self) -> Dict:
100
+ return {
101
+ 'reason': self.reason,
102
+ 'category': self.category,
103
+ 'value': self.value,
104
+ 'clinical_impact': self.clinical_impact
105
+ }
106
+
107
+ class InsulinAlgorithm(ABC):
108
+ """
109
+ Abstract base class for insulin delivery algorithms.
110
+
111
+ All specific insulin algorithms used in the simulation framework should
112
+ inherit from this class and implement its abstract methods.
113
+
114
+ This class supports the Plug-and-Play architecture, allowing any algorithm
115
+ to be registered and compared in Battle Mode.
116
+ """
117
+
118
+ def __init__(self, settings: Optional[Dict[str, Any]] = None):
119
+ """
120
+ Initializes the algorithm with its specific settings.
121
+
122
+ Args:
123
+ settings (Dict[str, Any]): A dictionary of algorithm-specific parameters.
124
+ """
125
+ self.settings = settings if settings is not None else {}
126
+ self.state: Dict[str, Any] = {} # To store internal algorithm state across calls
127
+ self.why_log: List[WhyLogEntry] = [] # Decision reasoning log
128
+ self._metadata: Optional[AlgorithmMetadata] = None # Lazy-loaded metadata
129
+ self.isf = self.settings.get('isf', 50.0) # Default ISF
130
+ self.icr = self.settings.get('icr', 10.0) # Default ICR
131
+
132
+ def set_isf(self, isf: float):
133
+ """Set the Insulin Sensitivity Factor (mg/dL per unit)."""
134
+ if isf <= 0:
135
+ raise ValueError("ISF must be a positive value.")
136
+ self.isf = isf
137
+
138
+ def set_icr(self, icr: float):
139
+ """Set the Insulin-to-Carb Ratio (grams per unit)."""
140
+ if icr <= 0:
141
+ raise ValueError("ICR must be a positive value.")
142
+ self.icr = icr
143
+
144
+ def get_algorithm_metadata(self) -> AlgorithmMetadata:
145
+ """
146
+ Get algorithm metadata. Override in subclasses for custom metadata.
147
+
148
+ Returns:
149
+ AlgorithmMetadata: Information about the algorithm for registration
150
+ """
151
+ if self._metadata is None:
152
+ # Default metadata - override in subclasses
153
+ self._metadata = AlgorithmMetadata(
154
+ name=self.__class__.__name__,
155
+ version="1.0.0",
156
+ author="IINTS-AF Team",
157
+ description=f"Insulin algorithm: {self.__class__.__name__}",
158
+ algorithm_type="rule_based"
159
+ )
160
+ return self._metadata
161
+
162
+ def set_algorithm_metadata(self, metadata: AlgorithmMetadata):
163
+ """Set custom algorithm metadata"""
164
+ self._metadata = metadata
165
+
166
+ def calculate_uncertainty(self, data: AlgorithmInput) -> float:
167
+ """
168
+ Calculate uncertainty score for the current prediction.
169
+
170
+ Override in subclasses to implement custom uncertainty quantification.
171
+
172
+ Args:
173
+ data: Current algorithm input
174
+
175
+ Returns:
176
+ float: Uncertainty score between 0.0 (certain) and 1.0 (very uncertain)
177
+ """
178
+ # Default: low uncertainty for rule-based algorithms
179
+ return 0.1
180
+
181
+ def calculate_confidence_interval(self,
182
+ data: AlgorithmInput,
183
+ prediction: float,
184
+ uncertainty: float) -> tuple:
185
+ """
186
+ Calculate confidence interval for the prediction.
187
+
188
+ Args:
189
+ data: Current algorithm input
190
+ prediction: Predicted insulin dose
191
+ uncertainty: Uncertainty score
192
+
193
+ Returns:
194
+ tuple: (lower_bound, upper_bound) for the prediction
195
+ """
196
+ # Default: ±20% of prediction based on uncertainty
197
+ margin = prediction * 0.2 * (1 + uncertainty)
198
+ return (max(0, prediction - margin), prediction + margin)
199
+
200
+ def explain_prediction(self,
201
+ data: AlgorithmInput,
202
+ prediction: Dict[str, Any]) -> str:
203
+ """
204
+ Generate human-readable explanation for the prediction.
205
+
206
+ This is used for the Reasoning Log in the Clinical Control Center.
207
+
208
+ Args:
209
+ data: Algorithm input
210
+ prediction: Prediction dictionary
211
+
212
+ Returns:
213
+ str: Explanation like "2 units delivered because glucose rising >2 mg/dL/min"
214
+ """
215
+ reasons = []
216
+
217
+ # Glucose level reasoning
218
+ if data.current_glucose < 70:
219
+ reasons.append(f"glucose critically low at {data.current_glucose:.0f} mg/dL")
220
+ elif data.current_glucose < 100:
221
+ reasons.append(f"glucose approaching low at {data.current_glucose:.0f} mg/dL")
222
+ elif data.current_glucose > 180:
223
+ reasons.append(f"glucose elevated at {data.current_glucose:.0f} mg/dL")
224
+ else:
225
+ reasons.append(f"glucose in target range at {data.current_glucose:.0f} mg/dL")
226
+
227
+ # Insulin on board
228
+ if data.insulin_on_board > 2.0:
229
+ reasons.append(f"high insulin on board ({data.insulin_on_board:.1f} U)")
230
+ elif data.insulin_on_board > 0.5:
231
+ reasons.append(f"moderate insulin on board ({data.insulin_on_board:.1f} U)")
232
+
233
+ # Carbs
234
+ if data.carb_intake > 0:
235
+ reasons.append(f"meal detected ({data.carb_intake:.0f}g carbs)")
236
+
237
+ if prediction.get('total_insulin_delivered', 0) > 0:
238
+ return f"{prediction['total_insulin_delivered']:.2f} units delivered because " + ", ".join(reasons)
239
+ else:
240
+ return f"No insulin delivered: " + ", ".join(reasons)
241
+
242
+ @abstractmethod
243
+ def predict_insulin(self, data: AlgorithmInput) -> Dict[str, Any]:
244
+ """
245
+ Calculates the insulin dose based on current physiological data.
246
+ This is the primary method to be implemented by custom algorithms.
247
+
248
+ Args:
249
+ data (AlgorithmInput): A dataclass containing all relevant data for the decision.
250
+
251
+ Returns:
252
+ Dict[str, Any]: A dictionary containing the calculated insulin doses.
253
+ A key 'total_insulin_delivered' is expected by the simulator.
254
+ (e.g., {'total_insulin_delivered': 1.5, 'bolus_insulin': 1.0, 'basal_insulin': 0.5})
255
+ """
256
+ # Clear why_log at start of each calculation
257
+ self.why_log = []
258
+ raise NotImplementedError("Subclasses must implement predict_insulin method")
259
+
260
+ def _log_reason(self, reason: str, category: str, value: Any = None, clinical_impact: str = ""):
261
+ """Helper method to add reasoning to why_log"""
262
+ entry = WhyLogEntry(
263
+ reason=reason,
264
+ category=category,
265
+ value=value,
266
+ clinical_impact=clinical_impact
267
+ )
268
+ self.why_log.append(entry)
269
+
270
+ def get_why_log(self) -> List[WhyLogEntry]:
271
+ """Get the decision reasoning log for the last calculation"""
272
+ return self.why_log
273
+
274
+ def get_why_log_text(self) -> str:
275
+ """Get human-readable why log"""
276
+ if not self.why_log:
277
+ return "No decision reasoning available"
278
+
279
+ text = "WHY_LOG:\n"
280
+ for entry in self.why_log:
281
+ text += f"- {entry.reason}"
282
+ if entry.value is not None:
283
+ text += f" (value: {entry.value})"
284
+ if entry.clinical_impact:
285
+ text += f" → {entry.clinical_impact}"
286
+ text += "\n"
287
+ return text
288
+
289
+ def reset(self):
290
+ """
291
+ Resets the algorithm's internal state.
292
+ This should be called at the start of each new simulation run.
293
+ """
294
+ self.state = {}
295
+ self.why_log = []
296
+
297
+ def get_state(self) -> Dict[str, Any]:
298
+ """
299
+ Returns the current internal state of the algorithm.
300
+ """
301
+ return self.state
302
+
303
+ def set_state(self, state: Dict[str, Any]):
304
+ """
305
+ Sets the internal state of the algorithm.
306
+ """
307
+ self.state = state
iints/api/registry.py ADDED
@@ -0,0 +1,103 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import List, Optional, Mapping, Sequence, Iterable, Any, cast
5
+ import importlib
6
+
7
+ try:
8
+ from importlib import metadata as importlib_metadata
9
+ except Exception: # pragma: no cover
10
+ import importlib_metadata # type: ignore
11
+
12
+ from iints.api.base_algorithm import InsulinAlgorithm, AlgorithmMetadata
13
+ from iints.core.algorithms.discovery import discover_algorithms
14
+
15
+
16
+ @dataclass
17
+ class AlgorithmListing:
18
+ name: str
19
+ class_path: str
20
+ source: str
21
+ metadata: Optional[AlgorithmMetadata]
22
+ status: str = "available"
23
+ error: Optional[str] = None
24
+
25
+
26
+ def _load_entry_point(ep) -> AlgorithmListing:
27
+ try:
28
+ obj = ep.load()
29
+ if isinstance(obj, type) and issubclass(obj, InsulinAlgorithm):
30
+ instance = obj()
31
+ meta = instance.get_algorithm_metadata()
32
+ return AlgorithmListing(
33
+ name=meta.name,
34
+ class_path=f"{obj.__module__}.{obj.__name__}",
35
+ source=f"entry_point:{ep.name}",
36
+ metadata=meta,
37
+ )
38
+ return AlgorithmListing(
39
+ name=ep.name,
40
+ class_path=f"{ep.module}:{ep.attr}",
41
+ source=f"entry_point:{ep.name}",
42
+ metadata=None,
43
+ status="invalid",
44
+ error="Entry point does not resolve to an InsulinAlgorithm",
45
+ )
46
+ except Exception as exc:
47
+ return AlgorithmListing(
48
+ name=ep.name,
49
+ class_path=f"{ep.module}:{ep.attr}",
50
+ source=f"entry_point:{ep.name}",
51
+ metadata=None,
52
+ status="unavailable",
53
+ error=str(exc),
54
+ )
55
+
56
+
57
+ def list_algorithm_plugins() -> List[AlgorithmListing]:
58
+ listings: List[AlgorithmListing] = []
59
+
60
+ # Built-in discovery
61
+ try:
62
+ discovered = discover_algorithms()
63
+ for name, cls in discovered.items():
64
+ try:
65
+ instance = cls()
66
+ meta = instance.get_algorithm_metadata()
67
+ listings.append(
68
+ AlgorithmListing(
69
+ name=meta.name,
70
+ class_path=f"{cls.__module__}.{cls.__name__}",
71
+ source="builtin",
72
+ metadata=meta,
73
+ )
74
+ )
75
+ except Exception as exc:
76
+ listings.append(
77
+ AlgorithmListing(
78
+ name=name,
79
+ class_path=f"{cls.__module__}.{cls.__name__}",
80
+ source="builtin",
81
+ metadata=None,
82
+ status="unavailable",
83
+ error=str(exc),
84
+ )
85
+ )
86
+ except Exception:
87
+ pass
88
+
89
+ # Entry points
90
+ try:
91
+ eps = importlib_metadata.entry_points()
92
+ group: Iterable[Any]
93
+ if hasattr(eps, "select"):
94
+ group = list(eps.select(group="iints.algorithms"))
95
+ else:
96
+ eps_mapping = cast(Mapping[str, Sequence[object]], eps)
97
+ group = eps_mapping.get("iints.algorithms", ())
98
+ for ep in group:
99
+ listings.append(_load_entry_point(ep))
100
+ except Exception:
101
+ pass
102
+
103
+ return listings
@@ -0,0 +1,195 @@
1
+ # IINTS-AF RESEARCH SDK: ALGORITHM TEMPLATE
2
+ #
3
+ # Welcome to the IINTS-AF Research SDK! This file is your starting point
4
+ # for creating your own insulin delivery algorithm.
5
+ #
6
+ # HOW IT WORKS:
7
+ # 1. COPY THIS FILE: Copy this template into a new file within this same
8
+ # `user` directory (e.g., `MyAwesomeAlgorithm.py`).
9
+ # 2. RENAME THE CLASS: Change the class name from `TemplateAlgorithm` to something
10
+ # unique (e.g., `MyAwesomeAlgorithm`).
11
+ # 3. WRITE YOUR LOGIC: Implement your insulin calculation logic in the
12
+ # `predict_insulin` method.
13
+ # 4. RUN THE BATTLE: Your new algorithm will be automatically detected
14
+ # and will appear as an option in the IINTS-AF terminal.
15
+ #
16
+ # Happy coding!
17
+ #
18
+ # -----------------------------------------------------------------------------
19
+
20
+ from typing import Dict, Any, List
21
+
22
+ # All algorithms must inherit from the InsulinAlgorithm base class.
23
+ # It provides the necessary structure and methods that the IINTS-AF simulator expects.
24
+ from iints.api.base_algorithm import InsulinAlgorithm, AlgorithmInput, AlgorithmMetadata, WhyLogEntry
25
+
26
+ # A "Dose" is a dictionary containing the insulin breakdown. The simulator
27
+ # currently only requires 'total_insulin_delivered'. The other keys are for
28
+ # more detailed logging and analysis.
29
+ Dose = Dict[str, float]
30
+
31
+
32
+ class TemplateAlgorithm(InsulinAlgorithm):
33
+ """
34
+ This is a template for a user-defined insulin delivery algorithm.
35
+ It demonstrates the required structure and provides a basic, safe implementation
36
+ of a correction and meal bolus logic.
37
+ """
38
+
39
+ def get_algorithm_metadata(self) -> AlgorithmMetadata:
40
+ """
41
+ REQUIRED: This method provides metadata about your algorithm.
42
+ The simulator uses this to identify and display your algorithm in the UI.
43
+ """
44
+ return AlgorithmMetadata(
45
+ name="Template Algorithm", # The name that will appear in the UI
46
+ version="1.0.0",
47
+ author="Your Name Here",
48
+ description="A basic example algorithm demonstrating the IINTS-AF SDK.",
49
+ algorithm_type="rule_based" # 'rule_based', 'ml', or 'hybrid'
50
+ )
51
+
52
+ def predict_insulin(self, data: AlgorithmInput) -> Dose:
53
+ """
54
+ REQUIRED: This is the core method of your algorithm.
55
+ The simulator calls this method at each time step (e.g., every 5 minutes).
56
+
57
+ Args:
58
+ data (AlgorithmInput): An object containing the current patient data.
59
+ - data.current_glucose (float): The latest glucose reading in mg/dL.
60
+ - data.insulin_on_board (float): Estimated insulin still active from previous doses.
61
+ - data.carb_intake (float): Carbohydrates from a meal announced at this time step (in grams).
62
+ - data.time_step (float): The duration since the last call, in minutes.
63
+ - data.current_time (float): The current simulation time in minutes from the start.
64
+ - data.patient_state (Dict): A dictionary for you to store custom state between function calls (e.g., tracking glucose trends).
65
+
66
+ Returns:
67
+ Dose (Dict[str, float]): A dictionary specifying the insulin dose to be delivered.
68
+ Must contain at least the 'total_insulin_delivered' key.
69
+ """
70
+ # This function is called at every time step.
71
+ # First, we clear the reasoning log from the previous step.
72
+ self.why_log = []
73
+
74
+ # --- Example: Accessing Patient Data ---
75
+ current_glucose = data.current_glucose
76
+ iob = data.insulin_on_board
77
+ carbs = data.carb_intake
78
+
79
+ # --- Your Custom State Management (Optional) ---
80
+ # The `self.state` dictionary persists across calls for this simulation run.
81
+ # You can use it to track trends, previous states, etc.
82
+ previous_glucose = self.state.get('previous_glucose', current_glucose)
83
+ glucose_trend = (current_glucose - previous_glucose) / data.time_step # mg/dL/min
84
+ self.state['previous_glucose'] = current_glucose # Update state for the next call
85
+
86
+ # ----------------------------------------------------------------------
87
+ # --- YOUR CUSTOM LOGIC START ---
88
+ # ----------------------------------------------------------------------
89
+
90
+ # Initialize insulin components
91
+ correction_bolus = 0.0
92
+ meal_bolus = 0.0
93
+ basal_rate = 0.0 # This simple example does not use a dynamic basal rate
94
+
95
+ # Define algorithm parameters
96
+ # In a real algorithm, these would be managed more robustly.
97
+ target_glucose = 110 # mg/dL
98
+ insulin_sensitivity_factor = self.isf # mg/dL per Unit of insulin (set in base class)
99
+ carb_to_insulin_ratio = self.icr # grams of carb per Unit of insulin (set in base class)
100
+
101
+ # --- Log Key Data for Traceability ---
102
+ # This is how you make your algorithm's reasoning transparent!
103
+ self._log_reason(f"Current glucose is {current_glucose:.0f} mg/dL.", "glucose_level", current_glucose)
104
+ self._log_reason(f"Glucose trend is {glucose_trend:+.1f} mg/dL/min.", "velocity", glucose_trend)
105
+ self._log_reason(f"Active insulin (IOB) is {iob:.2f} U.", "insulin_on_board", iob)
106
+
107
+
108
+ # --- Example Correction Bolus Logic ---
109
+ # Only correct if glucose is above the target
110
+ if current_glucose > target_glucose:
111
+ glucose_diff = current_glucose - target_glucose
112
+ # Standard correction formula: (Current - Target) / ISF
113
+ correction_bolus = glucose_diff / insulin_sensitivity_factor
114
+ self._log_reason(
115
+ f"Calculated correction bolus of {correction_bolus:.2f} U for high glucose.",
116
+ "calculation",
117
+ correction_bolus,
118
+ clinical_impact="Aims to bring glucose back to target range."
119
+ )
120
+ else:
121
+ self._log_reason("No correction bolus needed (glucose is at or below target).", "calculation")
122
+
123
+
124
+ # --- Example Meal Bolus Logic ---
125
+ # Deliver a bolus if carbs are announced
126
+ if carbs > 0:
127
+ # Standard meal bolus formula: Carbs / ICR
128
+ meal_bolus = carbs / carb_to_insulin_ratio
129
+ self._log_reason(
130
+ f"Calculated meal bolus of {meal_bolus:.2f} U for {carbs}g carbs.",
131
+ "calculation",
132
+ meal_bolus,
133
+ clinical_impact="Aims to cover carbohydrate intake from a meal."
134
+ )
135
+
136
+ # --- Example Safety Guard: Insulin Stacking ---
137
+ # Reduce the calculated dose by the amount of insulin already on board (IOB).
138
+ # This is a crucial safety feature to prevent hypoglycemia from "stacking" insulin.
139
+ total_calculated_dose = meal_bolus + correction_bolus
140
+
141
+ if total_calculated_dose > iob:
142
+ final_bolus = total_calculated_dose - iob
143
+ self._log_reason(
144
+ f"Reducing dose by IOB ({iob:.2f} U). Final bolus: {final_bolus:.2f} U.",
145
+ "safety",
146
+ final_bolus,
147
+ clinical_impact="Prevents insulin stacking and reduces hypoglycemia risk."
148
+ )
149
+ else:
150
+ final_bolus = 0.0 # Don't give a negative dose
151
+ self._log_reason(
152
+ f"Calculated dose ({total_calculated_dose:.2f} U) is less than IOB ({iob:.2f} U). No bolus advised.",
153
+ "safety",
154
+ final_bolus,
155
+ clinical_impact="High IOB indicates sufficient active insulin; delivering more could be dangerous."
156
+ )
157
+
158
+ # --- Example Safety Guard: Hypoglycemia Prevention ---
159
+ # If glucose is low or dropping fast, suspend all insulin delivery.
160
+ if current_glucose < 80 or glucose_trend < -3.0:
161
+ final_bolus = 0.0
162
+ basal_rate = 0.0
163
+ self._log_reason(
164
+ "SUSPENDING INSULIN: Glucose is low or dropping rapidly.",
165
+ "safety",
166
+ current_glucose,
167
+ clinical_impact="Critical safety action to prevent severe hypoglycemia."
168
+ )
169
+
170
+ # ----------------------------------------------------------------------
171
+ # --- YOUR CUSTOM LOGIC END ---
172
+ # ----------------------------------------------------------------------
173
+
174
+ # The final dose must be assembled into a dictionary.
175
+ # The simulator only strictly requires 'total_insulin_delivered'.
176
+ # The other keys are for detailed analysis and logging.
177
+ dose: Dose = {
178
+ 'basal_insulin': basal_rate,
179
+ 'meal_bolus': meal_bolus,
180
+ 'correction_bolus': correction_bolus,
181
+ 'total_insulin_delivered': max(0, final_bolus + basal_rate) # Ensure no negative insulin
182
+ }
183
+
184
+ return dose
185
+
186
+ def reset(self):
187
+ """
188
+ This method is called by the simulator at the start of a new simulation.
189
+ Use it to reset any internal state of your algorithm.
190
+ """
191
+ # Reset the state dictionary for the new simulation run.
192
+ self.state = {}
193
+ # Also clear the reasoning log.
194
+ self.why_log = []
195
+ print(f"[{self.get_algorithm_metadata().name}] has been reset.")
Binary file
iints/cli/__init__.py ADDED
File without changes