iints-sdk-python35 0.1.18__tar.gz → 0.1.20__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 (127) hide show
  1. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/PKG-INFO +5 -2
  2. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/README.md +3 -1
  3. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/pyproject.toml +2 -1
  4. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/__init__.py +3 -1
  5. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/cli/cli.py +59 -9
  6. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/core/algorithms/mock_algorithms.py +4 -1
  7. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/core/patient/models.py +4 -1
  8. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/core/patient/patient_factory.py +20 -1
  9. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/core/safety/input_validator.py +4 -4
  10. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/core/safety/supervisor.py +1 -6
  11. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/core/simulator.py +0 -3
  12. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/core/supervisor.py +15 -15
  13. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/highlevel.py +56 -4
  14. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/research/__init__.py +8 -1
  15. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/research/config.py +15 -1
  16. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/research/dataset.py +58 -1
  17. iints_sdk_python35-0.1.20/src/iints/research/losses.py +98 -0
  18. iints_sdk_python35-0.1.20/src/iints/research/metrics.py +105 -0
  19. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/research/predictor.py +0 -6
  20. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints_sdk_python35.egg-info/PKG-INFO +5 -2
  21. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints_sdk_python35.egg-info/SOURCES.txt +1 -0
  22. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints_sdk_python35.egg-info/requires.txt +1 -0
  23. iints_sdk_python35-0.1.18/src/iints/research/losses.py +0 -73
  24. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/LICENSE +0 -0
  25. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/setup.cfg +0 -0
  26. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/analysis/__init__.py +0 -0
  27. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/analysis/algorithm_xray.py +0 -0
  28. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/analysis/baseline.py +0 -0
  29. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/analysis/clinical_benchmark.py +0 -0
  30. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/analysis/clinical_metrics.py +0 -0
  31. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/analysis/clinical_tir_analyzer.py +0 -0
  32. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/analysis/diabetes_metrics.py +0 -0
  33. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/analysis/edge_efficiency.py +0 -0
  34. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/analysis/edge_performance_monitor.py +0 -0
  35. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/analysis/explainability.py +0 -0
  36. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/analysis/explainable_ai.py +0 -0
  37. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/analysis/hardware_benchmark.py +0 -0
  38. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/analysis/metrics.py +0 -0
  39. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/analysis/population_report.py +0 -0
  40. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/analysis/reporting.py +0 -0
  41. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/analysis/safety_index.py +0 -0
  42. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/analysis/sensor_filtering.py +0 -0
  43. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/analysis/validator.py +0 -0
  44. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/api/__init__.py +0 -0
  45. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/api/base_algorithm.py +0 -0
  46. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/api/registry.py +0 -0
  47. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/api/template_algorithm.py +0 -0
  48. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/assets/iints_logo.png +0 -0
  49. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/cli/__init__.py +0 -0
  50. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/core/__init__.py +0 -0
  51. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/core/algorithms/__init__.py +0 -0
  52. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/core/algorithms/battle_runner.py +0 -0
  53. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/core/algorithms/correction_bolus.py +0 -0
  54. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/core/algorithms/discovery.py +0 -0
  55. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/core/algorithms/fixed_basal_bolus.py +0 -0
  56. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/core/algorithms/hybrid_algorithm.py +0 -0
  57. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/core/algorithms/lstm_algorithm.py +0 -0
  58. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/core/algorithms/pid_controller.py +0 -0
  59. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/core/algorithms/standard_pump_algo.py +0 -0
  60. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/core/device.py +0 -0
  61. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/core/device_manager.py +0 -0
  62. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/core/devices/__init__.py +0 -0
  63. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/core/devices/models.py +0 -0
  64. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/core/patient/__init__.py +0 -0
  65. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/core/patient/bergman_model.py +0 -0
  66. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/core/patient/profile.py +0 -0
  67. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/core/safety/__init__.py +0 -0
  68. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/core/safety/config.py +0 -0
  69. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/core/simulation/__init__.py +0 -0
  70. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/core/simulation/scenario_parser.py +0 -0
  71. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/data/__init__.py +0 -0
  72. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/data/adapter.py +0 -0
  73. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/data/column_mapper.py +0 -0
  74. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/data/datasets.json +0 -0
  75. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/data/demo/__init__.py +0 -0
  76. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/data/demo/demo_cgm.csv +0 -0
  77. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/data/importer.py +0 -0
  78. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/data/ingestor.py +0 -0
  79. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/data/nightscout.py +0 -0
  80. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/data/quality_checker.py +0 -0
  81. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/data/registry.py +0 -0
  82. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/data/tidepool.py +0 -0
  83. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/data/universal_parser.py +0 -0
  84. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/data/virtual_patients/clinic_safe_baseline.yaml +0 -0
  85. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/data/virtual_patients/clinic_safe_hyper_challenge.yaml +0 -0
  86. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/data/virtual_patients/clinic_safe_hypo_prone.yaml +0 -0
  87. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/data/virtual_patients/clinic_safe_midnight.yaml +0 -0
  88. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/data/virtual_patients/clinic_safe_pizza.yaml +0 -0
  89. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/data/virtual_patients/clinic_safe_stress_meal.yaml +0 -0
  90. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/data/virtual_patients/default_patient.yaml +0 -0
  91. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/data/virtual_patients/patient_559_config.yaml +0 -0
  92. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/emulation/__init__.py +0 -0
  93. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/emulation/legacy_base.py +0 -0
  94. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/emulation/medtronic_780g.py +0 -0
  95. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/emulation/omnipod_5.py +0 -0
  96. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/emulation/tandem_controliq.py +0 -0
  97. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/learning/__init__.py +0 -0
  98. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/learning/autonomous_optimizer.py +0 -0
  99. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/learning/learning_system.py +0 -0
  100. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/metrics.py +0 -0
  101. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/population/__init__.py +0 -0
  102. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/population/generator.py +0 -0
  103. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/population/runner.py +0 -0
  104. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/presets/__init__.py +0 -0
  105. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/presets/presets.json +0 -0
  106. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/scenarios/__init__.py +0 -0
  107. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/scenarios/generator.py +0 -0
  108. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/templates/__init__.py +0 -0
  109. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/templates/default_algorithm.py +0 -0
  110. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/templates/scenarios/__init__.py +0 -0
  111. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/templates/scenarios/chaos_insulin_stacking.json +0 -0
  112. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/templates/scenarios/chaos_runaway_ai.json +0 -0
  113. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/templates/scenarios/example_scenario.json +0 -0
  114. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/templates/scenarios/exercise_stress.json +0 -0
  115. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/utils/__init__.py +0 -0
  116. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/utils/plotting.py +0 -0
  117. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/utils/run_io.py +0 -0
  118. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/validation/__init__.py +0 -0
  119. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/validation/schemas.py +0 -0
  120. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/visualization/__init__.py +0 -0
  121. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/visualization/cockpit.py +0 -0
  122. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints/visualization/uncertainty_cloud.py +0 -0
  123. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints_sdk_python35.egg-info/dependency_links.txt +0 -0
  124. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints_sdk_python35.egg-info/entry_points.txt +0 -0
  125. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/src/iints_sdk_python35.egg-info/top_level.txt +0 -0
  126. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/tests/test_bergman.py +0 -0
  127. {iints_sdk_python35-0.1.18 → iints_sdk_python35-0.1.20}/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.18
3
+ Version: 0.1.20
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
@@ -29,6 +29,7 @@ Requires-Dist: seaborn>=0.11.0
29
29
  Requires-Dist: typer[all]
30
30
  Provides-Extra: dev
31
31
  Requires-Dist: pytest>=7.0.0; extra == "dev"
32
+ Requires-Dist: hypothesis>=6.0.0; extra == "dev"
32
33
  Requires-Dist: flake8; extra == "dev"
33
34
  Requires-Dist: mypy; extra == "dev"
34
35
  Requires-Dist: pandas-stubs; extra == "dev"
@@ -206,7 +207,9 @@ outputs = iints.run_simulation(..., predictor=predictor)
206
207
  ```
207
208
 
208
209
  ### Documentation
209
- * Product manual: `docs/COMPREHENSIVE_GUIDE.md`
210
+ * PDF manual: `docs/manuals/IINTS-AF_SDK_Manual.pdf`
211
+ * Manual source: `docs/manuals/IINTS-AF_SDK_Manual.md`
212
+ * Comprehensive guide: `docs/COMPREHENSIVE_GUIDE.md`
210
213
  * Notebook index: `examples/notebooks/README.md`
211
214
  * Technical README: `docs/TECHNICAL_README.md`
212
215
  * API Stability: `API_STABILITY.md`
@@ -158,7 +158,9 @@ outputs = iints.run_simulation(..., predictor=predictor)
158
158
  ```
159
159
 
160
160
  ### Documentation
161
- * Product manual: `docs/COMPREHENSIVE_GUIDE.md`
161
+ * PDF manual: `docs/manuals/IINTS-AF_SDK_Manual.pdf`
162
+ * Manual source: `docs/manuals/IINTS-AF_SDK_Manual.md`
163
+ * Comprehensive guide: `docs/COMPREHENSIVE_GUIDE.md`
162
164
  * Notebook index: `examples/notebooks/README.md`
163
165
  * Technical README: `docs/TECHNICAL_README.md`
164
166
  * API Stability: `API_STABILITY.md`
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "iints-sdk-python35"
7
- version = "0.1.18"
7
+ version = "0.1.20"
8
8
  authors = [
9
9
  { name="Rune Bobbaers", email="rune.bobbaers@gmail.com" },
10
10
  ]
@@ -39,6 +39,7 @@ dependencies = [
39
39
  [project.optional-dependencies]
40
40
  dev = [
41
41
  "pytest>=7.0.0",
42
+ "hypothesis>=6.0.0",
42
43
  "flake8",
43
44
  "mypy",
44
45
  "pandas-stubs",
@@ -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.18"
6
+ __version__ = "0.1.20"
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
@@ -130,6 +131,7 @@ __all__ = [
130
131
  "InsulinAlgorithm", "AlgorithmInput", "AlgorithmResult", "AlgorithmMetadata", "WhyLogEntry",
131
132
  # Core
132
133
  "Simulator", "StressEvent", "PatientModel", "DeviceManager",
134
+ "PatientFactory",
133
135
  "PatientProfile",
134
136
  "SimulationLimitError",
135
137
  "SafetySupervisor",
@@ -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.
@@ -581,6 +581,11 @@ def presets_run(
581
581
  output_dir: Annotated[Optional[Path], typer.Option(help="Directory to save outputs")] = None,
582
582
  compare_baselines: Annotated[bool, typer.Option(help="Run PID and standard pump baselines in the background")] = True,
583
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,
584
589
  safety_min_glucose: Annotated[Optional[float], typer.Option("--safety-min-glucose", help="Min plausible glucose (mg/dL)")] = None,
585
590
  safety_max_glucose: Annotated[Optional[float], typer.Option("--safety-max-glucose", help="Max plausible glucose (mg/dL)")] = None,
586
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,
@@ -613,7 +618,7 @@ def presets_run(
613
618
  try:
614
619
  patient_config_name = preset.get("patient_config", "default_patient")
615
620
  validated_patient_params = load_patient_config_by_name(patient_config_name).model_dump()
616
- patient_model = iints.PatientModel(**validated_patient_params)
621
+ patient_model = iints.PatientFactory.create_patient(patient_type=patient_model_type, **validated_patient_params)
617
622
  except ValidationError as e:
618
623
  console.print("[bold red]Patient config validation failed:[/bold red]")
619
624
  for line in format_validation_error(e):
@@ -642,12 +647,32 @@ def presets_run(
642
647
  critical_glucose_duration_minutes=safety_critical_glucose_duration_minutes,
643
648
  )
644
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
+
645
668
  simulator_kwargs: Dict[str, Any] = {
646
669
  "patient_model": patient_model,
647
670
  "algorithm": algorithm_instance,
648
671
  "time_step": time_step,
649
672
  "safety_config": safety_config,
650
673
  }
674
+ if sensor_model is not None:
675
+ simulator_kwargs["sensor_model"] = sensor_model
651
676
  simulator_kwargs["seed"] = resolved_seed
652
677
  if safety_config is None:
653
678
  safety_config = SafetyConfig()
@@ -740,8 +765,9 @@ def presets_run(
740
765
  if compare_baselines:
741
766
  manifest_files["baseline_json"] = output_dir / "baseline" / "baseline_comparison.json"
742
767
  manifest_files["baseline_csv"] = output_dir / "baseline" / "baseline_comparison.csv"
743
- if output_dir / "audit" / "audit_summary.json":
744
- manifest_files["audit_summary"] = output_dir / "audit" / "audit_summary.json"
768
+ audit_summary_path = output_dir / "audit" / "audit_summary.json"
769
+ if audit_summary_path.exists():
770
+ manifest_files["audit_summary"] = audit_summary_path
745
771
  run_manifest = build_run_manifest(output_dir, manifest_files)
746
772
  run_manifest_path = output_dir / "run_manifest.json"
747
773
  write_json(run_manifest_path, run_manifest)
@@ -749,9 +775,6 @@ def presets_run(
749
775
  signature_path = maybe_sign_manifest(run_manifest_path)
750
776
  if signature_path:
751
777
  console.print(f"Run manifest signature: {signature_path}")
752
- signature_path = maybe_sign_manifest(run_manifest_path)
753
- if signature_path:
754
- console.print(f"Run manifest signature: {signature_path}")
755
778
 
756
779
 
757
780
  @presets_app.command("create")
@@ -981,6 +1004,11 @@ def run(
981
1004
  output_dir: Annotated[Optional[Path], typer.Option(help="Directory to save simulation results")] = None,
982
1005
  compare_baselines: Annotated[bool, typer.Option(help="Run PID and standard pump baselines in the background")] = True,
983
1006
  seed: Annotated[Optional[int], typer.Option(help="Random seed for deterministic runs")] = None,
1007
+ patient_model_type: Annotated[str, typer.Option("--patient-model", help="Patient model: auto, bergman, custom, simglucose")] = "auto",
1008
+ sensor_noise_std: Annotated[Optional[float], typer.Option("--sensor-noise-std", help="CGM noise std (mg/dL)")] = None,
1009
+ sensor_lag_minutes: Annotated[Optional[int], typer.Option("--sensor-lag-minutes", help="CGM lag (minutes)")] = None,
1010
+ sensor_dropout_prob: Annotated[Optional[float], typer.Option("--sensor-dropout-prob", help="CGM dropout probability (0-1)")] = None,
1011
+ sensor_bias: Annotated[Optional[float], typer.Option("--sensor-bias", help="CGM bias (mg/dL)")] = None,
984
1012
  safety_min_glucose: Annotated[Optional[float], typer.Option("--safety-min-glucose", help="Min plausible glucose (mg/dL)")] = None,
985
1013
  safety_max_glucose: Annotated[Optional[float], typer.Option("--safety-max-glucose", help="Max plausible glucose (mg/dL)")] = None,
986
1014
  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,
@@ -1060,8 +1088,11 @@ def run(
1060
1088
  validated_patient_params = load_patient_config_by_name(patient_config_name).model_dump()
1061
1089
  patient_label = patient_config_name
1062
1090
 
1063
- patient_model = iints.PatientModel(**validated_patient_params)
1064
- console.print(f"Using patient model: {patient_model.__class__.__name__} with config [cyan]{patient_label}[/cyan]")
1091
+ patient_model = iints.PatientFactory.create_patient(patient_type=patient_model_type, **validated_patient_params)
1092
+ console.print(
1093
+ f"Using patient model: {patient_model.__class__.__name__} "
1094
+ f"({patient_model_type}) with config [cyan]{patient_label}[/cyan]"
1095
+ )
1065
1096
  except ValidationError as e:
1066
1097
  console.print("[bold red]Patient config validation failed:[/bold red]")
1067
1098
  for line in format_validation_error(e):
@@ -1125,12 +1156,31 @@ def run(
1125
1156
  output_dir = resolve_output_dir(output_dir, run_id)
1126
1157
 
1127
1158
  effective_safety_config = safety_config or SafetyConfig()
1159
+ sensor_model = None
1160
+ if any(v is not None for v in (sensor_noise_std, sensor_lag_minutes, sensor_dropout_prob, sensor_bias)):
1161
+ sensor_model = iints.SensorModel(
1162
+ noise_std=float(sensor_noise_std or 0.0),
1163
+ lag_minutes=int(sensor_lag_minutes or 0),
1164
+ dropout_prob=float(sensor_dropout_prob or 0.0),
1165
+ bias=float(sensor_bias or 0.0),
1166
+ seed=resolved_seed,
1167
+ )
1168
+ elif patient_model_type == "auto":
1169
+ sensor_model = iints.SensorModel(
1170
+ noise_std=7.0,
1171
+ lag_minutes=10,
1172
+ dropout_prob=0.0,
1173
+ bias=0.0,
1174
+ seed=resolved_seed,
1175
+ )
1176
+
1128
1177
  simulator = iints.Simulator(
1129
1178
  patient_model=patient_model,
1130
1179
  algorithm=algorithm_instance,
1131
1180
  time_step=time_step,
1132
1181
  seed=resolved_seed,
1133
1182
  safety_config=effective_safety_config,
1183
+ sensor_model=sensor_model,
1134
1184
  )
1135
1185
 
1136
1186
  for event in stress_events:
@@ -95,7 +95,10 @@ class RunawayAIAlgorithm(InsulinAlgorithm):
95
95
 
96
96
  def predict_insulin(self, data: AlgorithmInput) -> Dict[str, Any]:
97
97
  trend = data.glucose_trend_mgdl_min
98
- if data.current_glucose <= self.trigger_glucose or (trend is not None and trend < 0):
98
+ current_glucose = data.current_glucose
99
+ if current_glucose is None:
100
+ dose = 0.0
101
+ elif current_glucose <= self.trigger_glucose or (trend is not None and trend < 0):
99
102
  dose = self.max_bolus
100
103
  else:
101
104
  dose = 0.0
@@ -16,6 +16,7 @@ class CustomPatientModel:
16
16
  def __init__(self, basal_insulin_rate: float = 0.8, insulin_sensitivity: float = 50.0,
17
17
  carb_factor: float = 10.0, glucose_decay_rate: float = 0.002,
18
18
  initial_glucose: float = 120.0, glucose_absorption_rate: float = 0.03,
19
+ basal_glucose_target: Optional[float] = None,
19
20
  insulin_action_duration: float = 300.0, # minutes, e.g., 5 hours
20
21
  insulin_peak_time: float = 75.0, # minutes
21
22
  meal_mismatch_epsilon: float = 1.0, # Factor for meal mismatch
@@ -43,6 +44,7 @@ class CustomPatientModel:
43
44
  self.carb_factor = carb_factor
44
45
  self.glucose_decay_rate = glucose_decay_rate
45
46
  self.glucose_absorption_rate = glucose_absorption_rate
47
+ self.basal_glucose_target = basal_glucose_target if basal_glucose_target is not None else initial_glucose
46
48
  self.insulin_action_duration = insulin_action_duration
47
49
  self.insulin_peak_time = insulin_peak_time
48
50
  self.meal_mismatch_epsilon = meal_mismatch_epsilon
@@ -182,7 +184,8 @@ class CustomPatientModel:
182
184
 
183
185
 
184
186
  # --- Basal metabolic glucose production/consumption (simplified) ---
185
- basal_glucose_change = -self.glucose_decay_rate * self.current_glucose * time_step
187
+ # Homeostatic drift toward a basal target (prevents runaway decline)
188
+ basal_glucose_change = -self.glucose_decay_rate * (self.current_glucose - self.basal_glucose_target) * time_step
186
189
 
187
190
  # --- Dawn phenomenon effect ---
188
191
  dawn_effect = 0.0
@@ -2,6 +2,13 @@ import numpy as np
2
2
  from typing import Dict, Any, Optional
3
3
  from .models import CustomPatientModel
4
4
 
5
+ try:
6
+ from .bergman_model import BergmanPatientModel
7
+ BERGMAN_AVAILABLE = True
8
+ except Exception:
9
+ BergmanPatientModel = None # type: ignore[assignment,misc]
10
+ BERGMAN_AVAILABLE = False
11
+
5
12
  try:
6
13
  from simglucose.simulation.env import T1DSimEnv
7
14
  from simglucose.patient.t1dpatient import T1DPatient
@@ -25,10 +32,22 @@ class PatientFactory:
25
32
  ]
26
33
 
27
34
  @staticmethod
28
- def create_patient(patient_type='custom', patient_id=None, initial_glucose=120.0, **kwargs):
35
+ def create_patient(patient_type='auto', patient_id=None, initial_glucose=120.0, **kwargs):
29
36
  """Create a patient model based on type."""
37
+ if patient_type == 'auto':
38
+ if BERGMAN_AVAILABLE and BergmanPatientModel is not None:
39
+ return BergmanPatientModel(initial_glucose=initial_glucose, **kwargs)
40
+ if SIMGLUCOSE_AVAILABLE:
41
+ patient_name = patient_id or PatientFactory.SIMGLUCOSE_PATIENTS[0]
42
+ return SimglucosePatientWrapper(patient_name, initial_glucose)
43
+ return CustomPatientModel(initial_glucose=initial_glucose, **kwargs)
30
44
  if patient_type == 'custom':
31
45
  return CustomPatientModel(initial_glucose=initial_glucose, **kwargs)
46
+ elif patient_type == 'bergman':
47
+ if not BERGMAN_AVAILABLE or BergmanPatientModel is None:
48
+ print("Warning: Bergman model not available, falling back to custom model")
49
+ return CustomPatientModel(initial_glucose=initial_glucose, **kwargs)
50
+ return BergmanPatientModel(initial_glucose=initial_glucose, **kwargs)
32
51
  elif patient_type == 'simglucose':
33
52
  if not SIMGLUCOSE_AVAILABLE:
34
53
  print("Warning: Simglucose not available, falling back to custom model")
@@ -1,4 +1,4 @@
1
- from typing import Optional
1
+ from typing import Any, Dict, Optional
2
2
 
3
3
  from iints.core.safety.config import SafetyConfig
4
4
 
@@ -33,18 +33,18 @@ class InputValidator:
33
33
  self.last_valid_glucose: Optional[float] = None
34
34
  self.last_validation_time: Optional[float] = None
35
35
 
36
- def reset(self):
36
+ def reset(self) -> None:
37
37
  """Resets the state of the validator for a new simulation."""
38
38
  self.last_valid_glucose = None
39
39
  self.last_validation_time = None
40
40
 
41
- def get_state(self) -> dict:
41
+ def get_state(self) -> Dict[str, Any]:
42
42
  return {
43
43
  "last_valid_glucose": self.last_valid_glucose,
44
44
  "last_validation_time": self.last_validation_time,
45
45
  }
46
46
 
47
- def set_state(self, state: dict) -> None:
47
+ def set_state(self, state: Dict[str, Any]) -> None:
48
48
  self.last_valid_glucose = state.get("last_valid_glucose")
49
49
  self.last_validation_time = state.get("last_validation_time")
50
50
 
@@ -1,6 +1,7 @@
1
1
  from typing import Optional
2
2
 
3
3
  from iints.core.safety.config import SafetyConfig
4
+ from iints.core.safety.input_validator import InputValidator
4
5
  from iints.core.supervisor import IndependentSupervisor as FullSupervisor
5
6
 
6
7
  class IndependentSupervisor(FullSupervisor):
@@ -31,9 +32,3 @@ class IndependentSupervisor(FullSupervisor):
31
32
 
32
33
  # Alias for backward compatibility as the codebase migrates
33
34
  SafetySupervisor = IndependentSupervisor
34
-
35
- class InputValidator:
36
- """
37
- Validates simulation inputs.
38
- """
39
- pass
@@ -115,9 +115,6 @@ class Simulator:
115
115
  self.simulation_data: List[Any] = [] # To store results
116
116
  self.stress_events: List[StressEvent] = []
117
117
  self.seed = seed
118
- if self.seed is not None:
119
- np.random.seed(self.seed) # Set numpy seed for reproducibility
120
- # Potentially set other seeds here if other random modules are used (e.g., random.seed(self.seed))
121
118
  self.safety_config = safety_config or SafetyConfig()
122
119
  self.supervisor = IndependentSupervisor(safety_config=self.safety_config)
123
120
  self.input_validator = InputValidator(safety_config=self.safety_config)
@@ -1,5 +1,5 @@
1
1
  import numpy as np
2
- from typing import Dict, Any, Optional, List, Tuple
2
+ from typing import Any, Dict, List, Optional, Tuple
3
3
 
4
4
  from iints.core.safety.config import SafetyConfig
5
5
  from dataclasses import dataclass
@@ -42,18 +42,18 @@ class IndependentSupervisor:
42
42
  Implements hard safety limits and emergency overrides.
43
43
  """
44
44
 
45
- def __init__(self,
46
- hypoglycemia_threshold=70, # mg/dL
47
- severe_hypoglycemia_threshold=54, # mg/dL
48
- hyperglycemia_threshold=250, # mg/dL
49
- max_insulin_per_bolus=5, # Units
50
- glucose_rate_alarm=5, # mg/dL per minute
51
- max_60min=3.0, # Units per 60 minutes
52
- max_iob=4.0, # Units
53
- trend_stop=-2.0, # mg/dL per minute
54
- hypo_cutoff=70.0, # mg/dL
55
- predicted_hypoglycemia_threshold=60.0, # mg/dL
56
- predicted_hypoglycemia_horizon_minutes=30, # minutes
45
+ def __init__(self,
46
+ hypoglycemia_threshold: float = 70.0, # mg/dL
47
+ severe_hypoglycemia_threshold: float = 54.0, # mg/dL
48
+ hyperglycemia_threshold: float = 250.0, # mg/dL
49
+ max_insulin_per_bolus: float = 5.0, # Units
50
+ glucose_rate_alarm: float = 5.0, # mg/dL per minute
51
+ max_60min: float = 3.0, # Units per 60 minutes
52
+ max_iob: float = 4.0, # Units
53
+ trend_stop: float = -2.0, # mg/dL per minute
54
+ hypo_cutoff: float = 70.0, # mg/dL
55
+ predicted_hypoglycemia_threshold: float = 60.0, # mg/dL
56
+ predicted_hypoglycemia_horizon_minutes: int = 30, # minutes
57
57
  contract_enabled: bool = True,
58
58
  contract_glucose_threshold: float = 90.0,
59
59
  contract_trend_threshold_mgdl_min: float = -1.0,
@@ -95,7 +95,7 @@ class IndependentSupervisor:
95
95
  self.violations: List[SafetyViolation] = []
96
96
  self.emergency_mode = False
97
97
  self.last_iob = 0.0
98
- self.dose_history: List[tuple] = []
98
+ self.dose_history: List[Tuple[float, float]] = []
99
99
 
100
100
  def evaluate_safety(
101
101
  self,
@@ -320,7 +320,7 @@ class IndependentSupervisor:
320
320
  ]
321
321
  }
322
322
 
323
- def reset(self):
323
+ def reset(self) -> None:
324
324
  """Reset supervisor state."""
325
325
  self.glucose_history.clear()
326
326
  self.violations.clear()
@@ -8,9 +8,10 @@ import pandas as pd
8
8
  import yaml
9
9
 
10
10
  from iints.api.base_algorithm import InsulinAlgorithm
11
- from iints.core.patient.models import PatientModel
11
+ from iints.core.patient.patient_factory import PatientFactory
12
12
  from iints.core.patient.profile import PatientProfile
13
13
  from iints.core.simulator import Simulator
14
+ from iints.core.devices.models import SensorModel
14
15
  from iints.core.safety import SafetyConfig
15
16
  from iints.analysis.baseline import run_baseline_comparison, write_baseline_comparison
16
17
  from iints.analysis.reporting import ClinicalReportGenerator
@@ -62,6 +63,11 @@ def run_simulation(
62
63
  algorithm: Union[InsulinAlgorithm, type],
63
64
  scenario: Optional[Union[str, Path, Dict[str, Any]]] = None,
64
65
  patient_config: Union[str, Path, Dict[str, Any], PatientProfile] = "default_patient",
66
+ patient_model_type: str = "auto",
67
+ sensor_noise_std: Optional[float] = None,
68
+ sensor_lag_minutes: Optional[int] = None,
69
+ sensor_dropout_prob: Optional[float] = None,
70
+ sensor_bias: Optional[float] = None,
65
71
  duration_minutes: int = 720,
66
72
  time_step: int = 5,
67
73
  seed: Optional[int] = None,
@@ -81,12 +87,30 @@ def run_simulation(
81
87
  output_path = resolve_output_dir(output_dir, run_id)
82
88
 
83
89
  patient_params = _resolve_patient_config(patient_config)
84
- patient_model = PatientModel(**patient_params)
90
+ patient_model = PatientFactory.create_patient(patient_type=patient_model_type, **patient_params)
85
91
 
86
92
  scenario_payload = _resolve_scenario_payloads(scenario)
87
93
  stress_event_payloads = scenario_payload.get("stress_events", []) if scenario_payload else []
88
94
  effective_safety_config = safety_config or SafetyConfig()
89
95
 
96
+ sensor_model = None
97
+ if any(v is not None for v in (sensor_noise_std, sensor_lag_minutes, sensor_dropout_prob, sensor_bias)):
98
+ sensor_model = SensorModel(
99
+ noise_std=float(sensor_noise_std or 0.0),
100
+ lag_minutes=int(sensor_lag_minutes or 0),
101
+ dropout_prob=float(sensor_dropout_prob or 0.0),
102
+ bias=float(sensor_bias or 0.0),
103
+ seed=resolved_seed,
104
+ )
105
+ elif patient_model_type == "auto":
106
+ sensor_model = SensorModel(
107
+ noise_std=7.0,
108
+ lag_minutes=10,
109
+ dropout_prob=0.0,
110
+ bias=0.0,
111
+ seed=resolved_seed,
112
+ )
113
+
90
114
  simulator = Simulator(
91
115
  patient_model=patient_model,
92
116
  algorithm=algorithm_instance,
@@ -94,6 +118,7 @@ def run_simulation(
94
118
  seed=resolved_seed,
95
119
  safety_config=effective_safety_config,
96
120
  predictor=predictor,
121
+ sensor_model=sensor_model,
97
122
  )
98
123
  for event in build_stress_events(stress_event_payloads):
99
124
  simulator.add_stress_event(event)
@@ -127,6 +152,7 @@ def run_simulation(
127
152
  "metadata": algorithm_instance.get_algorithm_metadata().to_dict(),
128
153
  },
129
154
  "patient_config": patient_params,
155
+ "patient_model_type": patient_model_type,
130
156
  "scenario": scenario_payload,
131
157
  "duration_minutes": duration_minutes,
132
158
  "time_step_minutes": time_step,
@@ -135,6 +161,7 @@ def run_simulation(
135
161
  "export_audit": export_audit,
136
162
  "generate_report": generate_report,
137
163
  "safety_config": asdict(effective_safety_config),
164
+ "sensor_model": sensor_model.get_state() if sensor_model else None,
138
165
  }
139
166
  config_path = output_path / "config.json"
140
167
  write_json(config_path, config_payload)
@@ -202,6 +229,11 @@ def run_full(
202
229
  algorithm: Union[InsulinAlgorithm, type],
203
230
  scenario: Optional[Union[str, Path, Dict[str, Any]]] = None,
204
231
  patient_config: Union[str, Path, Dict[str, Any], PatientProfile] = "default_patient",
232
+ patient_model_type: str = "auto",
233
+ sensor_noise_std: Optional[float] = None,
234
+ sensor_lag_minutes: Optional[int] = None,
235
+ sensor_dropout_prob: Optional[float] = None,
236
+ sensor_bias: Optional[float] = None,
205
237
  duration_minutes: int = 720,
206
238
  time_step: int = 5,
207
239
  seed: Optional[int] = None,
@@ -219,12 +251,30 @@ def run_full(
219
251
  output_path = resolve_output_dir(output_dir, run_id)
220
252
 
221
253
  patient_params = _resolve_patient_config(patient_config)
222
- patient_model = PatientModel(**patient_params)
254
+ patient_model = PatientFactory.create_patient(patient_type=patient_model_type, **patient_params)
223
255
 
224
256
  scenario_payload = _resolve_scenario_payloads(scenario)
225
257
  stress_event_payloads = scenario_payload.get("stress_events", []) if scenario_payload else []
226
258
  effective_safety_config = safety_config or SafetyConfig()
227
259
 
260
+ sensor_model = None
261
+ if any(v is not None for v in (sensor_noise_std, sensor_lag_minutes, sensor_dropout_prob, sensor_bias)):
262
+ sensor_model = SensorModel(
263
+ noise_std=float(sensor_noise_std or 0.0),
264
+ lag_minutes=int(sensor_lag_minutes or 0),
265
+ dropout_prob=float(sensor_dropout_prob or 0.0),
266
+ bias=float(sensor_bias or 0.0),
267
+ seed=resolved_seed,
268
+ )
269
+ elif patient_model_type == "auto":
270
+ sensor_model = SensorModel(
271
+ noise_std=7.0,
272
+ lag_minutes=10,
273
+ dropout_prob=0.0,
274
+ bias=0.0,
275
+ seed=resolved_seed,
276
+ )
277
+
228
278
  simulator = Simulator(
229
279
  patient_model=patient_model,
230
280
  algorithm=algorithm_instance,
@@ -233,6 +283,7 @@ def run_full(
233
283
  enable_profiling=enable_profiling,
234
284
  safety_config=effective_safety_config,
235
285
  predictor=predictor,
286
+ sensor_model=sensor_model,
236
287
  )
237
288
  for event in build_stress_events(stress_event_payloads):
238
289
  simulator.add_stress_event(event)
@@ -274,6 +325,7 @@ def run_full(
274
325
  "generate_report": True,
275
326
  "enable_profiling": enable_profiling,
276
327
  "safety_config": asdict(effective_safety_config),
328
+ "sensor_model": sensor_model.get_state() if sensor_model else None,
277
329
  }
278
330
  config_path = output_path / "config.json"
279
331
  write_json(config_path, config_payload)
@@ -345,7 +397,7 @@ def run_population(
345
397
  max_workers: Optional[int] = None,
346
398
  safety_config: Optional[SafetyConfig] = None,
347
399
  safety_weights: Optional[Dict[str, float]] = None,
348
- patient_model_type: str = "custom",
400
+ patient_model_type: str = "auto",
349
401
  population_cv: Optional[Dict[str, float]] = None,
350
402
  ) -> Dict[str, Any]:
351
403
  """
@@ -7,9 +7,11 @@ from .dataset import (
7
7
  save_parquet,
8
8
  load_dataset,
9
9
  save_dataset,
10
+ compute_dataset_lineage,
10
11
  )
11
12
  from .predictor import LSTMPredictor, load_predictor, PredictorService, load_predictor_service
12
- from .losses import QuantileLoss, SafetyWeightedMSE
13
+ from .losses import QuantileLoss, SafetyWeightedMSE, BandWeightedMSE
14
+ from .metrics import regression_metrics, band_regression_metrics, interval_coverage_metrics
13
15
 
14
16
  __all__ = [
15
17
  "PredictorConfig",
@@ -21,10 +23,15 @@ __all__ = [
21
23
  "save_parquet",
22
24
  "load_dataset",
23
25
  "save_dataset",
26
+ "compute_dataset_lineage",
24
27
  "LSTMPredictor",
25
28
  "load_predictor",
26
29
  "PredictorService",
27
30
  "load_predictor_service",
28
31
  "QuantileLoss",
29
32
  "SafetyWeightedMSE",
33
+ "BandWeightedMSE",
34
+ "regression_metrics",
35
+ "band_regression_metrics",
36
+ "interval_coverage_metrics",
30
37
  ]
@@ -52,7 +52,7 @@ class TrainingConfig:
52
52
  # P3-10: Normalization strategy. Options: "zscore", "robust", "none".
53
53
  normalization: str = "zscore"
54
54
 
55
- # P3-12: Loss function. Options: "mse", "quantile".
55
+ # P3-12: Loss function. Options: "mse", "quantile", "safety_weighted", "band_weighted".
56
56
  # For quantile loss, also set `quantile` (0 < q < 1).
57
57
  loss: str = "mse"
58
58
  quantile: Optional[float] = None # e.g. 0.9 for 90th-percentile upper bound
@@ -66,3 +66,17 @@ class TrainingConfig:
66
66
  safety_weighted_low_threshold: float = 80.0
67
67
  safety_weighted_alpha: float = 2.0
68
68
  safety_weighted_max_weight: float = 4.0
69
+
70
+ # Band-weighted loss (penalize low/high glucose errors)
71
+ band_weighted_low_threshold: float = 70.0
72
+ band_weighted_high_threshold: float = 180.0
73
+ band_weighted_low_weight: float = 2.0
74
+ band_weighted_high_weight: float = 1.5
75
+ band_weighted_max_weight: float = 5.0
76
+
77
+ # Optional: pre-announced meals (clinically common for AID systems).
78
+ # If enabled, create `meal_announcement_feature` by shifting
79
+ # `meal_announcement_column` earlier by `meal_announcement_minutes`.
80
+ meal_announcement_minutes: Optional[float] = None
81
+ meal_announcement_column: str = "carb_intake_grams"
82
+ meal_announcement_feature: str = "meal_announcement_grams"