iints-sdk-python35 0.1.17__tar.gz → 0.1.19__tar.gz

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 (128) hide show
  1. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/PKG-INFO +4 -1
  2. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/README.md +1 -0
  3. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/pyproject.toml +3 -1
  4. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/__init__.py +14 -2
  5. iints_sdk_python35-0.1.19/src/iints/analysis/edge_efficiency.py +33 -0
  6. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/analysis/population_report.py +3 -2
  7. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/analysis/safety_index.py +7 -6
  8. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/cli/cli.py +267 -4
  9. iints_sdk_python35-0.1.19/src/iints/core/algorithms/mock_algorithms.py +165 -0
  10. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/core/devices/models.py +5 -0
  11. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/core/patient/models.py +5 -2
  12. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/core/patient/patient_factory.py +23 -4
  13. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/core/patient/profile.py +1 -1
  14. iints_sdk_python35-0.1.19/src/iints/core/safety/__init__.py +12 -0
  15. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/core/safety/config.py +5 -0
  16. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/core/safety/input_validator.py +1 -1
  17. iints_sdk_python35-0.1.19/src/iints/core/safety/supervisor.py +39 -0
  18. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/core/simulator.py +117 -13
  19. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/core/supervisor.py +51 -9
  20. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/highlevel.py +57 -5
  21. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/population/generator.py +2 -0
  22. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/population/runner.py +4 -3
  23. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/research/__init__.py +3 -0
  24. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/research/config.py +26 -1
  25. iints_sdk_python35-0.1.19/src/iints/research/losses.py +98 -0
  26. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/research/predictor.py +0 -6
  27. iints_sdk_python35-0.1.19/src/iints/templates/scenarios/chaos_insulin_stacking.json +29 -0
  28. iints_sdk_python35-0.1.19/src/iints/templates/scenarios/chaos_runaway_ai.json +25 -0
  29. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints_sdk_python35.egg-info/PKG-INFO +4 -1
  30. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints_sdk_python35.egg-info/SOURCES.txt +4 -0
  31. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints_sdk_python35.egg-info/requires.txt +2 -0
  32. iints_sdk_python35-0.1.17/src/iints/core/algorithms/mock_algorithms.py +0 -69
  33. iints_sdk_python35-0.1.17/src/iints/core/safety/__init__.py +0 -5
  34. iints_sdk_python35-0.1.17/src/iints/core/safety/supervisor.py +0 -33
  35. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/LICENSE +0 -0
  36. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/setup.cfg +0 -0
  37. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/analysis/__init__.py +0 -0
  38. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/analysis/algorithm_xray.py +0 -0
  39. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/analysis/baseline.py +0 -0
  40. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/analysis/clinical_benchmark.py +0 -0
  41. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/analysis/clinical_metrics.py +0 -0
  42. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/analysis/clinical_tir_analyzer.py +0 -0
  43. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/analysis/diabetes_metrics.py +0 -0
  44. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/analysis/edge_performance_monitor.py +0 -0
  45. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/analysis/explainability.py +0 -0
  46. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/analysis/explainable_ai.py +0 -0
  47. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/analysis/hardware_benchmark.py +0 -0
  48. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/analysis/metrics.py +0 -0
  49. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/analysis/reporting.py +0 -0
  50. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/analysis/sensor_filtering.py +0 -0
  51. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/analysis/validator.py +0 -0
  52. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/api/__init__.py +0 -0
  53. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/api/base_algorithm.py +0 -0
  54. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/api/registry.py +0 -0
  55. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/api/template_algorithm.py +0 -0
  56. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/assets/iints_logo.png +0 -0
  57. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/cli/__init__.py +0 -0
  58. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/core/__init__.py +0 -0
  59. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/core/algorithms/__init__.py +0 -0
  60. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/core/algorithms/battle_runner.py +0 -0
  61. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/core/algorithms/correction_bolus.py +0 -0
  62. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/core/algorithms/discovery.py +0 -0
  63. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/core/algorithms/fixed_basal_bolus.py +0 -0
  64. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/core/algorithms/hybrid_algorithm.py +0 -0
  65. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/core/algorithms/lstm_algorithm.py +0 -0
  66. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/core/algorithms/pid_controller.py +0 -0
  67. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/core/algorithms/standard_pump_algo.py +0 -0
  68. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/core/device.py +0 -0
  69. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/core/device_manager.py +0 -0
  70. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/core/devices/__init__.py +0 -0
  71. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/core/patient/__init__.py +0 -0
  72. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/core/patient/bergman_model.py +0 -0
  73. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/core/simulation/__init__.py +0 -0
  74. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/core/simulation/scenario_parser.py +0 -0
  75. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/data/__init__.py +0 -0
  76. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/data/adapter.py +0 -0
  77. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/data/column_mapper.py +0 -0
  78. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/data/datasets.json +0 -0
  79. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/data/demo/__init__.py +0 -0
  80. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/data/demo/demo_cgm.csv +0 -0
  81. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/data/importer.py +0 -0
  82. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/data/ingestor.py +0 -0
  83. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/data/nightscout.py +0 -0
  84. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/data/quality_checker.py +0 -0
  85. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/data/registry.py +0 -0
  86. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/data/tidepool.py +0 -0
  87. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/data/universal_parser.py +0 -0
  88. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/data/virtual_patients/clinic_safe_baseline.yaml +0 -0
  89. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/data/virtual_patients/clinic_safe_hyper_challenge.yaml +0 -0
  90. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/data/virtual_patients/clinic_safe_hypo_prone.yaml +0 -0
  91. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/data/virtual_patients/clinic_safe_midnight.yaml +0 -0
  92. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/data/virtual_patients/clinic_safe_pizza.yaml +0 -0
  93. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/data/virtual_patients/clinic_safe_stress_meal.yaml +0 -0
  94. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/data/virtual_patients/default_patient.yaml +0 -0
  95. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/data/virtual_patients/patient_559_config.yaml +0 -0
  96. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/emulation/__init__.py +0 -0
  97. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/emulation/legacy_base.py +0 -0
  98. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/emulation/medtronic_780g.py +0 -0
  99. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/emulation/omnipod_5.py +0 -0
  100. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/emulation/tandem_controliq.py +0 -0
  101. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/learning/__init__.py +0 -0
  102. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/learning/autonomous_optimizer.py +0 -0
  103. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/learning/learning_system.py +0 -0
  104. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/metrics.py +0 -0
  105. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/population/__init__.py +0 -0
  106. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/presets/__init__.py +0 -0
  107. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/presets/presets.json +0 -0
  108. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/research/dataset.py +0 -0
  109. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/scenarios/__init__.py +0 -0
  110. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/scenarios/generator.py +0 -0
  111. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/templates/__init__.py +0 -0
  112. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/templates/default_algorithm.py +0 -0
  113. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/templates/scenarios/__init__.py +0 -0
  114. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/templates/scenarios/example_scenario.json +0 -0
  115. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/templates/scenarios/exercise_stress.json +0 -0
  116. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/utils/__init__.py +0 -0
  117. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/utils/plotting.py +0 -0
  118. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/utils/run_io.py +0 -0
  119. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/validation/__init__.py +0 -0
  120. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/validation/schemas.py +0 -0
  121. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/visualization/__init__.py +0 -0
  122. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/visualization/cockpit.py +0 -0
  123. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints/visualization/uncertainty_cloud.py +0 -0
  124. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints_sdk_python35.egg-info/dependency_links.txt +0 -0
  125. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints_sdk_python35.egg-info/entry_points.txt +0 -0
  126. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/src/iints_sdk_python35.egg-info/top_level.txt +0 -0
  127. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/tests/test_bergman.py +0 -0
  128. {iints_sdk_python35-0.1.17 → iints_sdk_python35-0.1.19}/tests/test_population.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: iints-sdk-python35
3
- Version: 0.1.17
3
+ Version: 0.1.19
4
4
  Summary: A pre-clinical Edge-AI SDK for diabetes management validation.
5
5
  Author-email: Rune Bobbaers <rune.bobbaers@gmail.com>
6
6
  Project-URL: Homepage, https://github.com/python35/IINTS-SDK
@@ -42,6 +42,8 @@ Provides-Extra: research
42
42
  Requires-Dist: torch>=2.0.0; extra == "research"
43
43
  Requires-Dist: pyarrow>=12.0.0; extra == "research"
44
44
  Requires-Dist: h5py>=3.10.0; extra == "research"
45
+ Requires-Dist: onnx>=1.16.0; extra == "research"
46
+ Requires-Dist: onnxscript>=0.1.0; extra == "research"
45
47
  Dynamic: license-file
46
48
 
47
49
  # IINTS-AF SDK
@@ -50,6 +52,7 @@ Dynamic: license-file
50
52
  [![Python Package CI](https://github.com/python35/IINTS-SDK/actions/workflows/python-package.yml/badge.svg)](https://github.com/python35/IINTS-SDK/actions/workflows/python-package.yml)
51
53
  [![Coverage](https://raw.githubusercontent.com/python35/IINTS-SDK/main/badges/coverage.svg)](https://github.com/python35/IINTS-SDK/actions/workflows/health-badges.yml)
52
54
  [![Docs Coverage](https://raw.githubusercontent.com/python35/IINTS-SDK/main/badges/docs.svg)](https://github.com/python35/IINTS-SDK/actions/workflows/health-badges.yml)
55
+ [![Site](https://img.shields.io/badge/site-IINTS--AF-0a66c2?style=flat&logo=firefox-browser&logoColor=white)](https://python35.github.io/IINTS-Site/index.html)
53
56
 
54
57
  <div style="text-align:center;">
55
58
  <img src="Ontwerp zonder titel.png" alt="" style="display:block; margin:0 auto;">
@@ -4,6 +4,7 @@
4
4
  [![Python Package CI](https://github.com/python35/IINTS-SDK/actions/workflows/python-package.yml/badge.svg)](https://github.com/python35/IINTS-SDK/actions/workflows/python-package.yml)
5
5
  [![Coverage](https://raw.githubusercontent.com/python35/IINTS-SDK/main/badges/coverage.svg)](https://github.com/python35/IINTS-SDK/actions/workflows/health-badges.yml)
6
6
  [![Docs Coverage](https://raw.githubusercontent.com/python35/IINTS-SDK/main/badges/docs.svg)](https://github.com/python35/IINTS-SDK/actions/workflows/health-badges.yml)
7
+ [![Site](https://img.shields.io/badge/site-IINTS--AF-0a66c2?style=flat&logo=firefox-browser&logoColor=white)](https://python35.github.io/IINTS-Site/index.html)
7
8
 
8
9
  <div style="text-align:center;">
9
10
  <img src="Ontwerp zonder titel.png" alt="" style="display:block; margin:0 auto;">
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "iints-sdk-python35"
7
- version = "0.1.17"
7
+ version = "0.1.19"
8
8
  authors = [
9
9
  { name="Rune Bobbaers", email="rune.bobbaers@gmail.com" },
10
10
  ]
@@ -55,6 +55,8 @@ research = [
55
55
  "torch>=2.0.0",
56
56
  "pyarrow>=12.0.0",
57
57
  "h5py>=3.10.0",
58
+ "onnx>=1.16.0",
59
+ "onnxscript>=0.1.0",
58
60
  ]
59
61
 
60
62
  [project.scripts]
@@ -3,7 +3,7 @@
3
3
  import pandas as pd # Required for type hints like pd.DataFrame
4
4
  from typing import Optional
5
5
 
6
- __version__ = "0.1.17"
6
+ __version__ = "0.1.19"
7
7
 
8
8
  # Note to developers: this SDK is currently maintained by a single author.
9
9
  # Please report bugs via GitHub issues and feel free to contribute fixes via PRs.
@@ -20,6 +20,7 @@ from .api.base_algorithm import (
20
20
  # Core Simulation Components
21
21
  from .core.simulator import Simulator, StressEvent, SimulationLimitError
22
22
  from .core.patient.models import PatientModel
23
+ from .core.patient.patient_factory import PatientFactory
23
24
  from .core.patient.profile import PatientProfile
24
25
  try:
25
26
  from .core.device_manager import DeviceManager
@@ -33,7 +34,12 @@ except Exception: # pragma: no cover - fallback if torch/device manager import
33
34
  from .core.safety import SafetyConfig, SafetySupervisor
34
35
  from .core.devices.models import SensorModel, PumpModel
35
36
  from .core.algorithms.standard_pump_algo import StandardPumpAlgorithm
36
- from .core.algorithms.mock_algorithms import ConstantDoseAlgorithm, RandomDoseAlgorithm
37
+ from .core.algorithms.mock_algorithms import (
38
+ ConstantDoseAlgorithm,
39
+ RandomDoseAlgorithm,
40
+ RunawayAIAlgorithm,
41
+ StackingAIAlgorithm,
42
+ )
37
43
 
38
44
  # Data Handling
39
45
  from .data.ingestor import DataIngestor
@@ -52,6 +58,7 @@ from .data.nightscout import NightscoutConfig, import_nightscout
52
58
  from .data.tidepool import TidepoolClient, load_openapi_spec
53
59
  from .analysis.metrics import generate_benchmark_metrics # Added for benchmark
54
60
  from .analysis.reporting import ClinicalReportGenerator
61
+ from .analysis.edge_efficiency import EnergyEstimate, estimate_energy_per_decision
55
62
  from .highlevel import run_simulation, run_full, run_population
56
63
  from .scenarios import ScenarioGeneratorConfig, generate_random_scenario
57
64
 
@@ -124,6 +131,7 @@ __all__ = [
124
131
  "InsulinAlgorithm", "AlgorithmInput", "AlgorithmResult", "AlgorithmMetadata", "WhyLogEntry",
125
132
  # Core
126
133
  "Simulator", "StressEvent", "PatientModel", "DeviceManager",
134
+ "PatientFactory",
127
135
  "PatientProfile",
128
136
  "SimulationLimitError",
129
137
  "SafetySupervisor",
@@ -133,6 +141,8 @@ __all__ = [
133
141
  "StandardPumpAlgorithm",
134
142
  "ConstantDoseAlgorithm",
135
143
  "RandomDoseAlgorithm",
144
+ "RunawayAIAlgorithm",
145
+ "StackingAIAlgorithm",
136
146
  # Data
137
147
  "DataIngestor",
138
148
  "ImportResult",
@@ -151,6 +161,8 @@ __all__ = [
151
161
  # Analysis Metrics
152
162
  "generate_benchmark_metrics",
153
163
  "ClinicalReportGenerator",
164
+ "EnergyEstimate",
165
+ "estimate_energy_per_decision",
154
166
  # Reporting
155
167
  "generate_report",
156
168
  "generate_quickstart_report",
@@ -0,0 +1,33 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+
5
+
6
+ @dataclass(frozen=True)
7
+ class EnergyEstimate:
8
+ energy_joules: float
9
+ energy_microjoules: float
10
+ decisions_per_day: int
11
+ energy_joules_per_day: float
12
+ energy_millijoules_per_day: float
13
+
14
+
15
+ def estimate_energy_per_decision(power_watts: float, latency_ms: float, decisions_per_day: int = 288) -> EnergyEstimate:
16
+ """
17
+ Estimate energy cost per decision on any device.
18
+
19
+ Energy (J) = Power (W) * Time (s)
20
+
21
+ decisions_per_day default: 288 (5-minute control loop)
22
+ """
23
+ latency_s = max(latency_ms, 0.0) / 1000.0
24
+ energy_j = max(power_watts, 0.0) * latency_s
25
+ energy_uj = energy_j * 1_000_000.0
26
+ energy_day_j = energy_j * max(int(decisions_per_day), 1)
27
+ return EnergyEstimate(
28
+ energy_joules=energy_j,
29
+ energy_microjoules=energy_uj,
30
+ decisions_per_day=max(int(decisions_per_day), 1),
31
+ energy_joules_per_day=energy_day_j,
32
+ energy_millijoules_per_day=energy_day_j * 1_000.0,
33
+ )
@@ -171,9 +171,10 @@ class PopulationReportGenerator:
171
171
  if hypo_cols:
172
172
  path = str(output_dir / "_plot_hypo_risk.png")
173
173
  fig, ax = plt.subplots(figsize=(6, 4))
174
- data = [df[c].dropna().values for c in hypo_cols]
174
+ data = [df[c].dropna().astype(float).to_numpy() for c in hypo_cols]
175
175
  labels = ["TBR <70 mg/dL (%)", "TBR <54 mg/dL (%)"][:len(data)]
176
- bp = ax.boxplot(data, labels=labels, patch_artist=True)
176
+ bp = ax.boxplot(data, patch_artist=True)
177
+ ax.set_xticklabels(labels)
177
178
  for patch, color in zip(bp["boxes"], [IINTS_BLUE, IINTS_RED]):
178
179
  patch.set_facecolor(color)
179
180
  patch.set_alpha(0.6)
@@ -96,7 +96,7 @@ def _interpret(score: float, penalties: Dict[str, float], weights: Dict[str, flo
96
96
 
97
97
  # Identify the top penalty contributor
98
98
  weighted = {k: weights[k] * _norm(v, NORM_SCALES[k]) for k, v in penalties.items()}
99
- top_key = max(weighted, key=weighted.get)
99
+ top_key = max(weighted, key=lambda k: weighted[k])
100
100
  key_labels = {
101
101
  "w_below54": "time critically below 54 mg/dL",
102
102
  "w_below70": "time below 70 mg/dL",
@@ -167,8 +167,8 @@ def _compute_hypo_episode_duration(glucose: pd.Series, time_step_minutes: float)
167
167
  An episode starts when glucose drops below 70 mg/dL and ends when it
168
168
  rises back above 70 mg/dL. Returns 0.0 if no episodes.
169
169
  """
170
- below = (glucose < 70.0).values
171
- if not below.any():
170
+ below = (glucose < 70.0).to_numpy(dtype=bool)
171
+ if not np.any(below):
172
172
  return 0.0
173
173
 
174
174
  episode_lengths: list[int] = []
@@ -228,7 +228,8 @@ def compute_safety_index(
228
228
  weights = dict(weights) # defensive copy
229
229
  _validate_weights(weights)
230
230
 
231
- glucose = results_df["glucose_actual_mgdl"].astype(float)
231
+ glucose_series = results_df["glucose_actual_mgdl"].astype(float)
232
+ glucose = glucose_series.to_numpy(dtype=float)
232
233
  n = max(len(glucose), 1)
233
234
 
234
235
  # --- Component 1: TBR critical < 54 mg/dL (%) ---
@@ -243,10 +244,10 @@ def compute_safety_index(
243
244
  supervisor_rate = interventions / duration_hours
244
245
 
245
246
  # --- Component 4: Mean hypo episode duration (hours) ---
246
- mean_episode_hr = _compute_hypo_episode_duration(glucose, time_step_minutes) / 60.0
247
+ mean_episode_hr = _compute_hypo_episode_duration(glucose_series, time_step_minutes) / 60.0
247
248
 
248
249
  # --- Component 5: Tail-risk binary (1 if any glucose < 54, else 0) ---
249
- tail_risk = 1.0 if (glucose < 54.0).any() else 0.0
250
+ tail_risk = 1.0 if np.any(glucose < 54.0) else 0.0
250
251
 
251
252
  penalties: Dict[str, float] = {
252
253
  "w_below54": tbr_critical,
@@ -226,7 +226,7 @@ def evaluate(
226
226
  output_dir: Annotated[Optional[Path], typer.Option(help="Output directory")] = None,
227
227
  max_workers: Annotated[Optional[int], typer.Option(help="Max parallel workers (default: all cores)")] = None,
228
228
  seed: Annotated[Optional[int], typer.Option(help="Random seed for reproducibility")] = None,
229
- patient_model: Annotated[str, typer.Option("--patient-model", help="Patient model type: 'custom' or 'bergman'")] = "custom",
229
+ patient_model: Annotated[str, typer.Option("--patient-model", help="Patient model: auto, bergman, custom, simglucose")] = "auto",
230
230
  ):
231
231
  """
232
232
  Run a Monte Carlo population evaluation of an algorithm.
@@ -361,11 +361,15 @@ def init(
361
361
  algo_content = files("iints.templates").joinpath("default_algorithm.py").read_text()
362
362
  scenario_content = files("iints.templates.scenarios").joinpath("example_scenario.json").read_text()
363
363
  exercise_content = files("iints.templates.scenarios").joinpath("exercise_stress.json").read_text()
364
+ stacking_content = files("iints.templates.scenarios").joinpath("chaos_insulin_stacking.json").read_text()
365
+ runaway_content = files("iints.templates.scenarios").joinpath("chaos_runaway_ai.json").read_text()
364
366
  else:
365
367
  from importlib import resources
366
368
  algo_content = resources.read_text("iints.templates", "default_algorithm.py")
367
369
  scenario_content = resources.read_text("iints.templates.scenarios", "example_scenario.json")
368
370
  exercise_content = resources.read_text("iints.templates.scenarios", "exercise_stress.json")
371
+ stacking_content = resources.read_text("iints.templates.scenarios", "chaos_insulin_stacking.json")
372
+ runaway_content = resources.read_text("iints.templates.scenarios", "chaos_runaway_ai.json")
369
373
  except Exception as e:
370
374
  console.print(f"[bold red]Error reading template files: {e}[/bold red]")
371
375
  raise typer.Exit(code=1)
@@ -381,6 +385,10 @@ def init(
381
385
  f.write(scenario_content)
382
386
  with open(project_path / "scenarios" / "exercise_stress.json", "w") as f:
383
387
  f.write(exercise_content)
388
+ with open(project_path / "scenarios" / "chaos_insulin_stacking.json", "w") as f:
389
+ f.write(stacking_content)
390
+ with open(project_path / "scenarios" / "chaos_runaway_ai.json", "w") as f:
391
+ f.write(runaway_content)
384
392
 
385
393
  # Create README
386
394
  readme_content = f"""# {project_name}
@@ -573,6 +581,11 @@ def presets_run(
573
581
  output_dir: Annotated[Optional[Path], typer.Option(help="Directory to save outputs")] = None,
574
582
  compare_baselines: Annotated[bool, typer.Option(help="Run PID and standard pump baselines in the background")] = True,
575
583
  seed: Annotated[Optional[int], typer.Option(help="Random seed for deterministic runs")] = None,
584
+ patient_model_type: Annotated[str, typer.Option("--patient-model", help="Patient model: auto, bergman, custom, simglucose")] = "auto",
585
+ sensor_noise_std: Annotated[Optional[float], typer.Option("--sensor-noise-std", help="CGM noise std (mg/dL)")] = None,
586
+ sensor_lag_minutes: Annotated[Optional[int], typer.Option("--sensor-lag-minutes", help="CGM lag (minutes)")] = None,
587
+ sensor_dropout_prob: Annotated[Optional[float], typer.Option("--sensor-dropout-prob", help="CGM dropout probability (0-1)")] = None,
588
+ sensor_bias: Annotated[Optional[float], typer.Option("--sensor-bias", help="CGM bias (mg/dL)")] = None,
576
589
  safety_min_glucose: Annotated[Optional[float], typer.Option("--safety-min-glucose", help="Min plausible glucose (mg/dL)")] = None,
577
590
  safety_max_glucose: Annotated[Optional[float], typer.Option("--safety-max-glucose", help="Max plausible glucose (mg/dL)")] = None,
578
591
  safety_max_glucose_delta_per_5_min: Annotated[Optional[float], typer.Option("--safety-max-glucose-delta-per-5-min", help="Max glucose delta per 5 min (mg/dL)")] = None,
@@ -605,7 +618,7 @@ def presets_run(
605
618
  try:
606
619
  patient_config_name = preset.get("patient_config", "default_patient")
607
620
  validated_patient_params = load_patient_config_by_name(patient_config_name).model_dump()
608
- patient_model = iints.PatientModel(**validated_patient_params)
621
+ patient_model = iints.PatientFactory.create_patient(patient_type=patient_model_type, **validated_patient_params)
609
622
  except ValidationError as e:
610
623
  console.print("[bold red]Patient config validation failed:[/bold red]")
611
624
  for line in format_validation_error(e):
@@ -634,12 +647,32 @@ def presets_run(
634
647
  critical_glucose_duration_minutes=safety_critical_glucose_duration_minutes,
635
648
  )
636
649
 
650
+ sensor_model = None
651
+ if any(v is not None for v in (sensor_noise_std, sensor_lag_minutes, sensor_dropout_prob, sensor_bias)):
652
+ sensor_model = iints.SensorModel(
653
+ noise_std=float(sensor_noise_std or 0.0),
654
+ lag_minutes=int(sensor_lag_minutes or 0),
655
+ dropout_prob=float(sensor_dropout_prob or 0.0),
656
+ bias=float(sensor_bias or 0.0),
657
+ seed=resolved_seed,
658
+ )
659
+ elif patient_model_type == "auto":
660
+ sensor_model = iints.SensorModel(
661
+ noise_std=7.0,
662
+ lag_minutes=10,
663
+ dropout_prob=0.0,
664
+ bias=0.0,
665
+ seed=resolved_seed,
666
+ )
667
+
637
668
  simulator_kwargs: Dict[str, Any] = {
638
669
  "patient_model": patient_model,
639
670
  "algorithm": algorithm_instance,
640
671
  "time_step": time_step,
641
672
  "safety_config": safety_config,
642
673
  }
674
+ if sensor_model is not None:
675
+ simulator_kwargs["sensor_model"] = sensor_model
643
676
  simulator_kwargs["seed"] = resolved_seed
644
677
  if safety_config is None:
645
678
  safety_config = SafetyConfig()
@@ -973,6 +1006,11 @@ def run(
973
1006
  output_dir: Annotated[Optional[Path], typer.Option(help="Directory to save simulation results")] = None,
974
1007
  compare_baselines: Annotated[bool, typer.Option(help="Run PID and standard pump baselines in the background")] = True,
975
1008
  seed: Annotated[Optional[int], typer.Option(help="Random seed for deterministic runs")] = None,
1009
+ patient_model_type: Annotated[str, typer.Option("--patient-model", help="Patient model: auto, bergman, custom, simglucose")] = "auto",
1010
+ sensor_noise_std: Annotated[Optional[float], typer.Option("--sensor-noise-std", help="CGM noise std (mg/dL)")] = None,
1011
+ sensor_lag_minutes: Annotated[Optional[int], typer.Option("--sensor-lag-minutes", help="CGM lag (minutes)")] = None,
1012
+ sensor_dropout_prob: Annotated[Optional[float], typer.Option("--sensor-dropout-prob", help="CGM dropout probability (0-1)")] = None,
1013
+ sensor_bias: Annotated[Optional[float], typer.Option("--sensor-bias", help="CGM bias (mg/dL)")] = None,
976
1014
  safety_min_glucose: Annotated[Optional[float], typer.Option("--safety-min-glucose", help="Min plausible glucose (mg/dL)")] = None,
977
1015
  safety_max_glucose: Annotated[Optional[float], typer.Option("--safety-max-glucose", help="Max plausible glucose (mg/dL)")] = None,
978
1016
  safety_max_glucose_delta_per_5_min: Annotated[Optional[float], typer.Option("--safety-max-glucose-delta-per-5-min", help="Max glucose delta per 5 min (mg/dL)")] = None,
@@ -1052,8 +1090,11 @@ def run(
1052
1090
  validated_patient_params = load_patient_config_by_name(patient_config_name).model_dump()
1053
1091
  patient_label = patient_config_name
1054
1092
 
1055
- patient_model = iints.PatientModel(**validated_patient_params)
1056
- console.print(f"Using patient model: {patient_model.__class__.__name__} with config [cyan]{patient_label}[/cyan]")
1093
+ patient_model = iints.PatientFactory.create_patient(patient_type=patient_model_type, **validated_patient_params)
1094
+ console.print(
1095
+ f"Using patient model: {patient_model.__class__.__name__} "
1096
+ f"({patient_model_type}) with config [cyan]{patient_label}[/cyan]"
1097
+ )
1057
1098
  except ValidationError as e:
1058
1099
  console.print("[bold red]Patient config validation failed:[/bold red]")
1059
1100
  for line in format_validation_error(e):
@@ -1117,12 +1158,31 @@ def run(
1117
1158
  output_dir = resolve_output_dir(output_dir, run_id)
1118
1159
 
1119
1160
  effective_safety_config = safety_config or SafetyConfig()
1161
+ sensor_model = None
1162
+ if any(v is not None for v in (sensor_noise_std, sensor_lag_minutes, sensor_dropout_prob, sensor_bias)):
1163
+ sensor_model = iints.SensorModel(
1164
+ noise_std=float(sensor_noise_std or 0.0),
1165
+ lag_minutes=int(sensor_lag_minutes or 0),
1166
+ dropout_prob=float(sensor_dropout_prob or 0.0),
1167
+ bias=float(sensor_bias or 0.0),
1168
+ seed=resolved_seed,
1169
+ )
1170
+ elif patient_model_type == "auto":
1171
+ sensor_model = iints.SensorModel(
1172
+ noise_std=7.0,
1173
+ lag_minutes=10,
1174
+ dropout_prob=0.0,
1175
+ bias=0.0,
1176
+ seed=resolved_seed,
1177
+ )
1178
+
1120
1179
  simulator = iints.Simulator(
1121
1180
  patient_model=patient_model,
1122
1181
  algorithm=algorithm_instance,
1123
1182
  time_step=time_step,
1124
1183
  seed=resolved_seed,
1125
1184
  safety_config=effective_safety_config,
1185
+ sensor_model=sensor_model,
1126
1186
  )
1127
1187
 
1128
1188
  for event in stress_events:
@@ -1725,6 +1785,180 @@ def research_prepare_azt1d(
1725
1785
  console.print(f"[green]Quality report :[/green] {report}")
1726
1786
 
1727
1787
 
1788
+ @research_app.command("prepare-ohio")
1789
+ def research_prepare_ohio(
1790
+ input_dir: Annotated[Path, typer.Option(help="Root directory containing OhioT1DM patient_* folders")] = Path("data_packs/public/ohio_t1dm"),
1791
+ output: Annotated[Path, typer.Option(help="Output dataset path (CSV or Parquet)")] = Path("data_packs/public/ohio_t1dm/processed/ohio_t1dm_merged.csv"),
1792
+ report: Annotated[Path, typer.Option(help="Quality report output path")] = Path("data_packs/public/ohio_t1dm/quality_report.json"),
1793
+ time_step: Annotated[int, typer.Option(help="Expected CGM sample interval (minutes)")] = 5,
1794
+ max_gap_multiplier: Annotated[float, typer.Option(help="Segment-break gap multiplier")] = 2.5,
1795
+ dia_minutes: Annotated[float, typer.Option(help="Insulin action duration (minutes)")] = 240.0,
1796
+ peak_minutes: Annotated[float, typer.Option(help="IOB peak time (minutes, OpenAPS bilinear)")] = 75.0,
1797
+ carb_absorb_minutes: Annotated[float, typer.Option(help="Carb absorption duration (minutes)")] = 120.0,
1798
+ max_insulin: Annotated[float, typer.Option(help="Clip insulin units above this")] = 30.0,
1799
+ max_carbs: Annotated[float, typer.Option(help="Clip carb grams above this")] = 200.0,
1800
+ icr_default: Annotated[float, typer.Option(help="Fallback ICR (g/U)")] = 10.0,
1801
+ isf_default: Annotated[float, typer.Option(help="Fallback ISF (mg/dL per U)")] = 50.0,
1802
+ basal_default: Annotated[float, typer.Option(help="Fallback basal rate (U/hr)")] = 0.0,
1803
+ meal_window_min: Annotated[float, typer.Option(help="Meal→insulin matching window (minutes)")] = 30.0,
1804
+ isf_window_min: Annotated[float, typer.Option(help="ISF estimation window (minutes)")] = 60.0,
1805
+ min_meal_carbs: Annotated[float, typer.Option(help="Minimum carbs to consider a meal (g)")] = 5.0,
1806
+ min_bolus: Annotated[float, typer.Option(help="Minimum insulin to consider a bolus (U)")] = 0.1,
1807
+ ):
1808
+ """
1809
+ Prepare the OhioT1DM dataset for LSTM predictor training.
1810
+
1811
+ Reads per-patient CSVs, derives IOB/COB using the OpenAPS bilinear model,
1812
+ estimates effective ISF/ICR/basal per subject, adds time-of-day features,
1813
+ and writes the merged dataset plus a quality report.
1814
+
1815
+ Example
1816
+ -------
1817
+ iints research prepare-ohio --input-dir data_packs/public/ohio_t1dm --output ohio.parquet
1818
+ """
1819
+ console = Console()
1820
+ if not input_dir.exists():
1821
+ console.print(f"[bold red]Input directory not found: {input_dir}[/bold red]")
1822
+ raise typer.Exit(code=1)
1823
+
1824
+ import subprocess, sys # noqa: E401
1825
+ cmd = [
1826
+ sys.executable,
1827
+ str(Path(__file__).parent.parent.parent.parent.parent / "research" / "prepare_ohio_t1dm.py"),
1828
+ "--input", str(input_dir),
1829
+ "--output", str(output),
1830
+ "--report", str(report),
1831
+ "--time-step", str(time_step),
1832
+ "--max-gap-multiplier", str(max_gap_multiplier),
1833
+ "--dia-minutes", str(dia_minutes),
1834
+ "--peak-minutes", str(peak_minutes),
1835
+ "--carb-absorb-minutes", str(carb_absorb_minutes),
1836
+ "--max-insulin", str(max_insulin),
1837
+ "--max-carbs", str(max_carbs),
1838
+ "--icr-default", str(icr_default),
1839
+ "--isf-default", str(isf_default),
1840
+ "--basal-default", str(basal_default),
1841
+ "--meal-window-min", str(meal_window_min),
1842
+ "--isf-window-min", str(isf_window_min),
1843
+ "--min-meal-carbs", str(min_meal_carbs),
1844
+ "--min-bolus", str(min_bolus),
1845
+ ]
1846
+ try:
1847
+ import importlib.util as _ilu
1848
+ spec = _ilu.spec_from_file_location(
1849
+ "_prepare_ohio_t1dm",
1850
+ Path(__file__).parent.parent.parent.parent.parent / "research" / "prepare_ohio_t1dm.py",
1851
+ )
1852
+ if spec is not None and spec.loader is not None:
1853
+ import sys as _sys
1854
+ _old_argv = _sys.argv[:]
1855
+ _sys.argv = cmd[1:]
1856
+ try:
1857
+ mod = _ilu.module_from_spec(spec)
1858
+ spec.loader.exec_module(mod) # type: ignore[union-attr]
1859
+ mod.main() # type: ignore[attr-defined]
1860
+ finally:
1861
+ _sys.argv = _old_argv
1862
+ else:
1863
+ subprocess.run(cmd, check=True)
1864
+ except Exception as exc:
1865
+ console.print(f"[bold red]prepare-ohio failed: {exc}[/bold red]")
1866
+ raise typer.Exit(code=1)
1867
+
1868
+ console.print(f"[green]Dataset written to:[/green] {output}")
1869
+ console.print(f"[green]Quality report :[/green] {report}")
1870
+
1871
+
1872
+ @research_app.command("prepare-hupa")
1873
+ def research_prepare_hupa(
1874
+ input_dir: Annotated[Path, typer.Option(help="Root directory containing HUPA-UCM CSV files")] = Path("data_packs/public/hupa_ucm"),
1875
+ output: Annotated[Path, typer.Option(help="Output dataset path (CSV or Parquet)")] = Path("data_packs/public/hupa_ucm/processed/hupa_ucm_merged.csv"),
1876
+ report: Annotated[Path, typer.Option(help="Quality report output path")] = Path("data_packs/public/hupa_ucm/quality_report.json"),
1877
+ time_step: Annotated[int, typer.Option(help="Expected CGM sample interval (minutes)")] = 5,
1878
+ max_gap_multiplier: Annotated[float, typer.Option(help="Segment-break gap multiplier")] = 2.5,
1879
+ dia_minutes: Annotated[float, typer.Option(help="Insulin action duration (minutes)")] = 240.0,
1880
+ peak_minutes: Annotated[float, typer.Option(help="IOB peak time (minutes, OpenAPS bilinear)")] = 75.0,
1881
+ carb_absorb_minutes: Annotated[float, typer.Option(help="Carb absorption duration (minutes)")] = 120.0,
1882
+ max_insulin: Annotated[float, typer.Option(help="Clip insulin units above this")] = 30.0,
1883
+ max_carbs: Annotated[float, typer.Option(help="Clip carb grams above this")] = 200.0,
1884
+ carb_serving_grams: Annotated[float, typer.Option(help="Carb serving size (g) for carb_input")] = 10.0,
1885
+ basal_is_rate: Annotated[bool, typer.Option(help="Treat basal_rate as U/hr (convert to U/step)")] = False,
1886
+ icr_default: Annotated[float, typer.Option(help="Fallback ICR (g/U)")] = 10.0,
1887
+ isf_default: Annotated[float, typer.Option(help="Fallback ISF (mg/dL per U)")] = 50.0,
1888
+ basal_default: Annotated[float, typer.Option(help="Fallback basal rate (U/hr)")] = 0.0,
1889
+ meal_window_min: Annotated[float, typer.Option(help="Meal→insulin matching window (minutes)")] = 30.0,
1890
+ isf_window_min: Annotated[float, typer.Option(help="ISF estimation window (minutes)")] = 60.0,
1891
+ min_meal_carbs: Annotated[float, typer.Option(help="Minimum carbs to consider a meal (g)")] = 5.0,
1892
+ min_bolus: Annotated[float, typer.Option(help="Minimum insulin to consider a bolus (U)")] = 0.1,
1893
+ ):
1894
+ """
1895
+ Prepare the HUPA-UCM dataset for LSTM predictor training.
1896
+
1897
+ Parses per-patient CSVs, derives IOB/COB, estimates ISF/ICR/basal per
1898
+ subject, and writes the merged dataset plus a quality report.
1899
+
1900
+ Example
1901
+ -------
1902
+ iints research prepare-hupa --input-dir data_packs/public/hupa_ucm --output hupa.parquet
1903
+ """
1904
+ console = Console()
1905
+ if not input_dir.exists():
1906
+ console.print(f"[bold red]Input directory not found: {input_dir}[/bold red]")
1907
+ raise typer.Exit(code=1)
1908
+
1909
+ import subprocess, sys # noqa: E401
1910
+ cmd = [
1911
+ sys.executable,
1912
+ str(Path(__file__).parent.parent.parent.parent.parent / "research" / "prepare_hupa_ucm.py"),
1913
+ "--input", str(input_dir),
1914
+ "--output", str(output),
1915
+ "--report", str(report),
1916
+ "--time-step", str(time_step),
1917
+ "--max-gap-multiplier", str(max_gap_multiplier),
1918
+ "--dia-minutes", str(dia_minutes),
1919
+ "--peak-minutes", str(peak_minutes),
1920
+ "--carb-absorb-minutes", str(carb_absorb_minutes),
1921
+ "--max-insulin", str(max_insulin),
1922
+ "--max-carbs", str(max_carbs),
1923
+ "--carb-serving-grams", str(carb_serving_grams),
1924
+ "--icr-default", str(icr_default),
1925
+ "--isf-default", str(isf_default),
1926
+ "--basal-default", str(basal_default),
1927
+ "--meal-window-min", str(meal_window_min),
1928
+ "--isf-window-min", str(isf_window_min),
1929
+ "--min-meal-carbs", str(min_meal_carbs),
1930
+ "--min-bolus", str(min_bolus),
1931
+ ]
1932
+ if basal_is_rate:
1933
+ cmd.append("--basal-is-rate")
1934
+ else:
1935
+ cmd.append("--no-basal-is-rate")
1936
+ try:
1937
+ import importlib.util as _ilu
1938
+ spec = _ilu.spec_from_file_location(
1939
+ "_prepare_hupa_ucm",
1940
+ Path(__file__).parent.parent.parent.parent.parent / "research" / "prepare_hupa_ucm.py",
1941
+ )
1942
+ if spec is not None and spec.loader is not None:
1943
+ import sys as _sys
1944
+ _old_argv = _sys.argv[:]
1945
+ _sys.argv = cmd[1:]
1946
+ try:
1947
+ mod = _ilu.module_from_spec(spec)
1948
+ spec.loader.exec_module(mod) # type: ignore[union-attr]
1949
+ mod.main() # type: ignore[attr-defined]
1950
+ finally:
1951
+ _sys.argv = _old_argv
1952
+ else:
1953
+ subprocess.run(cmd, check=True)
1954
+ except Exception as exc:
1955
+ console.print(f"[bold red]prepare-hupa failed: {exc}[/bold red]")
1956
+ raise typer.Exit(code=1)
1957
+
1958
+ console.print(f"[green]Dataset written to:[/green] {output}")
1959
+ console.print(f"[green]Quality report :[/green] {report}")
1960
+
1961
+
1728
1962
  @research_app.command("quality")
1729
1963
  def research_quality(
1730
1964
  report: Annotated[Path, typer.Option(help="Path to quality_report.json produced by prepare-azt1d")] = Path("data_packs/public/azt1d/quality_report.json"),
@@ -1790,6 +2024,35 @@ def research_quality(
1790
2024
  console.print(table)
1791
2025
 
1792
2026
 
2027
+ @research_app.command("export-onnx")
2028
+ def research_export_onnx(
2029
+ model: Annotated[Path, typer.Option(help="Predictor checkpoint (.pt)")] = Path("models/hupa_finetuned_v2/predictor.pt"),
2030
+ out: Annotated[Path, typer.Option(help="Output ONNX file path")] = Path("models/predictor.onnx"),
2031
+ ):
2032
+ """
2033
+ Export a trained predictor to ONNX for edge/Jetson deployment.
2034
+ """
2035
+ console = Console()
2036
+ if not model.exists():
2037
+ console.print(f"[bold red]Model not found: {model}[/bold red]")
2038
+ raise typer.Exit(code=1)
2039
+
2040
+ import subprocess, sys # noqa: E401
2041
+ cmd = [
2042
+ sys.executable,
2043
+ str(Path(__file__).parent.parent.parent.parent.parent / "research" / "export_predictor.py"),
2044
+ "--model", str(model),
2045
+ "--out", str(out),
2046
+ ]
2047
+ try:
2048
+ subprocess.run(cmd, check=True)
2049
+ except Exception as exc:
2050
+ console.print(f"[bold red]export-onnx failed: {exc}[/bold red]")
2051
+ raise typer.Exit(code=1)
2052
+
2053
+ console.print(f"[green]ONNX written to:[/green] {out}")
2054
+
2055
+
1793
2056
  @app.command("import-data")
1794
2057
  def import_data(
1795
2058
  input_csv: Annotated[Path, typer.Option(help="Path to CGM CSV file")],