iints-sdk-python35 1.3.2__py3-none-any.whl → 1.4.0__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 (68) hide show
  1. iints/__init__.py +1 -1
  2. iints/ai/backends/ollama.py +32 -2
  3. iints/ai/mdmp_guard.py +2 -3
  4. iints/ai/prepare.py +1 -1
  5. iints/analysis/clinical_metrics.py +13 -7
  6. iints/analysis/validator.py +10 -4
  7. iints/core/patient/bergman_model.py +3 -2
  8. iints/core/patient/models.py +1 -1
  9. iints/core/patient/patient_factory.py +8 -8
  10. iints/core/patient/profile.py +1 -1
  11. iints/core/safety/config.py +13 -4
  12. iints/core/safety/input_validator.py +12 -7
  13. iints/data/adapter.py +7 -2
  14. iints/data/ingestor.py +7 -2
  15. iints/data/quality_checker.py +12 -4
  16. iints/data/registry.py +23 -1
  17. iints/highlevel.py +1 -1
  18. iints/learning/autonomous_optimizer.py +13 -1
  19. iints/mdmp/backend.py +2 -3
  20. iints/research/predictor.py +9 -1
  21. {iints_sdk_python35-1.3.2.dist-info → iints_sdk_python35-1.4.0.dist-info}/METADATA +23 -17
  22. {iints_sdk_python35-1.3.2.dist-info → iints_sdk_python35-1.4.0.dist-info}/RECORD +67 -26
  23. {iints_sdk_python35-1.3.2.dist-info → iints_sdk_python35-1.4.0.dist-info}/entry_points.txt +1 -0
  24. iints_sdk_python35-1.4.0.dist-info/licenses/LICENSE +173 -0
  25. iints_sdk_python35-1.3.2.dist-info/licenses/LICENSE → iints_sdk_python35-1.4.0.dist-info/licenses/LICENSE-MIT-IINTS-LEGACY +0 -7
  26. iints_sdk_python35-1.4.0.dist-info/licenses/NOTICE +17 -0
  27. iints_sdk_python35-1.4.0.dist-info/top_level.txt +5 -0
  28. mdmp_ai/__init__.py +3 -0
  29. mdmp_ai/lineage.py +198 -0
  30. mdmp_core/__init__.py +133 -0
  31. mdmp_core/audit.py +166 -0
  32. mdmp_core/bias_hooks.py +36 -0
  33. mdmp_core/bundle.py +142 -0
  34. mdmp_core/certification.py +38 -0
  35. mdmp_core/cli.py +1351 -0
  36. mdmp_core/compare.py +56 -0
  37. mdmp_core/conformance.py +429 -0
  38. mdmp_core/contracts.py +164 -0
  39. mdmp_core/crypto.py +372 -0
  40. mdmp_core/data/conformance/vectors/delegation.json +56 -0
  41. mdmp_core/data/conformance/vectors/fingerprint.json +24 -0
  42. mdmp_core/data/conformance/vectors/grading.json +83 -0
  43. mdmp_core/data/conformance/vectors/signing.json +37 -0
  44. mdmp_core/delegate.py +476 -0
  45. mdmp_core/diffing.py +128 -0
  46. mdmp_core/drift.py +203 -0
  47. mdmp_core/exceptions.py +33 -0
  48. mdmp_core/fingerprint.py +92 -0
  49. mdmp_core/fingerprint_store.py +42 -0
  50. mdmp_core/hf.py +60 -0
  51. mdmp_core/keys/mdmp_pub_v1.pem +3 -0
  52. mdmp_core/llm_provenance.py +34 -0
  53. mdmp_core/migrate.py +182 -0
  54. mdmp_core/policy.py +185 -0
  55. mdmp_core/prov.py +31 -0
  56. mdmp_core/registry.py +350 -0
  57. mdmp_core/runner.py +292 -0
  58. mdmp_core/schema_export.py +92 -0
  59. mdmp_core/synthetic.py +30 -0
  60. mdmp_core/trust.py +216 -0
  61. mdmp_core/visualizer.py +61 -0
  62. mdmp_flavors/__init__.py +146 -0
  63. mdmp_integrations/__init__.py +9 -0
  64. mdmp_integrations/dvc.py +30 -0
  65. mdmp_integrations/mlflow.py +14 -0
  66. mdmp_integrations/wandb.py +19 -0
  67. iints_sdk_python35-1.3.2.dist-info/top_level.txt +0 -1
  68. {iints_sdk_python35-1.3.2.dist-info → iints_sdk_python35-1.4.0.dist-info}/WHEEL +0 -0
iints/__init__.py CHANGED
@@ -11,7 +11,7 @@ except ImportError: # pragma: no cover - Python < 3.8 fallback
11
11
  try:
12
12
  __version__ = version("iints-sdk-python35")
13
13
  except PackageNotFoundError: # pragma: no cover - source tree fallback
14
- __version__ = "1.3.2"
14
+ __version__ = "1.4.0"
15
15
 
16
16
  # Note to developers: this SDK is currently maintained by a single author.
17
17
  # Please report bugs via GitHub issues and feel free to contribute fixes via PRs.
@@ -2,9 +2,11 @@ from __future__ import annotations
2
2
 
3
3
  import json
4
4
  import os
5
+ from ipaddress import ip_address
5
6
  from http.client import IncompleteRead, RemoteDisconnected
6
7
  from time import sleep
7
8
  from urllib import error, request
9
+ from urllib.parse import urlparse
8
10
 
9
11
 
10
12
  DEFAULT_OLLAMA_HOST = "http://127.0.0.1:11434"
@@ -35,10 +37,38 @@ class OllamaBackend:
35
37
  timeout_seconds: float = 120.0,
36
38
  ) -> None:
37
39
  self.model_name = model_name
38
- self.base_url = (base_url or os.getenv("OLLAMA_HOST") or DEFAULT_OLLAMA_HOST).rstrip("/")
40
+ raw_base_url = base_url or os.getenv("OLLAMA_HOST") or DEFAULT_OLLAMA_HOST
41
+ self.base_url = self._validate_base_url(raw_base_url)
39
42
  self.timeout_seconds = timeout_seconds
40
43
  self.resolved_model_name: str | None = None
41
44
 
45
+ @staticmethod
46
+ def _is_loopback_host(hostname: str) -> bool:
47
+ if hostname == "localhost":
48
+ return True
49
+ try:
50
+ return ip_address(hostname).is_loopback
51
+ except ValueError:
52
+ return False
53
+
54
+ @classmethod
55
+ def _validate_base_url(cls, raw_base_url: str) -> str:
56
+ parsed = urlparse(raw_base_url)
57
+ if parsed.scheme not in {"http", "https"}:
58
+ raise ValueError(
59
+ "OLLAMA_HOST/base_url must use http or https. Other URL schemes are blocked."
60
+ )
61
+ if not parsed.hostname:
62
+ raise ValueError("OLLAMA_HOST/base_url must include a hostname.")
63
+ if parsed.path not in {"", "/"}:
64
+ raise ValueError("OLLAMA_HOST/base_url must not include a path component.")
65
+ if not cls._is_loopback_host(parsed.hostname) and os.getenv("IINTS_ALLOW_REMOTE_OLLAMA") != "1":
66
+ raise ValueError(
67
+ "Remote Ollama endpoints are disabled by default. "
68
+ "Use localhost/127.0.0.1 or set IINTS_ALLOW_REMOTE_OLLAMA=1 explicitly."
69
+ )
70
+ return raw_base_url.rstrip("/")
71
+
42
72
  def _pull_hint(self) -> str:
43
73
  return f"ollama pull {self.model_name}"
44
74
 
@@ -102,7 +132,7 @@ class OllamaBackend:
102
132
  headers["Content-Type"] = "application/json"
103
133
  req = request.Request(url, data=body, headers=headers, method=method)
104
134
  try:
105
- with request.urlopen(req, timeout=self.timeout_seconds) as response:
135
+ with request.urlopen(req, timeout=self.timeout_seconds) as response: # nosec B310 - base_url is scheme/host validated
106
136
  text = response.read().decode("utf-8")
107
137
  except error.HTTPError as exc:
108
138
  detail = exc.read().decode("utf-8", errors="replace").strip()
iints/ai/mdmp_guard.py CHANGED
@@ -14,9 +14,8 @@ def _load_mdmp_verifier() -> type[Any]:
14
14
  module = importlib.import_module("mdmp_core")
15
15
  except Exception as exc:
16
16
  raise ImportError(
17
- "MDMP verification requires the optional standalone package.\n"
18
- "Install with: pip install iints-sdk-python35[mdmp]\n"
19
- "or: pip install 'mdmp-protocol>=0.3.0'"
17
+ "MDMP verification requires the bundled MDMP crypto support.\n"
18
+ "Install with: pip install iints-sdk-python35[mdmp]"
20
19
  ) from exc
21
20
  verifier_cls = getattr(module, "MDMPVerifier", None)
22
21
  if verifier_cls is None:
iints/ai/prepare.py CHANGED
@@ -245,7 +245,7 @@ def _load_mdmp_signer_tools() -> tuple[type[Any], Any]:
245
245
  module = importlib.import_module("mdmp_core")
246
246
  except Exception as exc:
247
247
  raise ImportError(
248
- "Local AI certification requires the optional standalone MDMP package.\n"
248
+ "Local AI certification requires the bundled MDMP crypto support.\n"
249
249
  "Install with: pip install 'iints-sdk-python35[mdmp]'"
250
250
  ) from exc
251
251
 
@@ -144,6 +144,12 @@ class ClinicalMetricsCalculator:
144
144
  """
145
145
  self.target_range = target_range
146
146
  self.tight_range = tight_range
147
+
148
+ @staticmethod
149
+ def _percentage(mask: pd.Series) -> float:
150
+ if len(mask) == 0:
151
+ return 0.0
152
+ return float(mask.sum()) / float(len(mask)) * 100.0
147
153
 
148
154
  def calculate_tir(self,
149
155
  glucose: pd.Series,
@@ -169,13 +175,13 @@ class ClinicalMetricsCalculator:
169
175
  def calculate_all_tir_metrics(self, glucose: pd.Series) -> Dict[str, float]:
170
176
  """Calculate all TIR-related metrics"""
171
177
  return {
172
- 'tir_70_180': self.calculate_tir(glucose, 70, 180),
173
- 'tir_70_140': self.calculate_tir(glucose, 70, 140),
174
- 'tir_70_110': self.calculate_tir(glucose, 70, 110),
175
- 'tir_below_70': self.calculate_tir(glucose, 0, 70),
176
- 'tir_below_54': self.calculate_tir(glucose, 0, 54),
177
- 'tir_above_180': self.calculate_tir(glucose, 180, 600),
178
- 'tir_above_250': self.calculate_tir(glucose, 250, 600)
178
+ 'tir_70_180': self._percentage((glucose >= 70) & (glucose <= 180)),
179
+ 'tir_70_140': self._percentage((glucose >= 70) & (glucose <= 140)),
180
+ 'tir_70_110': self._percentage((glucose >= 70) & (glucose <= 110)),
181
+ 'tir_below_70': self._percentage(glucose < 70),
182
+ 'tir_below_54': self._percentage(glucose < 54),
183
+ 'tir_above_180': self._percentage(glucose > 180),
184
+ 'tir_above_250': self._percentage(glucose > 250)
179
185
  }
180
186
 
181
187
  def calculate_gmi(self, glucose: pd.Series) -> float:
@@ -4,6 +4,12 @@ from typing import Dict, List, Tuple, Optional, Any
4
4
  from dataclasses import dataclass
5
5
  from enum import Enum
6
6
 
7
+ from iints.core.safety.config import (
8
+ SENSOR_GLUCOSE_MAX_MGDL,
9
+ SENSOR_GLUCOSE_MIN_MGDL,
10
+ SENSOR_MAX_GLUCOSE_RATE_PER_MIN_MGDL,
11
+ )
12
+
7
13
  class ReliabilityLevel(Enum):
8
14
  HIGH = "high"
9
15
  MEDIUM = "medium"
@@ -22,9 +28,9 @@ class DataIntegrityValidator:
22
28
  """Validates data integrity for reverse engineering analysis."""
23
29
 
24
30
  def __init__(self):
25
- # Physiological limits
26
- self.max_glucose_rate = 10 # mg/dL per minute
27
- self.glucose_range = (20, 600) # Physiologically possible range
31
+ # Broad CGM/sensor fail-soft limits
32
+ self.max_glucose_rate = SENSOR_MAX_GLUCOSE_RATE_PER_MIN_MGDL
33
+ self.glucose_range = (SENSOR_GLUCOSE_MIN_MGDL, SENSOR_GLUCOSE_MAX_MGDL)
28
34
  self.max_insulin_per_step = 5.0 # Units per 5-min step
29
35
 
30
36
  def validate_glucose_data(self, glucose_values: List[float], timestamps: List[float]) -> ValidationResult:
@@ -270,4 +276,4 @@ class ReverseEngineeringValidator:
270
276
  "issues": all_issues,
271
277
  "warnings": all_warnings,
272
278
  "category_scores": {category: result.reliability_score for category, result in validation_results.items()}
273
- }
279
+ }
@@ -46,7 +46,7 @@ class BergmanParameters:
46
46
  n: float = 0.23 # 1/min — fractional insulin degradation
47
47
  Ib: float = 7.0 # mU/L — basal plasma insulin
48
48
  Vi: float = 0.05 # L/kg — insulin distribution volume
49
- gamma: float = 0.004 # (mU/L)/(mg/dL)/min — endogenous secretion gain
49
+ gamma: float = 0.0 # (mU/L)/(mg/dL)/min — endogenous secretion gain (0 for T1D default)
50
50
  h: float = 80.0 # mg/dL — secretion glucose threshold
51
51
 
52
52
  # --- Gut absorption ---
@@ -330,7 +330,8 @@ class BergmanPatientModel:
330
330
  dXdt = -p.p2 * X + p.p3 * max(I - p.Ib, 0.0)
331
331
 
332
332
  # --- dI/dt ---
333
- # Endogenous pancreatic secretion (blunted in T1D, but kept for generality)
333
+ # T1D defaults keep endogenous secretion disabled (gamma=0). Override
334
+ # gamma explicitly if you intentionally want residual beta-cell function.
334
335
  secretion = p.gamma * max(G - p.h, 0.0)
335
336
  dIdt = -p.n * (I - p.Ib) + secretion + u_insulin_mu_per_min / Vi_abs
336
337
 
@@ -14,7 +14,7 @@ class CustomPatientModel:
14
14
  This model is intended for educational and stress-testing purposes, not for clinical accuracy.
15
15
  """
16
16
  def __init__(self, basal_insulin_rate: float = 0.8, insulin_sensitivity: float = 50.0,
17
- carb_factor: float = 10.0, glucose_decay_rate: float = 0.002,
17
+ carb_factor: float = 10.0, glucose_decay_rate: float = 0.05,
18
18
  initial_glucose: float = 120.0, glucose_absorption_rate: float = 0.03,
19
19
  basal_glucose_target: Optional[float] = None,
20
20
  insulin_action_duration: float = 300.0, # minutes, e.g., 5 hours
@@ -63,14 +63,14 @@ class PatientFactory:
63
63
  """Get a diverse set of patients for population studies."""
64
64
  if not SIMGLUCOSE_AVAILABLE:
65
65
  # Create diverse custom patients with different parameters
66
- return [
67
- CustomPatientModel(initial_glucose=120, insulin_sensitivity=40), # High sensitivity
68
- CustomPatientModel(initial_glucose=120, insulin_sensitivity=60), # Low sensitivity
69
- CustomPatientModel(initial_glucose=120, carb_factor=8), # Fast carb absorption
70
- CustomPatientModel(initial_glucose=120, carb_factor=12), # Slow carb absorption
71
- CustomPatientModel(initial_glucose=120, glucose_decay_rate=0.0015), # Slow metabolism
72
- CustomPatientModel(initial_glucose=120, glucose_decay_rate=0.0035), # Fast metabolism
73
- ]
66
+ return [
67
+ CustomPatientModel(initial_glucose=120, insulin_sensitivity=40), # High sensitivity
68
+ CustomPatientModel(initial_glucose=120, insulin_sensitivity=60), # Low sensitivity
69
+ CustomPatientModel(initial_glucose=120, carb_factor=8), # Fast carb absorption
70
+ CustomPatientModel(initial_glucose=120, carb_factor=12), # Slow carb absorption
71
+ CustomPatientModel(initial_glucose=120, glucose_decay_rate=0.03), # Slower homeostatic drift
72
+ CustomPatientModel(initial_glucose=120, glucose_decay_rate=0.07), # Faster homeostatic drift
73
+ ]
74
74
  else:
75
75
  # Use FDA-approved virtual patients
76
76
  selected_patients = [
@@ -18,7 +18,7 @@ class PatientProfile:
18
18
  dawn_end_hour: float = 8.0
19
19
 
20
20
  # Advanced knobs (optional)
21
- glucose_decay_rate: float = 0.002
21
+ glucose_decay_rate: float = 0.05
22
22
  glucose_absorption_rate: float = 0.03
23
23
  insulin_action_duration: float = 300.0
24
24
  insulin_peak_time: float = 75.0
@@ -2,16 +2,25 @@ from __future__ import annotations
2
2
 
3
3
  from dataclasses import dataclass
4
4
 
5
+ SENSOR_GLUCOSE_MIN_MGDL = 40.0
6
+ SENSOR_GLUCOSE_MAX_MGDL = 500.0
7
+ SENSOR_MAX_GLUCOSE_DELTA_PER_5_MIN_MGDL = 20.0
8
+ SENSOR_MAX_GLUCOSE_RATE_PER_MIN_MGDL = 4.0
9
+ SIMULATION_GLUCOSE_FLOOR_MGDL = 20.0
10
+ SIMULATION_GLUCOSE_CEILING_MGDL = 600.0
11
+
5
12
 
6
13
  @dataclass
7
14
  class SafetyConfig:
8
15
  """
9
16
  Central safety configuration for simulator, input validation, and supervisor.
10
17
  """
11
- # Input validation limits
12
- min_glucose: float = 20.0
13
- max_glucose: float = 600.0
14
- max_glucose_delta_per_5_min: float = 35.0
18
+ # Broad CGM/sensor plausibility limits.
19
+ # These are intentionally device-aware fail-soft bounds, not a claim about
20
+ # the full physiologic envelope of blood glucose inside the simulator.
21
+ min_glucose: float = SENSOR_GLUCOSE_MIN_MGDL
22
+ max_glucose: float = SENSOR_GLUCOSE_MAX_MGDL
23
+ max_glucose_delta_per_5_min: float = SENSOR_MAX_GLUCOSE_DELTA_PER_5_MIN_MGDL
15
24
 
16
25
  # Supervisor thresholds
17
26
  hypoglycemia_threshold: float = 70.0
@@ -1,6 +1,11 @@
1
1
  from typing import Any, Dict, Optional
2
2
 
3
- from iints.core.safety.config import SafetyConfig
3
+ from iints.core.safety.config import (
4
+ SENSOR_GLUCOSE_MAX_MGDL,
5
+ SENSOR_GLUCOSE_MIN_MGDL,
6
+ SENSOR_MAX_GLUCOSE_DELTA_PER_5_MIN_MGDL,
7
+ SafetyConfig,
8
+ )
4
9
 
5
10
  class InputValidator:
6
11
  """
@@ -9,16 +14,16 @@ class InputValidator:
9
14
  This component makes the system robust against common sensor errors.
10
15
  """
11
16
  def __init__(self,
12
- min_glucose: float = 20.0,
13
- max_glucose: float = 600.0,
14
- max_glucose_delta_per_5_min: float = 35.0,
17
+ min_glucose: float = SENSOR_GLUCOSE_MIN_MGDL,
18
+ max_glucose: float = SENSOR_GLUCOSE_MAX_MGDL,
19
+ max_glucose_delta_per_5_min: float = SENSOR_MAX_GLUCOSE_DELTA_PER_5_MIN_MGDL,
15
20
  safety_config: Optional[SafetyConfig] = None):
16
21
  """
17
22
  Initializes the validator with plausible biological limits.
18
23
 
19
24
  Args:
20
- min_glucose (float): The absolute minimum plausible glucose value (mg/dL).
21
- max_glucose (float): The absolute maximum plausible glucose value (mg/dL).
25
+ min_glucose (float): Broad fail-soft lower bound for incoming CGM/sensor values (mg/dL).
26
+ max_glucose (float): Broad fail-soft upper bound for incoming CGM/sensor values (mg/dL).
22
27
  max_glucose_delta_per_5_min (float): The maximum plausible change in glucose
23
28
  over a 5-minute period (mg/dL).
24
29
  """
@@ -62,7 +67,7 @@ class InputValidator:
62
67
  Raises:
63
68
  ValueError: If the value is outside biological plausibility limits.
64
69
  """
65
- # 1. Absolute biological plausibility check
70
+ # 1. Broad CGM/sensor plausibility check
66
71
  if not (self.min_glucose <= glucose_value <= self.max_glucose):
67
72
  raise ValueError(
68
73
  f"BIOLOGICAL_PLAUSIBILITY_ERROR: Glucose {glucose_value} mg/dL is outside the "
iints/data/adapter.py CHANGED
@@ -10,6 +10,8 @@ import numpy as np
10
10
  from pathlib import Path
11
11
  from typing import Dict, List, Optional, Union, Any
12
12
 
13
+ from iints.core.safety.config import SENSOR_GLUCOSE_MAX_MGDL, SENSOR_GLUCOSE_MIN_MGDL
14
+
13
15
  class DataAdapter:
14
16
  """Universal data adapter for IINTS-AF framework"""
15
17
 
@@ -51,8 +53,11 @@ class DataAdapter:
51
53
  raise ValueError(f"Missing required columns: {missing_cols}")
52
54
 
53
55
  # Validate glucose range
54
- if df['glucose'].min() < 20 or df['glucose'].max() > 600:
55
- raise ValueError("Glucose values outside physiological range (20-600 mg/dL)")
56
+ if df['glucose'].min() < SENSOR_GLUCOSE_MIN_MGDL or df['glucose'].max() > SENSOR_GLUCOSE_MAX_MGDL:
57
+ raise ValueError(
58
+ "Glucose values outside broad CGM/sensor-valid range "
59
+ f"({int(SENSOR_GLUCOSE_MIN_MGDL)}-{int(SENSOR_GLUCOSE_MAX_MGDL)} mg/dL)"
60
+ )
56
61
 
57
62
  def load_ohio_dataset(self, patient_id: str) -> pd.DataFrame:
58
63
  """Load Ohio T1DM dataset with clinical benchmarks"""
iints/data/ingestor.py CHANGED
@@ -3,6 +3,8 @@ from pathlib import Path
3
3
  from typing import Dict, Any, Union, Optional
4
4
  import yaml
5
5
 
6
+ from iints.core.safety.config import SENSOR_GLUCOSE_MAX_MGDL, SENSOR_GLUCOSE_MIN_MGDL
7
+
6
8
  class DataIngestor:
7
9
  """
8
10
  Standardized Data Bridge for ingesting various diabetes datasets into a
@@ -64,8 +66,11 @@ class DataIngestor:
64
66
 
65
67
  # Basic quality checks (from DATA_SCHEMA.md)
66
68
  if 'glucose' in df.columns:
67
- if not ((df['glucose'] >= 20) & (df['glucose'] <= 600)).all():
68
- raise ValueError("Glucose values outside acceptable range (20-600 mg/dL)")
69
+ if not ((df['glucose'] >= SENSOR_GLUCOSE_MIN_MGDL) & (df['glucose'] <= SENSOR_GLUCOSE_MAX_MGDL)).all():
70
+ raise ValueError(
71
+ "Glucose values outside acceptable CGM/sensor range "
72
+ f"({int(SENSOR_GLUCOSE_MIN_MGDL)}-{int(SENSOR_GLUCOSE_MAX_MGDL)} mg/dL)"
73
+ )
69
74
  if 'insulin' in df.columns and not df['insulin'].isna().all():
70
75
  if not ((df['insulin'] >= 0) & (df['insulin'] <= 50)).all():
71
76
  raise ValueError("Insulin values outside acceptable range (0-50 units)")
@@ -10,6 +10,12 @@ from datetime import datetime, timedelta
10
10
  import pandas as pd
11
11
  import numpy as np
12
12
 
13
+ from iints.core.safety.config import (
14
+ SENSOR_GLUCOSE_MAX_MGDL,
15
+ SENSOR_GLUCOSE_MIN_MGDL,
16
+ SENSOR_MAX_GLUCOSE_RATE_PER_MIN_MGDL,
17
+ )
18
+
13
19
 
14
20
  @dataclass
15
21
  class QualityReport:
@@ -100,14 +106,16 @@ class DataQualityChecker:
100
106
 
101
107
  # Physiological limits for glucose values
102
108
  GLUCOSE_LIMITS = {
103
- 'minimum': 20, # mg/dL - physiologically possible minimum
104
- 'maximum': 600, # mg/dL - physiologically possible maximum
109
+ 'minimum': SENSOR_GLUCOSE_MIN_MGDL, # mg/dL - broad CGM/sensor-valid minimum
110
+ 'maximum': SENSOR_GLUCOSE_MAX_MGDL, # mg/dL - broad CGM/sensor-valid maximum
105
111
  'critical_low': 54, # mg/dL - clinically significant low
106
112
  'critical_high': 350 # mg/dL - clinically significant high
107
113
  }
108
-
114
+
109
115
  PHYSIOLOGICAL_RATES = {
110
- 'max_glucose_change_per_min': 19.9 # mg/dL/min - Detecting changes of 20 mg/dL/min or more
116
+ # Broad fail-soft CGM plausibility cap; common CGM trend-arrow systems
117
+ # already treat >2 mg/dL/min as "rapid", so this keeps a conservative margin.
118
+ 'max_glucose_change_per_min': SENSOR_MAX_GLUCOSE_RATE_PER_MIN_MGDL
111
119
  }
112
120
 
113
121
  # Expected sampling intervals (in minutes)
iints/data/registry.py CHANGED
@@ -5,8 +5,10 @@ import urllib.request
5
5
  import zipfile
6
6
  import hashlib
7
7
  import shutil
8
+ from ipaddress import ip_address
8
9
  from pathlib import Path
9
10
  from typing import Any, Dict, List, Optional, IO, cast
11
+ from urllib.parse import urlparse
10
12
 
11
13
  try: # Python 3.9+
12
14
  from importlib.resources import files
@@ -54,10 +56,30 @@ def list_dataset_ids() -> List[str]:
54
56
  return ids
55
57
 
56
58
 
59
+ def _is_loopback_host(hostname: str) -> bool:
60
+ if hostname == "localhost":
61
+ return True
62
+ try:
63
+ return ip_address(hostname).is_loopback
64
+ except ValueError:
65
+ return False
66
+
67
+
68
+ def _validate_download_url(url: str) -> str:
69
+ parsed = urlparse(url)
70
+ if parsed.scheme == "https":
71
+ return url
72
+ if parsed.scheme == "http" and parsed.hostname and _is_loopback_host(parsed.hostname):
73
+ return url
74
+ raise DatasetFetchError(
75
+ "Dataset download URL must use https, or http only for localhost/loopback development mirrors."
76
+ )
77
+
78
+
57
79
  def _download_file(url: str, output_path: Path) -> Path:
58
80
  output_path.parent.mkdir(parents=True, exist_ok=True)
59
81
  try:
60
- urllib.request.urlretrieve(url, output_path)
82
+ urllib.request.urlretrieve(_validate_download_url(url), output_path) # nosec B310 - URL is scheme validated before download
61
83
  except Exception as exc:
62
84
  raise DatasetFetchError(f"Failed to download {url}: {exc}") from exc
63
85
  return output_path
iints/highlevel.py CHANGED
@@ -442,7 +442,7 @@ def run_population(
442
442
  dawn_phenomenon_strength=patient_params.get("dawn_phenomenon_strength", 0.0),
443
443
  dawn_start_hour=patient_params.get("dawn_start_hour", 4.0),
444
444
  dawn_end_hour=patient_params.get("dawn_end_hour", 8.0),
445
- glucose_decay_rate=patient_params.get("glucose_decay_rate", 0.002),
445
+ glucose_decay_rate=patient_params.get("glucose_decay_rate", 0.05),
446
446
  glucose_absorption_rate=patient_params.get("glucose_absorption_rate", 0.03),
447
447
  insulin_peak_time=patient_params.get("insulin_peak_time", 75.0),
448
448
  meal_mismatch_epsilon=patient_params.get("meal_mismatch_epsilon", 1.0),
@@ -15,6 +15,18 @@ except Exception: # pragma: no cover
15
15
  _TORCH_AVAILABLE = False
16
16
 
17
17
 
18
+ def _safe_torch_load_weights(path: str):
19
+ if torch is None: # pragma: no cover
20
+ raise ImportError("Torch required for model loading.")
21
+ try:
22
+ return torch.load(path, map_location="cpu", weights_only=True)
23
+ except TypeError as exc:
24
+ raise RuntimeError(
25
+ "This PyTorch build does not support secure weights-only loading. "
26
+ "Upgrade torch to a version that supports `weights_only=True`."
27
+ ) from exc
28
+
29
+
18
30
  @dataclass
19
31
  class ClinicalConstraints:
20
32
  """Physiological constraints based on medical literature."""
@@ -98,7 +110,7 @@ if _TORCH_AVAILABLE:
98
110
  model = LSTMModel(input_size=7, hidden_size=50, output_size=1)
99
111
 
100
112
  if os.path.exists(self.model_path):
101
- model.load_state_dict(torch.load(self.model_path))
113
+ model.load_state_dict(_safe_torch_load_weights(self.model_path))
102
114
 
103
115
  improved_model = self._clinical_fine_tuning(model, clinical_X, clinical_y)
104
116
  safety_score = self._validate_clinical_safety(improved_model, clinical_X, clinical_y)
iints/mdmp/backend.py CHANGED
@@ -114,9 +114,8 @@ def get_backend() -> str:
114
114
  if requested in {BACKEND_MDMP, "mdmp", "external"}:
115
115
  if not is_mdmp_available():
116
116
  raise ImportError(
117
- "mdmp_core not found.\n"
118
- "Install with: pip install iints-sdk-python35[mdmp]\n"
119
- "or: pip install 'mdmp-protocol>=0.3.0'"
117
+ "Bundled MDMP support is not available in this environment.\n"
118
+ "Install with: pip install iints-sdk-python35[mdmp]"
120
119
  )
121
120
  return BACKEND_MDMP
122
121
  if requested == "auto" and is_mdmp_available():
@@ -293,7 +293,15 @@ def load_predictor(model_path: Path) -> Tuple["LSTMPredictor", dict]:
293
293
  raise ImportError(
294
294
  "Torch is required for predictor loading. Install with `pip install iints-sdk-python35[research]`."
295
295
  ) from _IMPORT_ERROR
296
- payload = torch.load(model_path, map_location="cpu", weights_only=False)
296
+ try:
297
+ payload = torch.load(model_path, map_location="cpu", weights_only=True)
298
+ except TypeError as exc:
299
+ raise RuntimeError(
300
+ "This PyTorch build does not support secure checkpoint loading. "
301
+ "Upgrade torch to a version with `weights_only=True` support."
302
+ ) from exc
303
+ if not isinstance(payload, dict):
304
+ raise RuntimeError("Predictor checkpoint must decode to a dictionary payload.")
297
305
  config = payload["config"]
298
306
  model = LSTMPredictor(
299
307
  input_size=config["input_size"],
@@ -1,20 +1,22 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: iints-sdk-python35
3
- Version: 1.3.2
3
+ Version: 1.4.0
4
4
  Summary: A pre-clinical Edge-AI SDK for diabetes management validation.
5
5
  Author-email: Rune Bobbaers <rune.bobbaers@gmail.com>
6
+ License-Expression: Apache-2.0
6
7
  Project-URL: Homepage, https://github.com/python35/IINTS-SDK
7
8
  Classifier: Programming Language :: Python :: 3
8
9
  Classifier: Programming Language :: Python :: 3.10
9
10
  Classifier: Programming Language :: Python :: 3.11
10
11
  Classifier: Programming Language :: Python :: 3.12
11
12
  Classifier: Programming Language :: Python :: 3.13
12
- Classifier: License :: OSI Approved :: MIT License
13
13
  Classifier: Operating System :: OS Independent
14
14
  Classifier: Development Status :: 5 - Production/Stable
15
15
  Requires-Python: >=3.10
16
16
  Description-Content-Type: text/markdown
17
17
  License-File: LICENSE
18
+ License-File: NOTICE
19
+ License-File: LICENSE-MIT-IINTS-LEGACY
18
20
  Requires-Dist: fpdf2>=2.8.0
19
21
  Requires-Dist: matplotlib>=3.5.0
20
22
  Requires-Dist: numpy>=1.24.0
@@ -46,7 +48,7 @@ Requires-Dist: h5py>=3.10.0; extra == "research"
46
48
  Requires-Dist: onnx>=1.16.0; extra == "research"
47
49
  Requires-Dist: onnxscript>=0.1.0; extra == "research"
48
50
  Provides-Extra: mdmp
49
- Requires-Dist: mdmp-protocol>=0.3.0; extra == "mdmp"
51
+ Requires-Dist: cryptography>=42.0.0; extra == "mdmp"
50
52
  Dynamic: license-file
51
53
 
52
54
  # IINTS-AF SDK
@@ -83,6 +85,8 @@ iints presets run --name baseline_t1d --algo algorithms/example_algorithm.py
83
85
 
84
86
  If you want the clearest install/path rules first, read:
85
87
  - `docs/INSTALLATION.md`
88
+ - `docs/AI_ASSISTANT.md` for the small Ollama setup and SDK linking flow
89
+ - `docs/EVIDENCE_BASE.md` for the full medical + technical source legend
86
90
 
87
91
  Short rule:
88
92
  - installed `iints ...` commands can run from any folder
@@ -160,6 +164,12 @@ ollama pull ministral-3:8b
160
164
  iints ai models
161
165
  ```
162
166
 
167
+ If you want the short "install Ollama + link it to the SDK" guide, read:
168
+ - `docs/AI_ASSISTANT.md`
169
+
170
+ If you want the full legend of medical, dataset, runtime, and emulator references used across the project docs:
171
+ - `docs/EVIDENCE_BASE.md`
172
+
163
173
  Recommended first-time setup:
164
174
 
165
175
  ```bash
@@ -345,13 +355,13 @@ iints mdmp validate mdmp_contract.yaml data/my_cgm.csv --output-json results/mdm
345
355
  iints mdmp visualizer results/mdmp_report.json --output-html results/mdmp_dashboard.html
346
356
  ```
347
357
 
348
- Use standalone MDMP backend (optional):
358
+ Use bundled `mdmp_core` backend (optional):
349
359
 
350
360
  ```bash
351
361
  export IINTS_MDMP_BACKEND=mdmp_core
352
362
  ```
353
363
 
354
- Staleness / lineage checks (standalone MDMP CLI):
364
+ Staleness / lineage checks via the bundled `mdmp` CLI:
355
365
 
356
366
  ```bash
357
367
  mdmp fingerprint-record data/my_cgm.csv --output-json results/fingerprint.json --expires-days 365
@@ -361,21 +371,17 @@ mdmp registry init --registry registry/mdmp_registry.json
361
371
  mdmp registry push --registry registry/mdmp_registry.json --report results/mdmp_report.json
362
372
  ```
363
373
 
364
- ## Dual Repo Workflow
365
- - SDK repo: `python35/IINTS-SDK`
366
- - MDMP repo: `python35/MDMP`
374
+ ## Bundled MDMP
367
375
 
368
- Local helper scripts:
369
- - `tools/dev/dual_repo_status.sh`
370
- - `tools/dev/dual_repo_commit_push.sh`
376
+ MDMP now ships inside the SDK, so the SDK no longer depends on a separate public MDMP repository checkout.
371
377
 
372
- Full process: `docs/DUAL_REPO_WORKFLOW.md`
378
+ That means:
379
+ - `iints mdmp ...` stays available
380
+ - the bundled `mdmp` CLI can still be used
381
+ - local AI signing and verification can still use `mdmp_core`
373
382
 
374
- MDMP sync CI gate:
375
- - `.github/workflows/mdmp-sync.yml`
376
- - Uses private-repo checkout when `MDMP_REPO_TOKEN` is configured.
377
- - Falls back to `mdmp-protocol` from PyPI when checkout is unavailable.
378
- - Auto dependency updates for MDMP are handled via Dependabot (`.github/dependabot.yml`).
383
+ Reference:
384
+ - `docs/DUAL_REPO_WORKFLOW.md`
379
385
 
380
386
  ## Tools Layout
381
387