iints-sdk-python35 0.1.7__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 (93) hide show
  1. iints/__init__.py +134 -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_performance_monitor.py +315 -0
  10. iints/analysis/explainability.py +94 -0
  11. iints/analysis/explainable_ai.py +232 -0
  12. iints/analysis/hardware_benchmark.py +221 -0
  13. iints/analysis/metrics.py +117 -0
  14. iints/analysis/reporting.py +261 -0
  15. iints/analysis/sensor_filtering.py +54 -0
  16. iints/analysis/validator.py +273 -0
  17. iints/api/__init__.py +0 -0
  18. iints/api/base_algorithm.py +300 -0
  19. iints/api/template_algorithm.py +195 -0
  20. iints/cli/__init__.py +0 -0
  21. iints/cli/cli.py +1286 -0
  22. iints/core/__init__.py +1 -0
  23. iints/core/algorithms/__init__.py +0 -0
  24. iints/core/algorithms/battle_runner.py +138 -0
  25. iints/core/algorithms/correction_bolus.py +86 -0
  26. iints/core/algorithms/discovery.py +92 -0
  27. iints/core/algorithms/fixed_basal_bolus.py +52 -0
  28. iints/core/algorithms/hybrid_algorithm.py +92 -0
  29. iints/core/algorithms/lstm_algorithm.py +138 -0
  30. iints/core/algorithms/mock_algorithms.py +69 -0
  31. iints/core/algorithms/pid_controller.py +88 -0
  32. iints/core/algorithms/standard_pump_algo.py +64 -0
  33. iints/core/device.py +0 -0
  34. iints/core/device_manager.py +64 -0
  35. iints/core/devices/__init__.py +3 -0
  36. iints/core/devices/models.py +155 -0
  37. iints/core/patient/__init__.py +3 -0
  38. iints/core/patient/models.py +246 -0
  39. iints/core/patient/patient_factory.py +117 -0
  40. iints/core/patient/profile.py +41 -0
  41. iints/core/safety/__init__.py +4 -0
  42. iints/core/safety/input_validator.py +87 -0
  43. iints/core/safety/supervisor.py +29 -0
  44. iints/core/simulation/__init__.py +0 -0
  45. iints/core/simulation/scenario_parser.py +61 -0
  46. iints/core/simulator.py +519 -0
  47. iints/core/supervisor.py +275 -0
  48. iints/data/__init__.py +42 -0
  49. iints/data/adapter.py +142 -0
  50. iints/data/column_mapper.py +398 -0
  51. iints/data/demo/__init__.py +1 -0
  52. iints/data/demo/demo_cgm.csv +289 -0
  53. iints/data/importer.py +275 -0
  54. iints/data/ingestor.py +162 -0
  55. iints/data/quality_checker.py +550 -0
  56. iints/data/universal_parser.py +813 -0
  57. iints/data/virtual_patients/clinic_safe_baseline.yaml +9 -0
  58. iints/data/virtual_patients/clinic_safe_hyper_challenge.yaml +9 -0
  59. iints/data/virtual_patients/clinic_safe_hypo_prone.yaml +9 -0
  60. iints/data/virtual_patients/clinic_safe_midnight.yaml +9 -0
  61. iints/data/virtual_patients/clinic_safe_pizza.yaml +9 -0
  62. iints/data/virtual_patients/clinic_safe_stress_meal.yaml +9 -0
  63. iints/data/virtual_patients/default_patient.yaml +11 -0
  64. iints/data/virtual_patients/patient_559_config.yaml +11 -0
  65. iints/emulation/__init__.py +80 -0
  66. iints/emulation/legacy_base.py +414 -0
  67. iints/emulation/medtronic_780g.py +337 -0
  68. iints/emulation/omnipod_5.py +367 -0
  69. iints/emulation/tandem_controliq.py +393 -0
  70. iints/highlevel.py +192 -0
  71. iints/learning/__init__.py +3 -0
  72. iints/learning/autonomous_optimizer.py +194 -0
  73. iints/learning/learning_system.py +122 -0
  74. iints/metrics.py +34 -0
  75. iints/presets/__init__.py +28 -0
  76. iints/presets/presets.json +114 -0
  77. iints/templates/__init__.py +0 -0
  78. iints/templates/default_algorithm.py +56 -0
  79. iints/templates/scenarios/__init__.py +0 -0
  80. iints/templates/scenarios/example_scenario.json +34 -0
  81. iints/utils/__init__.py +3 -0
  82. iints/utils/plotting.py +50 -0
  83. iints/validation/__init__.py +117 -0
  84. iints/validation/schemas.py +72 -0
  85. iints/visualization/__init__.py +34 -0
  86. iints/visualization/cockpit.py +691 -0
  87. iints/visualization/uncertainty_cloud.py +612 -0
  88. iints_sdk_python35-0.1.7.dist-info/METADATA +122 -0
  89. iints_sdk_python35-0.1.7.dist-info/RECORD +93 -0
  90. iints_sdk_python35-0.1.7.dist-info/WHEEL +5 -0
  91. iints_sdk_python35-0.1.7.dist-info/entry_points.txt +2 -0
  92. iints_sdk_python35-0.1.7.dist-info/licenses/LICENSE +28 -0
  93. iints_sdk_python35-0.1.7.dist-info/top_level.txt +1 -0
iints/__init__.py ADDED
@@ -0,0 +1,134 @@
1
+ # src/iints/__init__.py
2
+
3
+ import pandas as pd # Required for type hints like pd.DataFrame
4
+ from typing import Optional
5
+
6
+ __version__ = "0.1.5"
7
+
8
+ # API Components for Algorithm Development
9
+ from .api.base_algorithm import (
10
+ InsulinAlgorithm,
11
+ AlgorithmInput,
12
+ AlgorithmResult,
13
+ AlgorithmMetadata,
14
+ WhyLogEntry,
15
+ )
16
+
17
+ # Core Simulation Components
18
+ from .core.simulator import Simulator, StressEvent, SimulationLimitError
19
+ from .core.patient.models import PatientModel
20
+ from .core.patient.profile import PatientProfile
21
+ try:
22
+ from .core.device_manager import DeviceManager
23
+ except Exception: # pragma: no cover - fallback if torch/device manager import fails
24
+ class DeviceManager: # type: ignore
25
+ def __init__(self):
26
+ self._device = "cpu"
27
+
28
+ def get_device(self):
29
+ return self._device
30
+ from .core.safety import SafetySupervisor
31
+ from .core.devices.models import SensorModel, PumpModel
32
+ from .core.algorithms.standard_pump_algo import StandardPumpAlgorithm
33
+ from .core.algorithms.mock_algorithms import ConstantDoseAlgorithm, RandomDoseAlgorithm
34
+
35
+ # Data Handling
36
+ from .data.ingestor import DataIngestor
37
+ from .data.importer import (
38
+ ImportResult,
39
+ export_demo_csv,
40
+ export_standard_csv,
41
+ guess_column_mapping,
42
+ import_cgm_csv,
43
+ import_cgm_dataframe,
44
+ load_demo_dataframe,
45
+ scenario_from_csv,
46
+ scenario_from_dataframe,
47
+ )
48
+ from .analysis.metrics import generate_benchmark_metrics # Added for benchmark
49
+ from .analysis.reporting import ClinicalReportGenerator
50
+ from .highlevel import run_simulation, run_full
51
+
52
+ # Placeholder for Reporting/Analysis
53
+ # This will be further developed in a dedicated module (e.g., iints.analysis.reporting)
54
+ def generate_report(simulation_results: 'pd.DataFrame', output_path: Optional[str] = None, safety_report: Optional[dict] = None) -> Optional[str]:
55
+ """
56
+ Generate a clinical PDF report from simulation results.
57
+ """
58
+ if output_path is None:
59
+ return None
60
+ generator = ClinicalReportGenerator()
61
+ return generator.generate_pdf(simulation_results, safety_report or {}, output_path)
62
+
63
+ def generate_quickstart_report(
64
+ simulation_results: 'pd.DataFrame',
65
+ output_path: Optional[str] = None,
66
+ safety_report: Optional[dict] = None,
67
+ ) -> Optional[str]:
68
+ """
69
+ Generate a concise Quickstart PDF report from simulation results.
70
+ """
71
+ if output_path is None:
72
+ return None
73
+ generator = ClinicalReportGenerator()
74
+ return generator.generate_pdf(
75
+ simulation_results,
76
+ safety_report or {},
77
+ output_path,
78
+ title="IINTS-AF Quickstart Report",
79
+ )
80
+
81
+ def generate_demo_report(
82
+ simulation_results: 'pd.DataFrame',
83
+ output_path: Optional[str] = None,
84
+ safety_report: Optional[dict] = None,
85
+ ) -> Optional[str]:
86
+ """
87
+ Generate a demo-friendly PDF with big visuals (Maker Faire style).
88
+ """
89
+ if output_path is None:
90
+ return None
91
+ generator = ClinicalReportGenerator()
92
+ return generator.generate_demo_pdf(
93
+ simulation_results,
94
+ safety_report or {},
95
+ output_path,
96
+ title="IINTS-AF Demo Report",
97
+ )
98
+
99
+ # You can also define __all__ to explicitly control what gets imported with `from iints import *`
100
+ __all__ = [
101
+ # API
102
+ "InsulinAlgorithm", "AlgorithmInput", "AlgorithmResult", "AlgorithmMetadata", "WhyLogEntry",
103
+ # Core
104
+ "Simulator", "StressEvent", "PatientModel", "DeviceManager",
105
+ "PatientProfile",
106
+ "SimulationLimitError",
107
+ "SafetySupervisor",
108
+ "SensorModel",
109
+ "PumpModel",
110
+ "StandardPumpAlgorithm",
111
+ "ConstantDoseAlgorithm",
112
+ "RandomDoseAlgorithm",
113
+ # Data
114
+ "DataIngestor",
115
+ "ImportResult",
116
+ "export_demo_csv",
117
+ "export_standard_csv",
118
+ "guess_column_mapping",
119
+ "import_cgm_csv",
120
+ "import_cgm_dataframe",
121
+ "load_demo_dataframe",
122
+ "scenario_from_csv",
123
+ "scenario_from_dataframe",
124
+ # Analysis Metrics
125
+ "generate_benchmark_metrics",
126
+ "ClinicalReportGenerator",
127
+ # Reporting
128
+ "generate_report",
129
+ "generate_quickstart_report",
130
+ "generate_demo_report",
131
+ # High-level API
132
+ "run_simulation",
133
+ "run_full",
134
+ ]
@@ -0,0 +1,12 @@
1
+ from .clinical_metrics import ClinicalMetricsCalculator, ClinicalMetricsResult
2
+ from .baseline import compute_metrics, run_baseline_comparison, write_baseline_comparison
3
+ from .reporting import ClinicalReportGenerator
4
+
5
+ __all__ = [
6
+ "ClinicalMetricsCalculator",
7
+ "ClinicalMetricsResult",
8
+ "ClinicalReportGenerator",
9
+ "compute_metrics",
10
+ "run_baseline_comparison",
11
+ "write_baseline_comparison",
12
+ ]
@@ -0,0 +1,387 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Algorithm X-Ray - IINTS-AF
4
+ Make invisible medical decisions visible through decision replay and what-if analysis
5
+ """
6
+
7
+ import numpy as np
8
+ from typing import Any, Dict, List, Tuple, Optional
9
+ from dataclasses import dataclass
10
+ from datetime import datetime, timedelta
11
+ import json
12
+ from iints.data.quality_checker import QualityReport
13
+
14
+ @dataclass
15
+ class DecisionPoint:
16
+ """Single decision point in algorithm timeline"""
17
+ timestamp: datetime
18
+ glucose_mgdl: float
19
+ glucose_velocity: float
20
+ insulin_on_board: float
21
+ carbs_on_board: float
22
+
23
+ # Decision components
24
+ decision: float
25
+ confidence: float
26
+ reasoning: List[str]
27
+ safety_constraints: List[str]
28
+ risk_level: str
29
+
30
+ # What-if scenarios
31
+ alternative_decisions: Dict[str, float]
32
+
33
+ def to_dict(self):
34
+ return {
35
+ 'timestamp': self.timestamp.isoformat(),
36
+ 'glucose_mgdl': self.glucose_mgdl,
37
+ 'glucose_velocity': self.glucose_velocity,
38
+ 'insulin_on_board': self.insulin_on_board,
39
+ 'carbs_on_board': self.carbs_on_board,
40
+ 'decision': self.decision,
41
+ 'confidence': self.confidence,
42
+ 'reasoning': self.reasoning,
43
+ 'safety_constraints': self.safety_constraints,
44
+ 'risk_level': self.risk_level,
45
+ 'alternative_decisions': self.alternative_decisions
46
+ }
47
+
48
+ class AlgorithmXRay:
49
+ """X-ray vision into algorithm decision-making process"""
50
+
51
+ def __init__(self, quality_report: Optional[QualityReport] = None):
52
+ self.decision_timeline: List[DecisionPoint] = []
53
+ self.personality_profile: Dict[str, Any] = {}
54
+ self.quality_report = quality_report
55
+
56
+ def get_quality_report_summary(self) -> Dict[str, Any]:
57
+ """
58
+ Returns a summary of the data quality report if available.
59
+ """
60
+ if self.quality_report:
61
+ return {
62
+ "overall_score": self.quality_report.overall_score,
63
+ "summary": self.quality_report.summary,
64
+ "gaps_detected": len(self.quality_report.gaps),
65
+ "anomalies_detected": len(self.quality_report.anomalies),
66
+ "warnings": self.quality_report.warnings
67
+ }
68
+ return {}
69
+
70
+ def analyze_decision(self,
71
+ glucose_mgdl: float,
72
+ glucose_history: List[float],
73
+ insulin_history: List[float],
74
+ time_minutes: int) -> DecisionPoint:
75
+ """
76
+ Deep analysis of single decision point with full reasoning chain
77
+ """
78
+
79
+ # Calculate glucose velocity
80
+ if len(glucose_history) >= 2:
81
+ glucose_velocity = (glucose_history[-1] - glucose_history[-2]) / 5.0 # mg/dL per minute
82
+ else:
83
+ glucose_velocity = 0.0
84
+
85
+ # Estimate insulin on board (simplified)
86
+ insulin_on_board = sum(insulin_history[-6:]) * 0.5 if insulin_history else 0.0
87
+
88
+ # Estimate carbs on board (simplified - would need meal data)
89
+ carbs_on_board = 0.0
90
+
91
+ # Build reasoning chain
92
+ reasoning = []
93
+ safety_constraints = []
94
+ risk_level = "NORMAL"
95
+
96
+ # Glucose level reasoning
97
+ if glucose_mgdl < 70:
98
+ reasoning.append(f"Glucose critically low at {glucose_mgdl:.1f} mg/dL")
99
+ risk_level = "HIGH"
100
+ elif glucose_mgdl < 80:
101
+ reasoning.append(f"Glucose approaching low threshold at {glucose_mgdl:.1f} mg/dL")
102
+ risk_level = "MODERATE"
103
+ elif glucose_mgdl > 180:
104
+ reasoning.append(f"Glucose elevated at {glucose_mgdl:.1f} mg/dL")
105
+ risk_level = "MODERATE"
106
+ elif glucose_mgdl > 250:
107
+ reasoning.append(f"Glucose critically high at {glucose_mgdl:.1f} mg/dL")
108
+ risk_level = "HIGH"
109
+ else:
110
+ reasoning.append(f"Glucose in target range at {glucose_mgdl:.1f} mg/dL")
111
+
112
+ # Velocity reasoning
113
+ if abs(glucose_velocity) > 2.0:
114
+ direction = "rising" if glucose_velocity > 0 else "falling"
115
+ reasoning.append(f"Glucose {direction} rapidly at {abs(glucose_velocity):.2f} mg/dL/min")
116
+ if glucose_velocity < -2.0 and glucose_mgdl < 100:
117
+ safety_constraints.append("Rapid fall near hypoglycemia - insulin blocked")
118
+ elif abs(glucose_velocity) > 1.0:
119
+ direction = "rising" if glucose_velocity > 0 else "falling"
120
+ reasoning.append(f"Glucose {direction} moderately at {abs(glucose_velocity):.2f} mg/dL/min")
121
+ else:
122
+ reasoning.append("Glucose stable")
123
+
124
+ # Insulin on board reasoning
125
+ if insulin_on_board > 2.0:
126
+ reasoning.append(f"High insulin on board: {insulin_on_board:.2f} units")
127
+ safety_constraints.append("Insulin stacking prevention active")
128
+ elif insulin_on_board > 0.5:
129
+ reasoning.append(f"Moderate insulin on board: {insulin_on_board:.2f} units")
130
+
131
+ # Calculate base decision
132
+ error = glucose_mgdl - 120 # Target 120 mg/dL
133
+ base_decision = max(0, error * 0.02)
134
+
135
+ # Apply safety constraints
136
+ final_decision = base_decision
137
+ confidence = 0.8
138
+
139
+ if glucose_mgdl < 70 or (glucose_velocity < -2.0 and glucose_mgdl < 100):
140
+ final_decision = 0.0
141
+ confidence = 0.95
142
+ safety_constraints.append("Safety supervisor override: insulin delivery blocked")
143
+ elif insulin_on_board > 2.0:
144
+ final_decision = base_decision * 0.5
145
+ confidence = 0.7
146
+ safety_constraints.append("Insulin dose reduced due to stacking risk")
147
+
148
+ # Generate what-if scenarios
149
+ alternative_decisions = {
150
+ 'if_exercised_30min_ago': final_decision * 0.6,
151
+ 'if_meal_detected': final_decision * 1.3,
152
+ 'if_stress_detected': final_decision * 1.2,
153
+ 'if_sensor_noise_high': final_decision * 0.8,
154
+ 'aggressive_mode': base_decision * 1.5,
155
+ 'conservative_mode': base_decision * 0.5
156
+ }
157
+
158
+ decision_point = DecisionPoint(
159
+ timestamp=datetime.now(),
160
+ glucose_mgdl=glucose_mgdl,
161
+ glucose_velocity=glucose_velocity,
162
+ insulin_on_board=insulin_on_board,
163
+ carbs_on_board=carbs_on_board,
164
+ decision=final_decision,
165
+ confidence=confidence,
166
+ reasoning=reasoning,
167
+ safety_constraints=safety_constraints,
168
+ risk_level=risk_level,
169
+ alternative_decisions=alternative_decisions
170
+ )
171
+
172
+ self.decision_timeline.append(decision_point)
173
+ return decision_point
174
+
175
+ def calculate_personality_profile(self, decision_history: List[DecisionPoint]) -> Dict:
176
+ """
177
+ Calculate algorithm personality traits from decision history
178
+ """
179
+
180
+ if not decision_history:
181
+ return {}
182
+
183
+ # Hypo-aversion: how much does it avoid low glucose
184
+ hypo_decisions = [d for d in decision_history if d.glucose_mgdl < 80]
185
+ hypo_aversion = np.mean([1.0 - d.decision for d in hypo_decisions]) if hypo_decisions else 0.5
186
+
187
+ # Reaction speed: how quickly does it respond to changes
188
+ velocity_responses = [abs(d.decision - 0.5) for d in decision_history if abs(d.glucose_velocity) > 1.0]
189
+ reaction_speed = np.mean(velocity_responses) if velocity_responses else 0.5
190
+
191
+ # Correction intensity: how aggressive are corrections
192
+ high_glucose = [d for d in decision_history if d.glucose_mgdl > 180]
193
+ correction_intensity = np.mean([d.decision for d in high_glucose]) if high_glucose else 0.5
194
+
195
+ # Consistency: how stable are decisions in similar conditions
196
+ decision_values = [d.decision for d in decision_history]
197
+ consistency = 1.0 - (np.std(decision_values) if len(decision_values) > 1 else 0.5)
198
+
199
+ # Safety-first: how often safety constraints are triggered
200
+ safety_triggers = sum(1 for d in decision_history if d.safety_constraints)
201
+ safety_first = safety_triggers / len(decision_history) if decision_history else 0.0
202
+
203
+ personality = {
204
+ 'hypo_aversion': float(hypo_aversion),
205
+ 'reaction_speed': float(reaction_speed),
206
+ 'correction_intensity': float(correction_intensity),
207
+ 'consistency': float(consistency),
208
+ 'safety_first': float(safety_first),
209
+ 'decision_count': len(decision_history),
210
+ 'risk_distribution': {
211
+ 'high': sum(1 for d in decision_history if d.risk_level == 'HIGH'),
212
+ 'moderate': sum(1 for d in decision_history if d.risk_level == 'MODERATE'),
213
+ 'normal': sum(1 for d in decision_history if d.risk_level == 'NORMAL')
214
+ }
215
+ }
216
+
217
+ self.personality_profile = personality
218
+ return personality
219
+
220
+ def generate_decision_replay(self,
221
+ start_index: int = 0,
222
+ end_index: Optional[int] = None) -> Dict:
223
+ """
224
+ Generate interactive decision replay with full reasoning
225
+ """
226
+
227
+ if end_index is None:
228
+ end_index = len(self.decision_timeline)
229
+
230
+ replay_segment = self.decision_timeline[start_index:end_index]
231
+
232
+ replay_data = {
233
+ 'timeline': [d.to_dict() for d in replay_segment],
234
+ 'personality_profile': self.personality_profile,
235
+ 'summary': {
236
+ 'total_decisions': len(replay_segment),
237
+ 'safety_overrides': sum(1 for d in replay_segment if d.safety_constraints),
238
+ 'high_risk_moments': sum(1 for d in replay_segment if d.risk_level == 'HIGH'),
239
+ 'average_confidence': np.mean([d.confidence for d in replay_segment]),
240
+ 'glucose_range': {
241
+ 'min': min(d.glucose_mgdl for d in replay_segment),
242
+ 'max': max(d.glucose_mgdl for d in replay_segment),
243
+ 'mean': np.mean([d.glucose_mgdl for d in replay_segment])
244
+ }
245
+ }
246
+ }
247
+
248
+ return replay_data
249
+
250
+ def compare_what_if_scenarios(self, decision_point: DecisionPoint) -> Dict:
251
+ """
252
+ Compare actual decision with what-if alternatives
253
+ """
254
+
255
+ actual = decision_point.decision
256
+ alternatives = decision_point.alternative_decisions
257
+
258
+ comparison = {
259
+ 'actual_decision': actual,
260
+ 'alternatives': alternatives,
261
+ 'differences': {
262
+ scenario: {
263
+ 'absolute_diff': alt - actual,
264
+ 'percent_diff': ((alt - actual) / actual * 100) if actual > 0 else 0,
265
+ 'clinical_impact': self._estimate_clinical_impact(actual, alt, decision_point.glucose_mgdl)
266
+ }
267
+ for scenario, alt in alternatives.items()
268
+ }
269
+ }
270
+
271
+ return comparison
272
+
273
+ def _estimate_clinical_impact(self, actual: float, alternative: float, glucose: float) -> str:
274
+ """Estimate clinical impact of alternative decision"""
275
+
276
+ diff = alternative - actual
277
+
278
+ if abs(diff) < 0.1:
279
+ return "Minimal impact"
280
+ elif glucose < 70:
281
+ if diff < 0:
282
+ return "Safer - reduces hypo risk"
283
+ else:
284
+ return "Riskier - increases hypo risk"
285
+ elif glucose > 180:
286
+ if diff > 0:
287
+ return "More aggressive correction"
288
+ else:
289
+ return "More conservative approach"
290
+ else:
291
+ return "Moderate impact on glucose trajectory"
292
+
293
+ def export_xray_report(self, filepath: str):
294
+ """Export complete X-ray analysis to JSON"""
295
+
296
+ report = {
297
+ 'generated_at': datetime.now().isoformat(),
298
+ 'total_decisions': len(self.decision_timeline),
299
+ 'personality_profile': self.personality_profile,
300
+ 'decision_timeline': [d.to_dict() for d in self.decision_timeline],
301
+ 'summary_statistics': self._calculate_summary_stats()
302
+ }
303
+
304
+ with open(filepath, 'w') as f:
305
+ json.dump(report, f, indent=2)
306
+
307
+ return filepath
308
+
309
+ def _calculate_summary_stats(self) -> Dict:
310
+ """Calculate summary statistics across all decisions"""
311
+
312
+ if not self.decision_timeline:
313
+ return {}
314
+
315
+ return {
316
+ 'average_decision': np.mean([d.decision for d in self.decision_timeline]),
317
+ 'decision_std': np.std([d.decision for d in self.decision_timeline]),
318
+ 'average_confidence': np.mean([d.confidence for d in self.decision_timeline]),
319
+ 'safety_override_rate': sum(1 for d in self.decision_timeline if d.safety_constraints) / len(self.decision_timeline),
320
+ 'high_risk_rate': sum(1 for d in self.decision_timeline if d.risk_level == 'HIGH') / len(self.decision_timeline),
321
+ 'glucose_stats': {
322
+ 'mean': np.mean([d.glucose_mgdl for d in self.decision_timeline]),
323
+ 'std': np.std([d.glucose_mgdl for d in self.decision_timeline]),
324
+ 'min': min(d.glucose_mgdl for d in self.decision_timeline),
325
+ 'max': max(d.glucose_mgdl for d in self.decision_timeline)
326
+ }
327
+ }
328
+
329
+ def main():
330
+ """Demonstration of Algorithm X-Ray system"""
331
+
332
+ print(" ALGORITHM X-RAY DEMONSTRATION")
333
+ print("=" * 50)
334
+ print("Making invisible medical decisions visible\n")
335
+
336
+ xray = AlgorithmXRay()
337
+
338
+ # Simulate decision sequence
339
+ glucose_history = [120, 125, 135, 150, 165, 175, 180, 185, 190, 185]
340
+ insulin_history = [0.5, 0.6, 0.8, 1.0, 1.2, 1.5, 1.8, 2.0, 1.8, 1.5]
341
+
342
+ print("Analyzing decision sequence...\n")
343
+
344
+ for i, glucose in enumerate(glucose_history):
345
+ decision = xray.analyze_decision(
346
+ glucose_mgdl=glucose,
347
+ glucose_history=glucose_history[:i+1],
348
+ insulin_history=insulin_history[:i+1],
349
+ time_minutes=i * 5
350
+ )
351
+
352
+ print(f"Decision Point {i+1}:")
353
+ print(f" Glucose: {decision.glucose_mgdl:.1f} mg/dL")
354
+ print(f" Velocity: {decision.glucose_velocity:.2f} mg/dL/min")
355
+ print(f" Decision: {decision.decision:.2f} units")
356
+ print(f" Confidence: {decision.confidence:.1%}")
357
+ print(f" Risk Level: {decision.risk_level}")
358
+ print(f" Reasoning:")
359
+ for reason in decision.reasoning:
360
+ print(f" - {reason}")
361
+ if decision.safety_constraints:
362
+ print(f" Safety Constraints:")
363
+ for constraint in decision.safety_constraints:
364
+ print(f" {constraint}")
365
+ print()
366
+
367
+ # Calculate personality
368
+ print("\n ALGORITHM PERSONALITY PROFILE")
369
+ print("=" * 50)
370
+ personality = xray.calculate_personality_profile(xray.decision_timeline)
371
+
372
+ print(f"Hypo-Aversion: {personality['hypo_aversion']:.2f} (0=aggressive, 1=cautious)")
373
+ print(f"Reaction Speed: {personality['reaction_speed']:.2f} (0=slow, 1=fast)")
374
+ print(f"Correction Intensity: {personality['correction_intensity']:.2f} (0=gentle, 1=aggressive)")
375
+ print(f"Consistency: {personality['consistency']:.2f} (0=variable, 1=stable)")
376
+ print(f"Safety-First: {personality['safety_first']:.2f} (0=permissive, 1=strict)")
377
+
378
+ # Export report
379
+ from pathlib import Path
380
+ results_dir = Path("results/algorithm_xray")
381
+ results_dir.mkdir(parents=True, exist_ok=True)
382
+
383
+ report_path = xray.export_xray_report(str(results_dir / "xray_report.json"))
384
+ print(f"\n X-Ray report exported to: {report_path}")
385
+
386
+ if __name__ == "__main__":
387
+ main()
@@ -0,0 +1,92 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ from typing import Any, Dict, List, Tuple, Optional
5
+
6
+ import json
7
+ import pandas as pd
8
+
9
+ from iints.analysis.clinical_metrics import ClinicalMetricsCalculator
10
+ from iints.api.base_algorithm import InsulinAlgorithm
11
+ from iints.core.algorithms.pid_controller import PIDController
12
+ from iints.core.algorithms.standard_pump_algo import StandardPumpAlgorithm
13
+ from iints.core.patient.models import PatientModel
14
+ from iints.core.simulator import Simulator
15
+ from iints.validation import build_stress_events
16
+
17
+
18
+ def compute_metrics(results_df: pd.DataFrame) -> Dict[str, float]:
19
+ calculator = ClinicalMetricsCalculator()
20
+ duration_hours = results_df["time_minutes"].max() / 60.0 if len(results_df) else 0.0
21
+ metrics = calculator.calculate(
22
+ glucose=results_df["glucose_actual_mgdl"],
23
+ duration_hours=duration_hours,
24
+ )
25
+ return metrics.to_dict()
26
+
27
+
28
+ def run_baseline_comparison(
29
+ patient_params: Dict[str, Any],
30
+ stress_event_payloads: List[Dict[str, Any]],
31
+ duration: int,
32
+ time_step: int,
33
+ primary_label: str,
34
+ primary_results: pd.DataFrame,
35
+ primary_safety: Dict[str, Any],
36
+ compare_standard_pump: bool = True,
37
+ seed: Optional[int] = None,
38
+ ) -> Dict[str, Any]:
39
+ rows: List[Dict[str, Any]] = []
40
+
41
+ primary_metrics = compute_metrics(primary_results)
42
+ rows.append(
43
+ {
44
+ "algorithm": primary_label,
45
+ "tir_70_180": primary_metrics.get("tir_70_180", 0.0),
46
+ "tir_below_70": primary_metrics.get("tir_below_70", 0.0),
47
+ "tir_above_180": primary_metrics.get("tir_above_180", 0.0),
48
+ "bolus_interventions": primary_safety.get("bolus_interventions_count", 0),
49
+ "total_violations": primary_safety.get("total_violations", 0),
50
+ }
51
+ )
52
+
53
+ baselines: List[Tuple[str, InsulinAlgorithm]] = [("Standard PID", PIDController())]
54
+ if compare_standard_pump:
55
+ baselines.append(("Standard Pump", StandardPumpAlgorithm()))
56
+
57
+ for label, algo in baselines:
58
+ patient_model = PatientModel(**patient_params)
59
+ simulator = Simulator(
60
+ patient_model=patient_model,
61
+ algorithm=algo,
62
+ time_step=time_step,
63
+ seed=seed,
64
+ )
65
+ for event in build_stress_events(stress_event_payloads):
66
+ simulator.add_stress_event(event)
67
+ results_df, safety_report = simulator.run_batch(duration)
68
+ metrics = compute_metrics(results_df)
69
+ rows.append(
70
+ {
71
+ "algorithm": label,
72
+ "tir_70_180": metrics.get("tir_70_180", 0.0),
73
+ "tir_below_70": metrics.get("tir_below_70", 0.0),
74
+ "tir_above_180": metrics.get("tir_above_180", 0.0),
75
+ "bolus_interventions": safety_report.get("bolus_interventions_count", 0),
76
+ "total_violations": safety_report.get("total_violations", 0),
77
+ }
78
+ )
79
+
80
+ return {
81
+ "reference": "Standard PID",
82
+ "rows": rows,
83
+ }
84
+
85
+
86
+ def write_baseline_comparison(comparison: Dict[str, Any], output_dir: Path) -> Dict[str, str]:
87
+ output_dir.mkdir(parents=True, exist_ok=True)
88
+ json_path = output_dir / "baseline_comparison.json"
89
+ csv_path = output_dir / "baseline_comparison.csv"
90
+ json_path.write_text(json.dumps(comparison, indent=2))
91
+ pd.DataFrame(comparison.get("rows", [])).to_csv(csv_path, index=False)
92
+ return {"json": str(json_path), "csv": str(csv_path)}