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,289 @@
1
+ timestamp,glucose,carbs,insulin
2
+ 0,115.0,0.0,0.0
3
+ 5,115.2,0.0,0.0
4
+ 10,115.3,0.0,0.0
5
+ 15,115.5,0.0,0.0
6
+ 20,115.7,0.0,0.0
7
+ 25,115.9,0.0,0.0
8
+ 30,116.0,0.0,0.0
9
+ 35,116.2,0.0,0.0
10
+ 40,116.4,0.0,0.0
11
+ 45,116.6,0.0,0.0
12
+ 50,116.7,0.0,0.0
13
+ 55,116.9,0.0,0.0
14
+ 60,144.1,45.0,0.0
15
+ 65,143.5,0.0,0.0
16
+ 70,142.9,0.0,0.0
17
+ 75,142.4,0.0,0.0
18
+ 80,141.9,0.0,0.0
19
+ 85,141.4,0.0,0.0
20
+ 90,140.9,0.0,0.0
21
+ 95,140.5,0.0,0.0
22
+ 100,140.0,0.0,0.0
23
+ 105,139.6,0.0,0.0
24
+ 110,139.1,0.0,0.0
25
+ 115,138.7,0.0,0.0
26
+ 120,138.3,0.0,0.0
27
+ 125,138.0,0.0,0.0
28
+ 130,137.6,0.0,0.0
29
+ 135,137.2,0.0,0.0
30
+ 140,136.9,0.0,0.0
31
+ 145,136.6,0.0,0.0
32
+ 150,136.2,0.0,0.0
33
+ 155,135.9,0.0,0.0
34
+ 160,135.6,0.0,0.0
35
+ 165,135.3,0.0,0.0
36
+ 170,135.1,0.0,0.0
37
+ 175,134.8,0.0,0.0
38
+ 180,134.5,0.0,0.0
39
+ 185,134.3,0.0,0.0
40
+ 190,134.0,0.0,0.0
41
+ 195,133.8,0.0,0.0
42
+ 200,133.5,0.0,0.0
43
+ 205,133.3,0.0,0.0
44
+ 210,133.1,0.0,0.0
45
+ 215,132.9,0.0,0.0
46
+ 220,132.7,0.0,0.0
47
+ 225,132.4,0.0,0.0
48
+ 230,132.2,0.0,0.0
49
+ 235,132.1,0.0,0.0
50
+ 240,131.9,0.0,0.0
51
+ 245,131.7,0.0,0.0
52
+ 250,131.5,0.0,0.0
53
+ 255,131.3,0.0,0.0
54
+ 260,131.1,0.0,0.0
55
+ 265,131.0,0.0,0.0
56
+ 270,130.8,0.0,0.0
57
+ 275,130.6,0.0,0.0
58
+ 280,130.5,0.0,0.0
59
+ 285,130.3,0.0,0.0
60
+ 290,130.2,0.0,0.0
61
+ 295,130.0,0.0,0.0
62
+ 300,129.8,0.0,0.0
63
+ 305,129.7,0.0,0.0
64
+ 310,129.5,0.0,0.0
65
+ 315,129.4,0.0,0.0
66
+ 320,129.2,0.0,0.0
67
+ 325,129.1,0.0,0.0
68
+ 330,129.0,0.0,0.0
69
+ 335,128.8,0.0,0.0
70
+ 340,128.7,0.0,0.0
71
+ 345,128.5,0.0,0.0
72
+ 350,128.4,0.0,0.0
73
+ 355,128.2,0.0,0.0
74
+ 360,164.1,60.0,0.0
75
+ 365,163.0,0.0,0.0
76
+ 370,161.9,0.0,0.0
77
+ 375,160.8,0.0,0.0
78
+ 380,159.7,0.0,0.0
79
+ 385,158.7,0.0,0.0
80
+ 390,157.7,0.0,0.0
81
+ 395,156.7,0.0,0.0
82
+ 400,155.8,0.0,0.0
83
+ 405,154.9,0.0,0.0
84
+ 410,153.9,0.0,0.0
85
+ 415,153.0,0.0,0.0
86
+ 420,152.2,0.0,0.0
87
+ 425,151.3,0.0,0.0
88
+ 430,150.5,0.0,0.0
89
+ 435,149.7,0.0,0.0
90
+ 440,148.9,0.0,0.0
91
+ 445,148.1,0.0,0.0
92
+ 450,147.3,0.0,0.0
93
+ 455,146.6,0.0,0.0
94
+ 460,145.8,0.0,0.0
95
+ 465,145.1,0.0,0.0
96
+ 470,144.4,0.0,0.0
97
+ 475,143.7,0.0,0.0
98
+ 480,143.0,0.0,0.0
99
+ 485,142.4,0.0,0.0
100
+ 490,141.7,0.0,0.0
101
+ 495,141.1,0.0,0.0
102
+ 500,140.4,0.0,0.0
103
+ 505,139.8,0.0,0.0
104
+ 510,139.2,0.0,0.0
105
+ 515,138.6,0.0,0.0
106
+ 520,138.0,0.0,0.0
107
+ 525,137.4,0.0,0.0
108
+ 530,136.9,0.0,0.0
109
+ 535,136.3,0.0,0.0
110
+ 540,135.8,0.0,0.0
111
+ 545,135.2,0.0,0.0
112
+ 550,134.7,0.0,0.0
113
+ 555,134.2,0.0,0.0
114
+ 560,133.7,0.0,0.0
115
+ 565,133.2,0.0,0.0
116
+ 570,132.7,0.0,0.0
117
+ 575,132.2,0.0,0.0
118
+ 580,131.7,0.0,0.0
119
+ 585,131.2,0.0,0.0
120
+ 590,130.8,0.0,0.0
121
+ 595,130.3,0.0,0.0
122
+ 600,129.8,0.0,0.0
123
+ 605,129.4,0.0,0.0
124
+ 610,128.9,0.0,0.0
125
+ 615,128.5,0.0,0.0
126
+ 620,128.1,0.0,0.0
127
+ 625,127.7,0.0,0.0
128
+ 630,127.2,0.0,0.0
129
+ 635,126.8,0.0,0.0
130
+ 640,126.4,0.0,0.0
131
+ 645,126.0,0.0,0.0
132
+ 650,125.6,0.0,0.0
133
+ 655,125.2,0.0,0.0
134
+ 660,124.8,0.0,0.0
135
+ 665,124.5,0.0,0.0
136
+ 670,124.1,0.0,0.0
137
+ 675,123.7,0.0,0.0
138
+ 680,123.3,0.0,0.0
139
+ 685,123.0,0.0,0.0
140
+ 690,122.6,0.0,0.0
141
+ 695,122.3,0.0,0.0
142
+ 700,121.9,0.0,0.0
143
+ 705,121.6,0.0,0.0
144
+ 710,121.2,0.0,0.0
145
+ 715,120.9,0.0,0.0
146
+ 720,162.6,70.0,0.0
147
+ 725,161.1,0.0,0.0
148
+ 730,159.6,0.0,0.0
149
+ 735,158.2,0.0,0.0
150
+ 740,156.9,0.0,0.0
151
+ 745,155.5,0.0,0.0
152
+ 750,154.2,0.0,0.0
153
+ 755,152.9,0.0,0.0
154
+ 760,151.7,0.0,0.0
155
+ 765,150.5,0.0,0.0
156
+ 770,149.3,0.0,0.0
157
+ 775,148.1,0.0,0.0
158
+ 780,147.0,0.0,0.0
159
+ 785,145.9,0.0,0.0
160
+ 790,144.8,0.0,0.0
161
+ 795,143.8,0.0,0.0
162
+ 800,142.8,0.0,0.0
163
+ 805,141.8,0.0,0.0
164
+ 810,140.8,0.0,0.0
165
+ 815,139.8,0.0,0.0
166
+ 820,138.9,0.0,0.0
167
+ 825,138.0,0.0,0.0
168
+ 830,137.1,0.0,0.0
169
+ 835,136.3,0.0,0.0
170
+ 840,135.4,0.0,0.0
171
+ 845,134.6,0.0,0.0
172
+ 850,133.8,0.0,0.0
173
+ 855,133.0,0.0,0.0
174
+ 860,132.3,0.0,0.0
175
+ 865,131.5,0.0,0.0
176
+ 870,130.8,0.0,0.0
177
+ 875,130.1,0.0,0.0
178
+ 880,129.4,0.0,0.0
179
+ 885,128.7,0.0,0.0
180
+ 890,128.1,0.0,0.0
181
+ 895,127.5,0.0,0.0
182
+ 900,126.8,0.0,0.0
183
+ 905,126.2,0.0,0.0
184
+ 910,125.7,0.0,0.0
185
+ 915,125.1,0.0,0.0
186
+ 920,124.5,0.0,0.0
187
+ 925,124.0,0.0,0.0
188
+ 930,123.5,0.0,0.0
189
+ 935,123.0,0.0,0.0
190
+ 940,122.5,0.0,0.0
191
+ 945,122.0,0.0,0.0
192
+ 950,121.5,0.0,0.0
193
+ 955,121.1,0.0,0.0
194
+ 960,120.6,0.0,0.0
195
+ 965,120.2,0.0,0.0
196
+ 970,119.8,0.0,0.0
197
+ 975,119.4,0.0,0.0
198
+ 980,119.0,0.0,0.0
199
+ 985,118.6,0.0,0.0
200
+ 990,118.2,0.0,0.0
201
+ 995,117.9,0.0,0.0
202
+ 1000,117.5,0.0,0.0
203
+ 1005,117.2,0.0,0.0
204
+ 1010,116.9,0.0,0.0
205
+ 1015,116.6,0.0,0.0
206
+ 1020,116.3,0.0,0.0
207
+ 1025,116.0,0.0,0.0
208
+ 1030,115.7,0.0,0.0
209
+ 1035,115.4,0.0,0.0
210
+ 1040,115.2,0.0,0.0
211
+ 1045,114.9,0.0,0.0
212
+ 1050,114.7,0.0,0.0
213
+ 1055,114.4,0.0,0.0
214
+ 1060,114.2,0.0,0.0
215
+ 1065,114.0,0.0,0.0
216
+ 1070,113.8,0.0,0.0
217
+ 1075,113.6,0.0,0.0
218
+ 1080,113.4,0.0,0.0
219
+ 1085,113.3,0.0,0.0
220
+ 1090,113.1,0.0,0.0
221
+ 1095,112.9,0.0,0.0
222
+ 1100,112.8,0.0,0.0
223
+ 1105,112.6,0.0,0.0
224
+ 1110,112.5,0.0,0.0
225
+ 1115,112.4,0.0,0.0
226
+ 1120,112.3,0.0,0.0
227
+ 1125,112.2,0.0,0.0
228
+ 1130,112.1,0.0,0.0
229
+ 1135,112.0,0.0,0.0
230
+ 1140,111.9,0.0,0.0
231
+ 1145,111.8,0.0,0.0
232
+ 1150,111.7,0.0,0.0
233
+ 1155,111.7,0.0,0.0
234
+ 1160,111.6,0.0,0.0
235
+ 1165,111.6,0.0,0.0
236
+ 1170,111.5,0.0,0.0
237
+ 1175,111.5,0.0,0.0
238
+ 1180,111.4,0.0,0.0
239
+ 1185,111.4,0.0,0.0
240
+ 1190,111.4,0.0,0.0
241
+ 1195,111.4,0.0,0.0
242
+ 1200,96.4,0.0,0.0
243
+ 1205,96.4,0.0,0.0
244
+ 1210,96.4,0.0,0.0
245
+ 1215,96.4,0.0,0.0
246
+ 1220,96.4,0.0,0.0
247
+ 1225,96.4,0.0,0.0
248
+ 1230,96.5,0.0,0.0
249
+ 1235,96.5,0.0,0.0
250
+ 1240,96.5,0.0,0.0
251
+ 1245,96.6,0.0,0.0
252
+ 1250,96.6,0.0,0.0
253
+ 1255,96.7,0.0,0.0
254
+ 1260,96.7,0.0,0.0
255
+ 1265,96.8,0.0,0.0
256
+ 1270,96.8,0.0,0.0
257
+ 1275,96.9,0.0,0.0
258
+ 1280,97.0,0.0,0.0
259
+ 1285,97.1,0.0,0.0
260
+ 1290,97.1,0.0,0.0
261
+ 1295,97.2,0.0,0.0
262
+ 1300,97.3,0.0,0.0
263
+ 1305,97.4,0.0,0.0
264
+ 1310,97.5,0.0,0.0
265
+ 1315,97.6,0.0,0.0
266
+ 1320,97.7,0.0,0.0
267
+ 1325,112.8,0.0,0.0
268
+ 1330,112.9,0.0,0.0
269
+ 1335,113.0,0.0,0.0
270
+ 1340,113.1,0.0,0.0
271
+ 1345,113.3,0.0,0.0
272
+ 1350,113.4,0.0,0.0
273
+ 1355,113.5,0.0,0.0
274
+ 1360,113.6,0.0,0.0
275
+ 1365,113.7,0.0,0.0
276
+ 1370,113.9,0.0,0.0
277
+ 1375,114.0,0.0,0.0
278
+ 1380,114.1,0.0,0.0
279
+ 1385,114.3,0.0,0.0
280
+ 1390,114.4,0.0,0.0
281
+ 1395,114.6,0.0,0.0
282
+ 1400,114.7,0.0,0.0
283
+ 1405,114.8,0.0,0.0
284
+ 1410,115.0,0.0,0.0
285
+ 1415,115.1,0.0,0.0
286
+ 1420,115.3,0.0,0.0
287
+ 1425,115.4,0.0,0.0
288
+ 1430,115.6,0.0,0.0
289
+ 1435,115.7,0.0,0.0
iints/data/importer.py ADDED
@@ -0,0 +1,275 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from pathlib import Path
5
+ from typing import Any, Dict, Iterable, List, Optional, Tuple, Union
6
+ import io
7
+ import re
8
+ import sys
9
+
10
+ import pandas as pd
11
+
12
+ from iints.data.ingestor import DataIngestor
13
+
14
+
15
+ def _normalize_column(name: str) -> str:
16
+ return re.sub(r"[^a-z0-9]+", "", name.lower())
17
+
18
+
19
+ def _find_column(columns: Iterable[str], candidates: Iterable[str]) -> Optional[str]:
20
+ normalized = {col: _normalize_column(col) for col in columns}
21
+ candidate_set = {_normalize_column(c) for c in candidates}
22
+ for col, norm in normalized.items():
23
+ if norm in candidate_set:
24
+ return col
25
+ return None
26
+
27
+
28
+ DEFAULT_MAPPINGS: Dict[str, Dict[str, List[str]]] = {
29
+ "generic": {
30
+ "timestamp": ["timestamp", "time", "datetime", "date", "eventtime", "device timestamp"],
31
+ "glucose": ["glucose", "bg", "sgv", "sensorglucose", "glucosemgdl", "glucosevalue"],
32
+ "carbs": ["carbs", "carb", "carbohydrates", "carbsg", "carbgrams"],
33
+ "insulin": ["insulin", "insulinunits", "bolus", "basal", "totalinsulin"],
34
+ },
35
+ "dexcom": {
36
+ "timestamp": ["timestamp", "eventtime", "device timestamp"],
37
+ "glucose": ["glucose", "glucosevalue", "sgv", "sensorglucose"],
38
+ "carbs": ["carbs", "carb", "carbohydrates"],
39
+ "insulin": ["insulin", "insulinunits", "bolus", "basal"],
40
+ },
41
+ "libre": {
42
+ "timestamp": ["timestamp", "device timestamp", "datetime", "time"],
43
+ "glucose": ["glucose", "glucosevalue", "sensorglucose", "sgv"],
44
+ "carbs": ["carbs", "carb", "carbohydrates"],
45
+ "insulin": ["insulin", "insulinunits", "bolus", "basal"],
46
+ },
47
+ }
48
+
49
+ IMPORT_FORMAT_SCHEMAS: Dict[str, Dict[str, List[str]]] = {
50
+ "generic": {
51
+ "required": ["timestamp", "glucose"],
52
+ "optional": ["carbs", "insulin"],
53
+ },
54
+ "dexcom": {
55
+ "required": ["timestamp", "glucose"],
56
+ "optional": ["carbs", "insulin"],
57
+ },
58
+ "libre": {
59
+ "required": ["timestamp", "glucose"],
60
+ "optional": ["carbs", "insulin"],
61
+ },
62
+ }
63
+
64
+
65
+ @dataclass
66
+ class ImportResult:
67
+ dataframe: pd.DataFrame
68
+ scenario: Dict[str, Any]
69
+
70
+
71
+ def guess_column_mapping(columns: Iterable[str], data_format: str = "generic") -> Dict[str, Optional[str]]:
72
+ candidates = DEFAULT_MAPPINGS.get(data_format, DEFAULT_MAPPINGS["generic"])
73
+ return {
74
+ "timestamp": _find_column(columns, candidates.get("timestamp", [])),
75
+ "glucose": _find_column(columns, candidates.get("glucose", [])),
76
+ "carbs": _find_column(columns, candidates.get("carbs", [])),
77
+ "insulin": _find_column(columns, candidates.get("insulin", [])),
78
+ }
79
+
80
+
81
+ def validate_import_schema(
82
+ columns: Iterable[str],
83
+ data_format: str,
84
+ column_map: Optional[Dict[str, str]] = None,
85
+ ) -> None:
86
+ schema = IMPORT_FORMAT_SCHEMAS.get(data_format, IMPORT_FORMAT_SCHEMAS["generic"])
87
+ candidates = DEFAULT_MAPPINGS.get(data_format, DEFAULT_MAPPINGS["generic"])
88
+ mapping = column_map or {}
89
+
90
+ missing: List[str] = []
91
+ for key in schema["required"]:
92
+ if key in mapping:
93
+ continue
94
+ found = _find_column(columns, candidates.get(key, []))
95
+ if found is None:
96
+ missing.append(key)
97
+
98
+ if missing:
99
+ raise ValueError(
100
+ f"Missing required columns for format '{data_format}': {', '.join(missing)}. "
101
+ f"Columns: {list(columns)}"
102
+ )
103
+
104
+
105
+ def import_cgm_dataframe(
106
+ df: pd.DataFrame,
107
+ data_format: str = "generic",
108
+ column_map: Optional[Dict[str, str]] = None,
109
+ time_unit: str = "minutes",
110
+ source: Optional[str] = None,
111
+ ) -> pd.DataFrame:
112
+ """
113
+ Import CGM data from an in-memory DataFrame into the universal IINTS schema.
114
+ """
115
+ columns = list(df.columns)
116
+ mapping = column_map or {}
117
+ mapping = {k: v for k, v in mapping.items() if v}
118
+
119
+ candidates = DEFAULT_MAPPINGS.get(data_format, DEFAULT_MAPPINGS["generic"])
120
+
121
+ validate_import_schema(columns, data_format=data_format, column_map=mapping)
122
+
123
+ def resolve(key: str, required: bool = True) -> Optional[str]:
124
+ if key in mapping:
125
+ return mapping[key]
126
+ col = _find_column(columns, candidates.get(key, []))
127
+ if required and col is None:
128
+ raise ValueError(f"Missing required column for '{key}'. Columns: {columns}")
129
+ return col
130
+
131
+ ts_col = resolve("timestamp", required=True)
132
+ glucose_col = resolve("glucose", required=True)
133
+ carbs_col = resolve("carbs", required=False)
134
+ insulin_col = resolve("insulin", required=False)
135
+
136
+ df = df.rename(
137
+ columns={
138
+ ts_col: "timestamp",
139
+ glucose_col: "glucose",
140
+ carbs_col: "carbs",
141
+ insulin_col: "insulin",
142
+ }
143
+ )
144
+
145
+ if "carbs" not in df.columns:
146
+ df["carbs"] = 0.0
147
+ if "insulin" not in df.columns:
148
+ df["insulin"] = 0.0
149
+
150
+ # Parse timestamps
151
+ if pd.api.types.is_numeric_dtype(df["timestamp"]):
152
+ # Assume numeric (minutes or seconds)
153
+ if time_unit == "seconds":
154
+ df["timestamp"] = df["timestamp"].astype(float) / 60.0
155
+ else:
156
+ df["timestamp"] = df["timestamp"].astype(float)
157
+ elif pd.api.types.is_datetime64_any_dtype(df["timestamp"]):
158
+ ts = df["timestamp"]
159
+ df["timestamp"] = (ts - ts.iloc[0]).dt.total_seconds() / 60.0
160
+ else:
161
+ # Try datetime parsing, fallback to numeric
162
+ ts = pd.to_datetime(df["timestamp"], errors="coerce")
163
+ if ts.isna().all():
164
+ if time_unit == "seconds":
165
+ df["timestamp"] = df["timestamp"].astype(float) / 60.0
166
+ else:
167
+ df["timestamp"] = df["timestamp"].astype(float)
168
+ else:
169
+ df["timestamp"] = (ts - ts.iloc[0]).dt.total_seconds() / 60.0
170
+
171
+ df["source"] = source or data_format
172
+ ingestor = DataIngestor()
173
+ ingestor._validate_schema(df, ingestor.UNIVERSAL_SCHEMA)
174
+ return df[list(ingestor.UNIVERSAL_SCHEMA.keys())]
175
+
176
+
177
+ def import_cgm_csv(
178
+ path: Union[str, Path],
179
+ data_format: str = "generic",
180
+ column_map: Optional[Dict[str, str]] = None,
181
+ time_unit: str = "minutes",
182
+ source: Optional[str] = None,
183
+ ) -> pd.DataFrame:
184
+ """
185
+ Import CGM data from CSV into the universal IINTS schema.
186
+ """
187
+ df = pd.read_csv(path)
188
+ return import_cgm_dataframe(
189
+ df,
190
+ data_format=data_format,
191
+ column_map=column_map,
192
+ time_unit=time_unit,
193
+ source=source,
194
+ )
195
+
196
+
197
+ def scenario_from_dataframe(
198
+ df: pd.DataFrame,
199
+ scenario_name: str,
200
+ scenario_version: str = "1.0",
201
+ description: str = "Imported CGM scenario",
202
+ carb_threshold: float = 0.1,
203
+ absorption_delay_minutes: int = 10,
204
+ duration_minutes: int = 60,
205
+ ) -> Dict[str, Any]:
206
+ stress_events = []
207
+ if "carbs" in df.columns:
208
+ for _, row in df[df["carbs"] > carb_threshold].iterrows():
209
+ stress_events.append(
210
+ {
211
+ "start_time": int(row["timestamp"]),
212
+ "event_type": "meal",
213
+ "value": float(row["carbs"]),
214
+ "absorption_delay_minutes": absorption_delay_minutes,
215
+ "duration": duration_minutes,
216
+ }
217
+ )
218
+
219
+ return {
220
+ "scenario_name": scenario_name,
221
+ "scenario_version": scenario_version,
222
+ "description": description,
223
+ "stress_events": stress_events,
224
+ }
225
+
226
+
227
+ def scenario_from_csv(
228
+ path: Union[str, Path],
229
+ scenario_name: str = "Imported CGM Scenario",
230
+ scenario_version: str = "1.0",
231
+ data_format: str = "generic",
232
+ column_map: Optional[Dict[str, str]] = None,
233
+ time_unit: str = "minutes",
234
+ carb_threshold: float = 0.1,
235
+ ) -> ImportResult:
236
+ df = import_cgm_csv(
237
+ path,
238
+ data_format=data_format,
239
+ column_map=column_map,
240
+ time_unit=time_unit,
241
+ )
242
+ scenario = scenario_from_dataframe(
243
+ df,
244
+ scenario_name=scenario_name,
245
+ scenario_version=scenario_version,
246
+ carb_threshold=carb_threshold,
247
+ )
248
+ return ImportResult(dataframe=df, scenario=scenario)
249
+
250
+
251
+ def export_standard_csv(df: pd.DataFrame, output_path: Union[str, Path]) -> str:
252
+ output_path = Path(output_path)
253
+ output_path.parent.mkdir(parents=True, exist_ok=True)
254
+ df.to_csv(output_path, index=False)
255
+ return str(output_path)
256
+
257
+
258
+ def _read_demo_csv_text() -> str:
259
+ if sys.version_info >= (3, 9):
260
+ from importlib.resources import files
261
+ return files("iints.data.demo").joinpath("demo_cgm.csv").read_text()
262
+ from importlib import resources
263
+ return resources.read_text("iints.data.demo", "demo_cgm.csv")
264
+
265
+
266
+ def load_demo_dataframe() -> pd.DataFrame:
267
+ text = _read_demo_csv_text()
268
+ return pd.read_csv(io.StringIO(text))
269
+
270
+
271
+ def export_demo_csv(output_path: Union[str, Path]) -> str:
272
+ output_path = Path(output_path)
273
+ output_path.parent.mkdir(parents=True, exist_ok=True)
274
+ output_path.write_text(_read_demo_csv_text())
275
+ return str(output_path)