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,691 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Clinical Control Center - IINTS-AF
4
+ Professional medical dashboard for diabetes algorithm research.
5
+
6
+ Features:
7
+ - Real-time glucose visualization with uncertainty cloud
8
+ - Algorithm reasoning log ("Why" panel)
9
+ - Multi-algorithm comparison (Battle Mode)
10
+ - Legacy pump comparison
11
+ - Clinical metrics dashboard
12
+ - Alert and safety monitoring
13
+
14
+ This is not a hobby app - it's a professional medical cockpit.
15
+ """
16
+
17
+ import numpy as np
18
+ import pandas as pd
19
+ import matplotlib.pyplot as plt
20
+ from matplotlib.gridspec import GridSpec
21
+ from typing import Dict, List, Optional, Any, Tuple
22
+ from dataclasses import dataclass, field
23
+ from datetime import datetime
24
+ import json
25
+
26
+
27
+ @dataclass
28
+ class CockpitConfig:
29
+ """Configuration for the clinical cockpit"""
30
+ # Layout
31
+ figure_size: Tuple[int, int] = (20, 12)
32
+ dpi: int = 150
33
+
34
+ # Colors
35
+ primary_color: str = '#2196F3'
36
+ secondary_color: str = '#4CAF50'
37
+ alert_color: str = '#F44336'
38
+ warning_color: str = '#FF9800'
39
+ success_color: str = '#4CAF50'
40
+
41
+ # Target zones
42
+ target_low: float = 70.0
43
+ target_high: float = 180.0
44
+ critical_low: float = 54.0
45
+ critical_high: float = 250.0
46
+
47
+ # Update interval (for real-time mode)
48
+ update_interval: float = 0.5 # seconds
49
+
50
+
51
+ @dataclass
52
+ class DashboardState:
53
+ """Current state of the dashboard"""
54
+ current_time: int = 0
55
+ current_glucose: float = 0.0
56
+ glucose_velocity: float = 0.0
57
+ insulin_delivered: float = 0.0
58
+ iob: float = 0.0
59
+ cob: float = 0.0
60
+
61
+ # Algorithm state
62
+ algorithm_name: str = ""
63
+ algorithm_confidence: float = 0.0
64
+ prediction: float = 0.0
65
+ uncertainty: float = 0.0
66
+
67
+ # Safety state
68
+ safety_alerts: List[str] = field(default_factory=list)
69
+ hypo_risk: str = "normal"
70
+ hyper_risk: str = "normal"
71
+
72
+ # Metrics
73
+ tir: float = 0.0
74
+ cv: float = 0.0
75
+ gmi: float = 0.0
76
+
77
+ def to_dict(self) -> Dict:
78
+ return {
79
+ 'current_time': self.current_time,
80
+ 'current_glucose': self.current_glucose,
81
+ 'glucose_velocity': self.glucose_velocity,
82
+ 'insulin_delivered': self.insulin_delivered,
83
+ 'iob': self.iob,
84
+ 'cob': self.cob,
85
+ 'algorithm_name': self.algorithm_name,
86
+ 'algorithm_confidence': self.algorithm_confidence,
87
+ 'prediction': self.prediction,
88
+ 'uncertainty': self.uncertainty,
89
+ 'safety_alerts': self.safety_alerts,
90
+ 'tir': self.tir,
91
+ 'cv': self.cv,
92
+ 'gmi': self.gmi
93
+ }
94
+
95
+
96
+ class ClinicalCockpit:
97
+ """
98
+ Professional clinical dashboard for diabetes algorithm research.
99
+
100
+ This dashboard provides:
101
+ 1. Main glucose display with target zones
102
+ 2. Uncertainty cloud around predictions
103
+ 3. Reasoning log ("Why" panel)
104
+ 4. Algorithm personality metrics
105
+ 5. Safety alerts
106
+ 6. Battle mode comparison
107
+
108
+ Usage:
109
+ cockpit = ClinicalCockpit()
110
+
111
+ # For real-time updates
112
+ cockpit.update(state)
113
+
114
+ # For end-to-end visualization
115
+ cockpit.visualize_results(simulation_data)
116
+
117
+ # For battle mode comparison
118
+ cockpit.compare_battle(battle_report)
119
+ """
120
+
121
+ def __init__(self, config: Optional[CockpitConfig] = None):
122
+ """
123
+ Initialize clinical cockpit.
124
+
125
+ Args:
126
+ config: Dashboard configuration (default used if None)
127
+ """
128
+ self.config = config or CockpitConfig()
129
+ self.state = DashboardState()
130
+ self.history: List[Dict[str, Any]] = []
131
+
132
+ def _create_glucose_panel(self,
133
+ gs: GridSpec,
134
+ simulation_data: pd.DataFrame,
135
+ predictions: Optional[np.ndarray] = None,
136
+ ax: Optional[plt.Axes] = None) -> plt.Axes:
137
+ """Create main glucose visualization panel"""
138
+ if ax is None:
139
+ ax = plt.subplot(gs[0, :])
140
+
141
+ timestamps = simulation_data['time_minutes']
142
+ glucose = simulation_data['glucose_actual_mgdl']
143
+
144
+ # Target zones
145
+ ax.axhspan(self.config.target_low, self.config.target_high,
146
+ alpha=0.15, color='green', label='Target (70-180)')
147
+ ax.axhspan(self.config.critical_low, self.config.target_low,
148
+ alpha=0.2, color='red', label='Low Zone')
149
+ ax.axhspan(self.config.target_high, self.config.critical_high,
150
+ alpha=0.2, color='orange', label='High Zone')
151
+
152
+ # Glucose line
153
+ ax.plot(timestamps, glucose,
154
+ color=self.config.primary_color,
155
+ linewidth=2,
156
+ label='Glucose')
157
+
158
+ # Safety overrides timeline markers
159
+ if 'safety_triggered' in simulation_data.columns:
160
+ safety_mask = simulation_data['safety_triggered'].astype(bool)
161
+ if safety_mask.any():
162
+ ax.scatter(
163
+ timestamps[safety_mask],
164
+ glucose[safety_mask],
165
+ color='red',
166
+ s=20,
167
+ label='Safety Override',
168
+ zorder=3
169
+ )
170
+
171
+ # Add predictions if provided
172
+ if predictions is not None:
173
+ ax.plot(timestamps, predictions,
174
+ color=self.config.secondary_color,
175
+ linewidth=1.5,
176
+ linestyle='--',
177
+ label='Prediction',
178
+ alpha=0.7)
179
+
180
+ # Uncertainty band
181
+ if 'uncertainty' in simulation_data.columns:
182
+ uncertainty = np.asarray(simulation_data['uncertainty'], dtype=float)
183
+ lower = predictions - 30 * (1.0 + uncertainty)
184
+ upper = predictions + 30 * (1.0 + uncertainty)
185
+ ax.fill_between(timestamps, lower, upper,
186
+ alpha=0.2, color=self.config.secondary_color,
187
+ label='Uncertainty')
188
+
189
+ # Reference lines
190
+ ax.axhline(y=120, color='gray', linestyle=':', alpha=0.5)
191
+ ax.axhline(y=self.config.target_low, color='red', linestyle='--', alpha=0.5)
192
+ ax.axhline(y=self.config.target_high, color='orange', linestyle='--', alpha=0.5)
193
+
194
+ # Formatting
195
+ ax.set_xlim(timestamps.min(), timestamps.max())
196
+ ax.set_ylim(40, 350)
197
+ ax.set_xlabel('Time (minutes)', fontsize=10)
198
+ ax.set_ylabel('Glucose (mg/dL)', fontsize=10)
199
+ ax.set_title('Glucose Monitor', fontsize=12, fontweight='bold')
200
+ ax.legend(loc='upper right', fontsize=8)
201
+ ax.grid(True, alpha=0.3)
202
+
203
+ return ax
204
+
205
+ def _create_reasoning_panel(self,
206
+ gs: GridSpec,
207
+ reasoning_logs: List[Dict],
208
+ ax: Optional[plt.Axes] = None) -> plt.Axes:
209
+ """Create reasoning log ("Why") panel"""
210
+ if ax is None:
211
+ ax = plt.subplot(gs[1, :2])
212
+
213
+ ax.set_xlim(0, 10)
214
+ ax.set_ylim(0, 10)
215
+ ax.axis('off')
216
+
217
+ ax.set_title('Reasoning Log', fontsize=12, fontweight='bold', pad=10)
218
+
219
+ # Display most recent reasoning
220
+ if reasoning_logs:
221
+ latest = reasoning_logs[-1]
222
+ algorithm = latest.get('algorithm', 'Unknown')
223
+ decision = latest.get('decision', 0)
224
+ reasons = latest.get('reasons', ['No reasoning available'])
225
+
226
+ # Algorithm name
227
+ ax.text(0.5, 9.5, f"Algorithm: {algorithm}", fontsize=11, fontweight='bold')
228
+
229
+ # Decision
230
+ ax.text(0.5, 8.5, f"Decision: {decision:.2f} units", fontsize=10)
231
+
232
+ # Primary reason
233
+ ax.text(0.5, 7.5, "Why?", fontsize=10, fontweight='bold', color='blue')
234
+
235
+ for i, reason in enumerate(reasons[:5]):
236
+ y_pos = 6.5 - i * 0.8
237
+ ax.text(0.7, y_pos, f"• {reason}", fontsize=9)
238
+
239
+ # Confidence indicator
240
+ confidence = latest.get('confidence', 0.5)
241
+ conf_color = 'green' if confidence > 0.7 else 'orange' if confidence > 0.4 else 'red'
242
+ ax.text(0.5, 2.5, f"AI Confidence: {confidence:.0%}",
243
+ fontsize=10, color=conf_color, fontweight='bold')
244
+ else:
245
+ ax.text(0.5, 5, "No decisions recorded", fontsize=10, ha='center')
246
+
247
+ return ax
248
+
249
+ def _create_metrics_panel(self,
250
+ gs: GridSpec,
251
+ metrics: Dict,
252
+ ax: Optional[plt.Axes] = None) -> plt.Axes:
253
+ """Create clinical metrics panel"""
254
+ if ax is None:
255
+ ax = plt.subplot(gs[1, 2:4])
256
+
257
+ ax.set_xlim(0, 10)
258
+ ax.set_ylim(0, 10)
259
+ ax.axis('off')
260
+
261
+ ax.set_title('Clinical Metrics', fontsize=12, fontweight='bold', pad=10)
262
+
263
+ # Key metrics
264
+ metrics_to_show = [
265
+ ('TIR (70-180)', f"{metrics.get('tir_70_180', 0):.1f}%"),
266
+ ('TIR (70-140)', f"{metrics.get('tir_70_140', 0):.1f}%"),
267
+ ('Time <70', f"{metrics.get('tir_below_70', 0):.1f}%"),
268
+ ('Time >180', f"{metrics.get('tir_above_180', 0):.1f}%"),
269
+ ('CV', f"{metrics.get('cv', 0):.1f}%"),
270
+ ('GMI', f"{metrics.get('gmi', 0):.1f}%"),
271
+ ('LBGI', f"{metrics.get('lbgi', 0):.2f}"),
272
+ ('Total Insulin', f"{metrics.get('total_insulin', 0):.1f} U"),
273
+ ]
274
+
275
+ for i, (name, value) in enumerate(metrics_to_show):
276
+ ax.text(0.5, 8.5 - i * 0.9, f"{name}:", fontsize=9, fontweight='bold')
277
+
278
+ # Color code TIR
279
+ if 'TIR' in name and '70-180' in name:
280
+ val = float(value.replace('%', ''))
281
+ color = 'green' if val > 70 else 'orange' if val > 50 else 'red'
282
+ elif 'CV' in name:
283
+ val = float(value.replace('%', ''))
284
+ color = 'green' if val < 36 else 'orange' if val < 50 else 'red'
285
+ else:
286
+ color = 'black'
287
+
288
+ ax.text(4.5, 8.5 - i * 0.9, value, fontsize=9, color=color)
289
+
290
+ return ax
291
+
292
+ def _create_safety_panel(self,
293
+ gs: GridSpec,
294
+ safety_timeline: List[Dict[str, Any]],
295
+ ax: Optional[plt.Axes] = None) -> plt.Axes:
296
+ """Create safety alerts panel with timeline"""
297
+ if ax is None:
298
+ ax = plt.subplot(gs[1, 4:6])
299
+
300
+ ax.set_xlim(0, 10)
301
+ ax.set_ylim(0, 10)
302
+ ax.axis('off')
303
+
304
+ ax.set_title('Safety Monitor', fontsize=12, fontweight='bold', pad=10)
305
+
306
+ if safety_timeline:
307
+ recent = safety_timeline[-6:]
308
+ for i, entry in enumerate(recent):
309
+ y_pos = 8.5 - i * 1.2
310
+ reason = entry.get('reason', 'UNKNOWN')
311
+ time_min = entry.get('time_min', 0)
312
+ marker = '🔴' if 'HYPO' in reason or 'EMERGENCY' in reason else '🟡'
313
+ ax.text(0.5, y_pos, f"{marker} t={time_min:.0f}m {reason}", fontsize=8)
314
+ ax.text(0.5, 1.0, f"Overrides: {len(safety_timeline)}", fontsize=9, fontweight='bold')
315
+ else:
316
+ ax.text(0.5, 5, "✅ No active alerts", fontsize=10, ha='center', color='green')
317
+
318
+ return ax
319
+
320
+ def _create_algorithm_personality_panel(self,
321
+ gs: GridSpec,
322
+ personality: Dict,
323
+ ax: Optional[plt.Axes] = None) -> plt.Axes:
324
+ """Create algorithm personality panel"""
325
+ if ax is None:
326
+ ax = plt.subplot(gs[2, :2])
327
+
328
+ ax.set_xlim(0, 10)
329
+ ax.set_ylim(0, 10)
330
+ ax.axis('off')
331
+
332
+ ax.set_title('Algorithm Personality', fontsize=12, fontweight='bold', pad=10)
333
+
334
+ name = personality.get('name', 'Unknown')
335
+ p = personality.get('personality', {})
336
+
337
+ ax.text(0.5, 9.0, name, fontsize=11, fontweight='bold')
338
+
339
+ traits = [
340
+ ('Aggressiveness', p.get('aggressiveness', 'Unknown')),
341
+ ('Hypo Aversion', p.get('hypo_aversion', 'Unknown')),
342
+ ('Response Speed', p.get('response_speed', 'Unknown')),
343
+ ('Correction Style', p.get('correction_aggressiveness', 'Unknown')),
344
+ ]
345
+
346
+ for i, (trait, value) in enumerate(traits):
347
+ ax.text(0.5, 7.5 - i * 1.0, f"{trait}:", fontsize=9, fontweight='bold')
348
+ ax.text(4.0, 7.5 - i * 1.0, value, fontsize=9)
349
+
350
+ return ax
351
+
352
+ def _create_insulin_panel(self,
353
+ gs: GridSpec,
354
+ simulation_data: pd.DataFrame,
355
+ ax: Optional[plt.Axes] = None) -> plt.Axes:
356
+ """Create insulin delivery panel"""
357
+ if ax is None:
358
+ ax = plt.subplot(gs[2, 2:4])
359
+
360
+ timestamps = simulation_data['time_minutes']
361
+ insulin = simulation_data['delivered_insulin_units']
362
+ if 'patient_iob_units' in simulation_data.columns:
363
+ iob = simulation_data['patient_iob_units']
364
+ else:
365
+ iob = pd.Series(np.zeros(len(timestamps)))
366
+
367
+ ax.bar(timestamps, insulin, width=4, alpha=0.7,
368
+ color=self.config.secondary_color, label='Insulin Delivered')
369
+ ax.plot(timestamps, iob, color='purple', linewidth=2,
370
+ label='Insulin on Board')
371
+
372
+ ax.set_xlabel('Time (minutes)', fontsize=10)
373
+ ax.set_ylabel('Units', fontsize=10)
374
+ ax.set_title('Insulin Delivery', fontsize=12, fontweight='bold')
375
+ ax.legend(loc='upper right', fontsize=8)
376
+ ax.grid(True, alpha=0.3)
377
+
378
+ return ax
379
+
380
+ def _create_summary_panel(self,
381
+ gs: GridSpec,
382
+ summary: Dict,
383
+ ax: Optional[plt.Axes] = None) -> plt.Axes:
384
+ """Create summary statistics panel"""
385
+ if ax is None:
386
+ ax = plt.subplot(gs[2, 4:6])
387
+
388
+ ax.set_xlim(0, 10)
389
+ ax.set_ylim(0, 10)
390
+ ax.axis('off')
391
+
392
+ ax.set_title('Summary', fontsize=12, fontweight='bold', pad=10)
393
+
394
+ summary_text = [
395
+ f"Duration: {summary.get('duration_hours', 0):.1f} hours",
396
+ f"Data Points: {summary.get('data_points', 0)}",
397
+ f"Decisions: {summary.get('decisions', 0)}",
398
+ f"Hypo Events: {summary.get('hypo_events', 0)}",
399
+ f"Hyper Events: {summary.get('hyper_events', 0)}",
400
+ f"Avg Glucose: {summary.get('mean_glucose', 0):.0f} mg/dL",
401
+ f"Glucose Range: {summary.get('min_glucose', 0):.0f}-{summary.get('max_glucose', 0):.0f}",
402
+ ]
403
+
404
+ for i, line in enumerate(summary_text):
405
+ ax.text(0.5, 8.5 - i * 1.0, line, fontsize=9)
406
+
407
+ return ax
408
+
409
+ def visualize_results(self,
410
+ simulation_data: pd.DataFrame,
411
+ predictions: Optional[np.ndarray] = None,
412
+ reasoning_logs: Optional[List[Dict]] = None,
413
+ metrics: Optional[Dict] = None,
414
+ personality: Optional[Dict] = None,
415
+ save_path: Optional[str] = None) -> plt.Figure:
416
+ """
417
+ Create complete clinical dashboard visualization.
418
+
419
+ Args:
420
+ simulation_data: DataFrame with simulation results
421
+ predictions: Optional array of glucose predictions
422
+ reasoning_logs: Optional list of reasoning logs
423
+ metrics: Optional clinical metrics dictionary
424
+ personality: Optional algorithm personality
425
+ save_path: Optional path to save figure
426
+
427
+ Returns:
428
+ matplotlib Figure
429
+ """
430
+ # Create figure
431
+ fig = plt.figure(
432
+ figsize=self.config.figure_size,
433
+ dpi=self.config.dpi
434
+ )
435
+
436
+ # Create grid layout
437
+ gs = GridSpec(3, 6, figure=fig, hspace=0.35, wspace=0.3)
438
+
439
+ # Create panels
440
+ self._create_glucose_panel(gs, simulation_data, predictions)
441
+
442
+ reasoning_logs = reasoning_logs or []
443
+ self._create_reasoning_panel(gs, reasoning_logs)
444
+
445
+ metrics = metrics or {}
446
+ self._create_metrics_panel(gs, metrics)
447
+
448
+ safety_timeline: List[Dict[str, Any]] = []
449
+ if 'safety_triggered' in simulation_data.columns and 'safety_reason' in simulation_data.columns:
450
+ for _, row in simulation_data.iterrows():
451
+ if bool(row.get('safety_triggered', False)):
452
+ safety_timeline.append({
453
+ 'time_min': row.get('time_minutes', 0),
454
+ 'reason': row.get('safety_reason', 'UNKNOWN')
455
+ })
456
+ self._create_safety_panel(gs, safety_timeline)
457
+
458
+ personality = personality or {}
459
+ self._create_algorithm_personality_panel(gs, personality)
460
+
461
+ self._create_insulin_panel(gs, simulation_data)
462
+
463
+ # Calculate summary
464
+ glucose_col = pd.to_numeric(simulation_data['glucose_actual_mgdl'])
465
+ summary = {
466
+ 'duration_hours': (simulation_data['time_minutes'].max() -
467
+ simulation_data['time_minutes'].min()) / 60,
468
+ 'data_points': len(simulation_data),
469
+ 'decisions': (simulation_data['delivered_insulin_units'].gt(0)).sum(),
470
+ 'hypo_events': ((glucose_col.lt(70.0)) & (glucose_col.diff().lt(0))).sum(),
471
+ 'hyper_events': ((glucose_col.gt(250.0)) & (glucose_col.diff().gt(0))).sum(),
472
+ 'mean_glucose': glucose_col.mean(),
473
+ 'min_glucose': glucose_col.min(),
474
+ 'max_glucose': glucose_col.max(),
475
+ }
476
+ self._create_summary_panel(gs, summary)
477
+
478
+ # Add title
479
+ fig.suptitle('IINTS-AF Clinical Control Center',
480
+ fontsize=16, fontweight='bold', y=0.98)
481
+
482
+ plt.tight_layout(rect=(0, 0, 1, 0.96))
483
+
484
+ if save_path:
485
+ plt.savefig(save_path, dpi=self.config.dpi, bbox_inches='tight')
486
+ print(f" Dashboard saved to: {save_path}")
487
+
488
+ return fig
489
+
490
+ def compare_battle(self,
491
+ battle_report,
492
+ save_path: Optional[str] = None) -> plt.Figure:
493
+ """
494
+ Create battle mode comparison dashboard.
495
+
496
+ Args:
497
+ battle_report: BattleReport object
498
+ save_path: Optional path to save
499
+
500
+ Returns:
501
+ matplotlib Figure
502
+ """
503
+ fig = plt.figure(
504
+ figsize=(20, 14),
505
+ dpi=self.config.dpi
506
+ )
507
+
508
+ gs = GridSpec(3, 2, figure=fig, hspace=0.35, wspace=0.3)
509
+
510
+ # Title
511
+ fig.suptitle(f' Battle Mode: {battle_report.battle_name}\n Winner: {battle_report.winner}',
512
+ fontsize=14, fontweight='bold')
513
+
514
+ # Rankings summary
515
+ ax_rankings = plt.subplot(gs[0, :])
516
+ ax_rankings.axis('off')
517
+
518
+ ranking_text = "RANKINGS\n" + "="*50 + "\n"
519
+ for i, rank in enumerate(battle_report.rankings, 1):
520
+ medal = "1st" if i == 1 else "2nd" if i == 2 else "3rd"
521
+ ranking_text += (
522
+ f"{medal} {i}. {rank['participant']}: "
523
+ f"Score={rank['overall_score']:.3f}, "
524
+ f"TIR={rank['tir']:.1f}%, "
525
+ f"CV={rank['cv']:.1f}%\n"
526
+ )
527
+
528
+ ax_rankings.text(0.5, 0.5, ranking_text, fontsize=11,
529
+ ha='center', va='center', transform=ax_rankings.transAxes,
530
+ family='monospace')
531
+
532
+ # Comparison bars
533
+ ax_comparison = plt.subplot(gs[1, :])
534
+
535
+ algorithms = [r['participant'] for r in battle_report.rankings]
536
+ tir_scores = [r['tir'] for r in battle_report.rankings]
537
+ cv_scores = [r['cv'] for r in battle_report.rankings]
538
+
539
+ x = np.arange(len(algorithms))
540
+ width = 0.35
541
+
542
+ bars1 = ax_comparison.bar(x - width/2, tir_scores, width,
543
+ label='TIR (%)', color='#2196F3')
544
+ bars2 = ax_comparison.bar(x + width/2, cv_scores, width,
545
+ label='CV (%)', color='#4CAF50')
546
+
547
+ ax_comparison.set_ylabel('Percentage')
548
+ ax_comparison.set_title('Algorithm Comparison: TIR vs CV')
549
+ ax_comparison.set_xticks(x)
550
+ ax_comparison.set_xticklabels(algorithms)
551
+ ax_comparison.legend()
552
+ ax_comparison.grid(True, alpha=0.3, axis='y')
553
+
554
+ # Add value labels
555
+ for bar in bars1:
556
+ height = bar.get_height()
557
+ ax_comparison.text(bar.get_x() + bar.get_width()/2., height + 1,
558
+ f'{height:.1f}%', ha='center', fontsize=9)
559
+
560
+ # Detailed metrics table
561
+ ax_table = plt.subplot(gs[2, :])
562
+ ax_table.axis('off')
563
+
564
+ table_data = []
565
+ headers = ['Algorithm', 'TIR', 'TIR Tight', '<70', '>180', 'CV', 'GMI', 'LBGI']
566
+
567
+ for rank in battle_report.rankings:
568
+ table_data.append([
569
+ rank['participant'],
570
+ f"{rank['tir']:.1f}%",
571
+ f"{rank['tir_tight']:.1f}%",
572
+ f"{rank['time_below_70']:.1f}%",
573
+ f"{rank['time_above_180']:.1f}%",
574
+ f"{rank['cv']:.1f}%",
575
+ f"{rank['gmi']:.1f}%",
576
+ f"{rank['lbgi']:.2f}"
577
+ ])
578
+
579
+ table = ax_table.table(
580
+ cellText=table_data,
581
+ colLabels=headers,
582
+ loc='center',
583
+ cellLoc='center'
584
+ )
585
+ table.auto_set_font_size(False)
586
+ table.set_fontsize(10)
587
+ table.scale(1.2, 1.5)
588
+
589
+ plt.tight_layout(rect=(0, 0, 1, 0.95))
590
+
591
+ if save_path:
592
+ plt.savefig(save_path, dpi=self.config.dpi, bbox_inches='tight')
593
+
594
+ return fig
595
+
596
+ def update(self, state: DashboardState):
597
+ """Update dashboard with new state (for real-time mode)"""
598
+ self.state = state
599
+ self.history.append(state.to_dict())
600
+
601
+ def export_state(self) -> Dict:
602
+ """Export current dashboard state"""
603
+ return {
604
+ 'state': self.state.to_dict(),
605
+ 'history': self.history,
606
+ 'timestamp': datetime.now().isoformat()
607
+ }
608
+
609
+
610
+ def demo_clinical_cockpit():
611
+ """Demonstrate clinical cockpit functionality"""
612
+ print("=" * 70)
613
+ print("CLINICAL CONTROL CENTER DEMONSTRATION")
614
+ print("=" * 70)
615
+
616
+ # Generate sample data
617
+ np.random.seed(42)
618
+ n_points = 97
619
+
620
+ timestamps = np.arange(0, n_points * 5, 5)
621
+ glucose = 120 + 30 * np.sin(timestamps / (24 * 12 / (2 * np.pi))) + np.random.normal(0, 15, n_points)
622
+ glucose = np.clip(glucose, 40, 350)
623
+
624
+ simulation_data = pd.DataFrame({
625
+ 'time_minutes': timestamps,
626
+ 'glucose_actual_mgdl': glucose,
627
+ 'delivered_insulin_units': np.random.uniform(0, 1, n_points),
628
+ 'patient_iob_units': np.cumsum(np.random.uniform(0, 0.1, n_points)),
629
+ 'uncertainty': np.random.uniform(0.1, 0.4, n_points)
630
+ })
631
+
632
+ # Create cockpit
633
+ cockpit = ClinicalCockpit()
634
+
635
+ # Create visualization
636
+ print("\n Generating clinical dashboard...")
637
+ predictions = np.roll(glucose, 1)
638
+ predictions[0] = glucose[0]
639
+
640
+ reasoning_logs = [
641
+ {
642
+ 'algorithm': 'PID Controller',
643
+ 'decision': 0.5,
644
+ 'reasons': [
645
+ 'Glucose elevated at 145 mg/dL',
646
+ 'Rising at 1.5 mg/dL/min',
647
+ 'No significant IOB'
648
+ ],
649
+ 'confidence': 0.85
650
+ }
651
+ ]
652
+
653
+ metrics = {
654
+ 'tir_70_180': 72.5,
655
+ 'tir_70_140': 45.2,
656
+ 'tir_below_70': 5.1,
657
+ 'tir_above_180': 22.4,
658
+ 'cv': 32.5,
659
+ 'gmi': 6.8,
660
+ 'lbgi': 2.1,
661
+ 'total_insulin': 45.2
662
+ }
663
+
664
+ personality = {
665
+ 'name': 'PID Controller',
666
+ 'personality': {
667
+ 'aggressiveness': 'Moderate',
668
+ 'hypo_aversion': 'Moderate',
669
+ 'response_speed': 'Fast',
670
+ 'correction_aggressiveness': 'Moderate'
671
+ }
672
+ }
673
+
674
+ fig = cockpit.visualize_results(
675
+ simulation_data=simulation_data,
676
+ predictions=predictions,
677
+ reasoning_logs=reasoning_logs,
678
+ metrics=metrics,
679
+ personality=personality,
680
+ save_path="results/visualization/clinical_cockpit.png"
681
+ )
682
+
683
+ print(" Dashboard saved to: results/visualization/clinical_cockpit.png")
684
+
685
+ print("\n" + "=" * 70)
686
+ print("CLINICAL CONTROL CENTER DEMONSTRATION COMPLETE")
687
+ print("=" * 70)
688
+
689
+
690
+ if __name__ == "__main__":
691
+ demo_clinical_cockpit()