iints-sdk-python35 1.2.0__py3-none-any.whl → 1.3.1__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.
- iints/__init__.py +3 -1
- iints/analysis/__init__.py +2 -0
- iints/analysis/booth_demo.py +448 -0
- iints/cli/cli.py +66 -0
- {iints_sdk_python35-1.2.0.dist-info → iints_sdk_python35-1.3.1.dist-info}/METADATA +44 -2
- {iints_sdk_python35-1.2.0.dist-info → iints_sdk_python35-1.3.1.dist-info}/RECORD +10 -9
- {iints_sdk_python35-1.2.0.dist-info → iints_sdk_python35-1.3.1.dist-info}/WHEEL +0 -0
- {iints_sdk_python35-1.2.0.dist-info → iints_sdk_python35-1.3.1.dist-info}/entry_points.txt +0 -0
- {iints_sdk_python35-1.2.0.dist-info → iints_sdk_python35-1.3.1.dist-info}/licenses/LICENSE +0 -0
- {iints_sdk_python35-1.2.0.dist-info → iints_sdk_python35-1.3.1.dist-info}/top_level.txt +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.
|
|
14
|
+
__version__ = "1.3.1"
|
|
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.
|
|
@@ -71,6 +71,7 @@ from .data.tidepool import TidepoolClient, load_openapi_spec
|
|
|
71
71
|
from .data.guardians import mdmp_gate, MDMPGateError
|
|
72
72
|
from .data.synthetic_mirror import generate_synthetic_mirror, SyntheticMirrorArtifact
|
|
73
73
|
from .analysis.metrics import generate_benchmark_metrics # Added for benchmark
|
|
74
|
+
from .analysis.booth_demo import build_booth_demo
|
|
74
75
|
from .analysis.carelink_workbench import build_carelink_workbench
|
|
75
76
|
from .analysis.poster import generate_results_poster
|
|
76
77
|
from .analysis.reporting import ClinicalReportGenerator
|
|
@@ -185,6 +186,7 @@ __all__ = [
|
|
|
185
186
|
"SyntheticMirrorArtifact",
|
|
186
187
|
# Analysis Metrics
|
|
187
188
|
"generate_benchmark_metrics",
|
|
189
|
+
"build_booth_demo",
|
|
188
190
|
"build_carelink_workbench",
|
|
189
191
|
"ClinicalReportGenerator",
|
|
190
192
|
"EnergyEstimate",
|
iints/analysis/__init__.py
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
from .clinical_metrics import ClinicalMetricsCalculator, ClinicalMetricsResult
|
|
2
2
|
from .baseline import compute_metrics, run_baseline_comparison, write_baseline_comparison
|
|
3
|
+
from .booth_demo import build_booth_demo
|
|
3
4
|
from .carelink_workbench import build_carelink_workbench
|
|
4
5
|
from .poster import generate_results_poster
|
|
5
6
|
from .reporting import ClinicalReportGenerator
|
|
6
7
|
|
|
7
8
|
__all__ = [
|
|
9
|
+
"build_booth_demo",
|
|
8
10
|
"build_carelink_workbench",
|
|
9
11
|
"ClinicalMetricsCalculator",
|
|
10
12
|
"ClinicalMetricsResult",
|
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any, Callable
|
|
7
|
+
|
|
8
|
+
from iints.ai.prepare import prepare_ai_ready_artifacts
|
|
9
|
+
from iints.analysis.poster import generate_results_poster
|
|
10
|
+
from iints.core.algorithms.mock_algorithms import RunawayAIAlgorithm
|
|
11
|
+
from iints.core.algorithms.pid_controller import PIDController
|
|
12
|
+
from iints.highlevel import run_full
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass(frozen=True)
|
|
16
|
+
class BoothScenarioSpec:
|
|
17
|
+
slug: str
|
|
18
|
+
label: str
|
|
19
|
+
headline: str
|
|
20
|
+
jury_takeaway: str
|
|
21
|
+
scenario: dict[str, Any]
|
|
22
|
+
algorithm_factory: Callable[[], Any]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _scenario_specs() -> list[BoothScenarioSpec]:
|
|
26
|
+
return [
|
|
27
|
+
BoothScenarioSpec(
|
|
28
|
+
slug="01_normal_run",
|
|
29
|
+
label="Normal Run",
|
|
30
|
+
headline="The controller keeps glucose in range during a calm day.",
|
|
31
|
+
jury_takeaway=(
|
|
32
|
+
"This is the control case. It shows the SDK can simulate a realistic closed-loop day "
|
|
33
|
+
"and produce clean, auditable outputs."
|
|
34
|
+
),
|
|
35
|
+
scenario={
|
|
36
|
+
"scenario_name": "Booth Demo - Normal Run",
|
|
37
|
+
"schema_version": "1.1",
|
|
38
|
+
"scenario_version": "1.0",
|
|
39
|
+
"description": "A calm day with one moderate meal to show normal controller behavior.",
|
|
40
|
+
"stress_events": [
|
|
41
|
+
{
|
|
42
|
+
"start_time": 45,
|
|
43
|
+
"event_type": "meal",
|
|
44
|
+
"value": 35,
|
|
45
|
+
"reported_value": 35,
|
|
46
|
+
"absorption_delay_minutes": 10,
|
|
47
|
+
"duration": 45,
|
|
48
|
+
}
|
|
49
|
+
],
|
|
50
|
+
},
|
|
51
|
+
algorithm_factory=PIDController,
|
|
52
|
+
),
|
|
53
|
+
BoothScenarioSpec(
|
|
54
|
+
slug="02_meal_stress_test",
|
|
55
|
+
label="Meal Stress Test",
|
|
56
|
+
headline="The controller reacts to a harder day with meals and exercise.",
|
|
57
|
+
jury_takeaway=(
|
|
58
|
+
"This is the stress case. We deliberately add larger disturbances and show that the "
|
|
59
|
+
"system remains explainable and clinically readable."
|
|
60
|
+
),
|
|
61
|
+
scenario={
|
|
62
|
+
"scenario_name": "Booth Demo - Meal Stress Test",
|
|
63
|
+
"schema_version": "1.1",
|
|
64
|
+
"scenario_version": "1.0",
|
|
65
|
+
"description": "Two meal challenges plus exercise to stress the controller.",
|
|
66
|
+
"stress_events": [
|
|
67
|
+
{
|
|
68
|
+
"start_time": 45,
|
|
69
|
+
"event_type": "meal",
|
|
70
|
+
"value": 70,
|
|
71
|
+
"reported_value": 70,
|
|
72
|
+
"absorption_delay_minutes": 15,
|
|
73
|
+
"duration": 75,
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"start_time": 150,
|
|
77
|
+
"event_type": "meal",
|
|
78
|
+
"value": 45,
|
|
79
|
+
"reported_value": 45,
|
|
80
|
+
"absorption_delay_minutes": 10,
|
|
81
|
+
"duration": 50,
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"start_time": 210,
|
|
85
|
+
"event_type": "exercise",
|
|
86
|
+
"value": 0.5,
|
|
87
|
+
"duration": 35,
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
},
|
|
91
|
+
algorithm_factory=PIDController,
|
|
92
|
+
),
|
|
93
|
+
BoothScenarioSpec(
|
|
94
|
+
slug="03_supervisor_override",
|
|
95
|
+
label="Supervisor Override",
|
|
96
|
+
headline="A deliberately unsafe AI is blocked by the safety supervisor.",
|
|
97
|
+
jury_takeaway=(
|
|
98
|
+
"This is the safety case. We intentionally use a bad algorithm so the audience can see "
|
|
99
|
+
"that the supervisor prevents dangerous insulin commands and records why."
|
|
100
|
+
),
|
|
101
|
+
scenario={
|
|
102
|
+
"scenario_name": "Booth Demo - Supervisor Override",
|
|
103
|
+
"schema_version": "1.1",
|
|
104
|
+
"scenario_version": "1.0",
|
|
105
|
+
"description": "A chaos run that forces unsafe insulin requests during a falling glucose phase.",
|
|
106
|
+
"stress_events": [
|
|
107
|
+
{
|
|
108
|
+
"start_time": 30,
|
|
109
|
+
"event_type": "meal",
|
|
110
|
+
"value": 60,
|
|
111
|
+
"reported_value": 60,
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
"start_time": 120,
|
|
115
|
+
"event_type": "exercise",
|
|
116
|
+
"value": 0.8,
|
|
117
|
+
"duration": 60,
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"start_time": 200,
|
|
121
|
+
"event_type": "sensor_error",
|
|
122
|
+
"value": 180,
|
|
123
|
+
},
|
|
124
|
+
],
|
|
125
|
+
},
|
|
126
|
+
algorithm_factory=lambda: RunawayAIAlgorithm(max_bolus=5.0),
|
|
127
|
+
),
|
|
128
|
+
]
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _write_json(path: Path, payload: dict[str, Any]) -> None:
|
|
132
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
133
|
+
path.write_text(json.dumps(payload, indent=2, sort_keys=True), encoding="utf-8")
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _write_text(path: Path, content: str) -> None:
|
|
137
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
138
|
+
path.write_text(content, encoding="utf-8")
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _build_jury_brief(
|
|
142
|
+
*,
|
|
143
|
+
output_dir: Path,
|
|
144
|
+
scenario_specs: list[BoothScenarioSpec],
|
|
145
|
+
poster_png: Path,
|
|
146
|
+
poster_summary_json: Path,
|
|
147
|
+
run_outputs: dict[str, dict[str, Any]],
|
|
148
|
+
ai_outputs: dict[str, str],
|
|
149
|
+
ai_status: str,
|
|
150
|
+
) -> str:
|
|
151
|
+
lines: list[str] = [
|
|
152
|
+
"# IINTS-AF Booth Demo",
|
|
153
|
+
"",
|
|
154
|
+
"## 30-second intro",
|
|
155
|
+
"",
|
|
156
|
+
(
|
|
157
|
+
"IINTS-AF is a safety-first SDK for testing insulin-delivery algorithms. "
|
|
158
|
+
"In this demo we show three stories: normal control, stress handling, and a safety supervisor "
|
|
159
|
+
"blocking an unsafe AI recommendation."
|
|
160
|
+
),
|
|
161
|
+
"",
|
|
162
|
+
"## Live flow",
|
|
163
|
+
"",
|
|
164
|
+
"1. Run the script and point out that it creates three full simulation bundles.",
|
|
165
|
+
f"2. Open the poster: `{poster_png}`",
|
|
166
|
+
"3. Walk the jury left to right: normal run, stress test, supervisor override.",
|
|
167
|
+
"4. Show that each run also has results CSV, audit trail, PDF report, and manifest.",
|
|
168
|
+
"5. Optionally show the local AI explanation on the supervisor run.",
|
|
169
|
+
"",
|
|
170
|
+
"## What to say per panel",
|
|
171
|
+
"",
|
|
172
|
+
]
|
|
173
|
+
for spec in scenario_specs:
|
|
174
|
+
outputs = run_outputs[spec.slug]
|
|
175
|
+
lines.extend(
|
|
176
|
+
[
|
|
177
|
+
f"### {spec.label}",
|
|
178
|
+
"",
|
|
179
|
+
f"- Headline: {spec.headline}",
|
|
180
|
+
f"- Why it matters: {spec.jury_takeaway}",
|
|
181
|
+
f"- Run directory: `{outputs['output_dir']}`",
|
|
182
|
+
f"- Results CSV: `{outputs['results_csv']}`",
|
|
183
|
+
f"- PDF report: `{outputs['report_pdf']}`",
|
|
184
|
+
"",
|
|
185
|
+
]
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
lines.extend(
|
|
189
|
+
[
|
|
190
|
+
"## Optional AI step",
|
|
191
|
+
"",
|
|
192
|
+
ai_status,
|
|
193
|
+
"",
|
|
194
|
+
"If local Ollama + Ministral are ready, run:",
|
|
195
|
+
"",
|
|
196
|
+
"```bash",
|
|
197
|
+
"iints ai local-check --model ministral-3:3b",
|
|
198
|
+
f"iints ai report {run_outputs['03_supervisor_override']['output_dir']} --model ministral-3:3b",
|
|
199
|
+
f"iints ai explain {run_outputs['03_supervisor_override']['output_dir']} --model ministral-3:3b",
|
|
200
|
+
"```",
|
|
201
|
+
"",
|
|
202
|
+
"## Key artifacts",
|
|
203
|
+
"",
|
|
204
|
+
f"- Poster PNG: `{poster_png}`",
|
|
205
|
+
f"- Poster summary: `{poster_summary_json}`",
|
|
206
|
+
f"- Demo summary JSON: `{output_dir / 'demo_summary.json'}`",
|
|
207
|
+
f"- Demo commands: `{output_dir / 'run_commands.md'}`",
|
|
208
|
+
]
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
if ai_outputs:
|
|
212
|
+
lines.extend(
|
|
213
|
+
[
|
|
214
|
+
"",
|
|
215
|
+
"### AI-ready artifacts",
|
|
216
|
+
"",
|
|
217
|
+
]
|
|
218
|
+
)
|
|
219
|
+
for key, value in ai_outputs.items():
|
|
220
|
+
lines.append(f"- {key}: `{value}`")
|
|
221
|
+
|
|
222
|
+
return "\n".join(lines) + "\n"
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def _build_commands_markdown(
|
|
226
|
+
*,
|
|
227
|
+
output_dir: Path,
|
|
228
|
+
example_script: Path,
|
|
229
|
+
run_outputs: dict[str, dict[str, Any]],
|
|
230
|
+
) -> str:
|
|
231
|
+
supervisor_dir = run_outputs["03_supervisor_override"]["output_dir"]
|
|
232
|
+
return (
|
|
233
|
+
"# Booth Demo Commands\n\n"
|
|
234
|
+
"## Showable live demo script\n\n"
|
|
235
|
+
"```bash\n"
|
|
236
|
+
f"PYTHONPATH=src python3 {example_script} --output-dir {output_dir}\n"
|
|
237
|
+
"```\n\n"
|
|
238
|
+
"## Run from source tree\n\n"
|
|
239
|
+
"```bash\n"
|
|
240
|
+
f"PYTHONPATH=src python3 examples/demos/06_booth_demo.py --output-dir {output_dir}\n"
|
|
241
|
+
"```\n\n"
|
|
242
|
+
"## Run via installed CLI\n\n"
|
|
243
|
+
"```bash\n"
|
|
244
|
+
f"iints demo-booth --output-dir {output_dir}\n"
|
|
245
|
+
"```\n\n"
|
|
246
|
+
"## Optional local AI explanation\n\n"
|
|
247
|
+
"```bash\n"
|
|
248
|
+
"iints ai local-check --model ministral-3:3b\n"
|
|
249
|
+
f"iints ai report {supervisor_dir} --model ministral-3:3b\n"
|
|
250
|
+
f"iints ai explain {supervisor_dir} --model ministral-3:3b\n"
|
|
251
|
+
"```\n"
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def _build_live_demo_script_text(
|
|
256
|
+
*,
|
|
257
|
+
output_dir: Path,
|
|
258
|
+
poster_png: Path,
|
|
259
|
+
run_outputs: dict[str, dict[str, Any]],
|
|
260
|
+
) -> str:
|
|
261
|
+
return (
|
|
262
|
+
"IINTS-AF BOOTH LIVE DEMO SCRIPT\n"
|
|
263
|
+
"===============================\n\n"
|
|
264
|
+
"1. WHAT CODE TO SHOW FIRST\n"
|
|
265
|
+
"- Show examples/demos/07_live_stage_demo.py first.\n"
|
|
266
|
+
" Reason: the top of that file exposes the patient profile, output folder, duration, and seed on one screen.\n"
|
|
267
|
+
"- Point out that you can swap PATIENT_CONFIG to another packaged profile such as patient_559_config or clinic_safe_hypo_prone.\n"
|
|
268
|
+
"- If someone asks how the full bundle is generated, open examples/demos/06_booth_demo.py and then src/iints/analysis/booth_demo.py.\n"
|
|
269
|
+
" Reason: those files define the three scenarios and write the poster, talk track, and run bundle outputs.\n\n"
|
|
270
|
+
"2. LIVE COMMAND TO RUN\n"
|
|
271
|
+
"- From the repository root, run the live stage script:\n"
|
|
272
|
+
" ./scripts/run_live_stage_demo.sh\n"
|
|
273
|
+
"- Or run the booth bundle directly:\n"
|
|
274
|
+
" ./scripts/run_booth_demo.sh\n"
|
|
275
|
+
"- Or use the installed CLI:\n"
|
|
276
|
+
" iints demo-booth --output-dir results/booth_demo\n\n"
|
|
277
|
+
"3. WHAT TO SAY WHILE IT RUNS\n"
|
|
278
|
+
"- IINTS-AF is a safety-first SDK for testing insulin-delivery algorithms before real-world use.\n"
|
|
279
|
+
"- This demo generates three reproducible scenarios: a normal run, a stress test, and a supervisor override case.\n"
|
|
280
|
+
"- Every scenario produces real artifacts: CSV, PDF, audit trail, baseline comparison, and a manifest.\n\n"
|
|
281
|
+
"4. WHAT TO OPEN AFTER THE RUN\n"
|
|
282
|
+
f"- Open the poster: {poster_png}\n"
|
|
283
|
+
f"- Normal run folder: {run_outputs['01_normal_run']['output_dir']}\n"
|
|
284
|
+
f"- Meal stress folder: {run_outputs['02_meal_stress_test']['output_dir']}\n"
|
|
285
|
+
f"- Supervisor folder: {run_outputs['03_supervisor_override']['output_dir']}\n\n"
|
|
286
|
+
"5. JURY WALKTHROUGH\n"
|
|
287
|
+
"- Panel 1: Normal Run\n"
|
|
288
|
+
" Say: this is the control case. The algorithm keeps glucose in a clinically readable range.\n"
|
|
289
|
+
"- Panel 2: Meal Stress Test\n"
|
|
290
|
+
" Say: here we add larger disturbances and show that the system stays explainable under stress.\n"
|
|
291
|
+
"- Panel 3: Supervisor Override\n"
|
|
292
|
+
" Say: here we intentionally use a bad AI policy so the safety supervisor can prove it blocks unsafe insulin.\n\n"
|
|
293
|
+
"6. OPTIONAL AI STEP\n"
|
|
294
|
+
"- If Ollama is ready, run:\n"
|
|
295
|
+
" iints ai local-check --model ministral-3:3b\n"
|
|
296
|
+
f" iints ai report {run_outputs['03_supervisor_override']['output_dir']} --model ministral-3:3b\n"
|
|
297
|
+
f" iints ai explain {run_outputs['03_supervisor_override']['output_dir']} --model ministral-3:3b\n"
|
|
298
|
+
"- Say: the local model explains the result, but only after the SDK has prepared the run artifacts.\n\n"
|
|
299
|
+
"7. IF THE JURY ASKS WHY THIS MATTERS\n"
|
|
300
|
+
"- It is not just a graph generator.\n"
|
|
301
|
+
"- It is a reproducible safety workflow: simulate, stress, audit, visualize, explain.\n"
|
|
302
|
+
"- The supervisor case is the key proof that the SDK is safety-first, not just AI-first.\n\n"
|
|
303
|
+
"8. HANDY FILES\n"
|
|
304
|
+
f"- Poster PNG: {poster_png}\n"
|
|
305
|
+
f"- Jury markdown: {output_dir / 'JURY_TALK_TRACK.md'}\n"
|
|
306
|
+
f"- Demo commands: {output_dir / 'run_commands.md'}\n"
|
|
307
|
+
f"- Demo summary JSON: {output_dir / 'demo_summary.json'}\n"
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def build_booth_demo(
|
|
312
|
+
output_dir: str | Path = "./results/booth_demo",
|
|
313
|
+
*,
|
|
314
|
+
patient_config: str | Path | dict[str, Any] = "default_patient",
|
|
315
|
+
duration_minutes: int = 360,
|
|
316
|
+
time_step: int = 5,
|
|
317
|
+
seed: int = 42,
|
|
318
|
+
prepare_ai: bool = True,
|
|
319
|
+
create_dev_mdmp_cert: bool = True,
|
|
320
|
+
) -> dict[str, str]:
|
|
321
|
+
"""
|
|
322
|
+
Create a fair-ready demo bundle with three scenario runs, a poster, and jury notes.
|
|
323
|
+
"""
|
|
324
|
+
resolved_output = Path(output_dir).expanduser().resolve()
|
|
325
|
+
resolved_output.mkdir(parents=True, exist_ok=True)
|
|
326
|
+
|
|
327
|
+
specs = _scenario_specs()
|
|
328
|
+
run_outputs: dict[str, dict[str, Any]] = {}
|
|
329
|
+
run_dirs: list[Path] = []
|
|
330
|
+
labels: list[str] = []
|
|
331
|
+
|
|
332
|
+
for spec in specs:
|
|
333
|
+
run_dir = resolved_output / spec.slug
|
|
334
|
+
outputs = run_full(
|
|
335
|
+
algorithm=spec.algorithm_factory(),
|
|
336
|
+
scenario=spec.scenario,
|
|
337
|
+
patient_config=patient_config,
|
|
338
|
+
duration_minutes=duration_minutes,
|
|
339
|
+
time_step=time_step,
|
|
340
|
+
seed=seed,
|
|
341
|
+
output_dir=run_dir,
|
|
342
|
+
enable_profiling=False,
|
|
343
|
+
)
|
|
344
|
+
run_outputs[spec.slug] = {
|
|
345
|
+
"label": spec.label,
|
|
346
|
+
"headline": spec.headline,
|
|
347
|
+
"jury_takeaway": spec.jury_takeaway,
|
|
348
|
+
"output_dir": str(run_dir),
|
|
349
|
+
"results_csv": str(outputs["results_csv"]),
|
|
350
|
+
"report_pdf": str(outputs["report_pdf"]),
|
|
351
|
+
"run_manifest_path": str(outputs["run_manifest_path"]),
|
|
352
|
+
}
|
|
353
|
+
run_dirs.append(run_dir)
|
|
354
|
+
labels.append(spec.label)
|
|
355
|
+
|
|
356
|
+
poster_outputs = generate_results_poster(
|
|
357
|
+
run_dirs=run_dirs,
|
|
358
|
+
labels=labels,
|
|
359
|
+
output_path=resolved_output / "booth_demo_poster.png",
|
|
360
|
+
summary_output_path=resolved_output / "booth_demo_poster.json",
|
|
361
|
+
poster_title="288 Decisions. Every Day. We Test Them All.",
|
|
362
|
+
subtitle="Normal control, stress handling, and safety override in one fair-ready story.",
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
ai_outputs: dict[str, str] = {}
|
|
366
|
+
ai_status = "AI preparation was skipped."
|
|
367
|
+
if prepare_ai:
|
|
368
|
+
supervisor_dir = run_dirs[-1]
|
|
369
|
+
try:
|
|
370
|
+
ai_outputs = prepare_ai_ready_artifacts(
|
|
371
|
+
supervisor_dir,
|
|
372
|
+
create_dev_mdmp_cert=create_dev_mdmp_cert,
|
|
373
|
+
)
|
|
374
|
+
if "mdmp_cert" in ai_outputs:
|
|
375
|
+
ai_status = (
|
|
376
|
+
"AI-ready payloads and a local development MDMP certificate were generated for the "
|
|
377
|
+
"Supervisor Override run."
|
|
378
|
+
)
|
|
379
|
+
else:
|
|
380
|
+
ai_status = "AI-ready payloads were generated for the Supervisor Override run."
|
|
381
|
+
except Exception as exc:
|
|
382
|
+
ai_status = f"AI preparation did not block the demo, but it could not finish cleanly: {exc}"
|
|
383
|
+
|
|
384
|
+
summary_payload = {
|
|
385
|
+
"output_dir": str(resolved_output),
|
|
386
|
+
"patient_config": str(patient_config),
|
|
387
|
+
"duration_minutes": duration_minutes,
|
|
388
|
+
"time_step_minutes": time_step,
|
|
389
|
+
"seed": seed,
|
|
390
|
+
"poster_png": poster_outputs["poster_png"],
|
|
391
|
+
"poster_summary_json": poster_outputs["summary_json"],
|
|
392
|
+
"ai_status": ai_status,
|
|
393
|
+
"scenarios": [
|
|
394
|
+
{
|
|
395
|
+
"slug": spec.slug,
|
|
396
|
+
"label": spec.label,
|
|
397
|
+
"headline": spec.headline,
|
|
398
|
+
"jury_takeaway": spec.jury_takeaway,
|
|
399
|
+
"scenario_name": spec.scenario["scenario_name"],
|
|
400
|
+
**run_outputs[spec.slug],
|
|
401
|
+
}
|
|
402
|
+
for spec in specs
|
|
403
|
+
],
|
|
404
|
+
}
|
|
405
|
+
_write_json(resolved_output / "demo_summary.json", summary_payload)
|
|
406
|
+
|
|
407
|
+
example_script = Path("examples/demos/07_live_stage_demo.py")
|
|
408
|
+
commands_markdown = _build_commands_markdown(
|
|
409
|
+
output_dir=resolved_output,
|
|
410
|
+
example_script=example_script,
|
|
411
|
+
run_outputs=run_outputs,
|
|
412
|
+
)
|
|
413
|
+
commands_path = resolved_output / "run_commands.md"
|
|
414
|
+
_write_text(commands_path, commands_markdown)
|
|
415
|
+
|
|
416
|
+
live_demo_script_text = _build_live_demo_script_text(
|
|
417
|
+
output_dir=resolved_output,
|
|
418
|
+
poster_png=Path(poster_outputs["poster_png"]),
|
|
419
|
+
run_outputs=run_outputs,
|
|
420
|
+
)
|
|
421
|
+
live_demo_script_path = resolved_output / "BEURS_LIVE_DEMO_SCRIPT.txt"
|
|
422
|
+
_write_text(live_demo_script_path, live_demo_script_text)
|
|
423
|
+
|
|
424
|
+
jury_brief = _build_jury_brief(
|
|
425
|
+
output_dir=resolved_output,
|
|
426
|
+
scenario_specs=specs,
|
|
427
|
+
poster_png=Path(poster_outputs["poster_png"]),
|
|
428
|
+
poster_summary_json=Path(poster_outputs["summary_json"]),
|
|
429
|
+
run_outputs=run_outputs,
|
|
430
|
+
ai_outputs=ai_outputs,
|
|
431
|
+
ai_status=ai_status,
|
|
432
|
+
)
|
|
433
|
+
jury_brief_path = resolved_output / "JURY_TALK_TRACK.md"
|
|
434
|
+
_write_text(jury_brief_path, jury_brief)
|
|
435
|
+
|
|
436
|
+
artifact_paths: dict[str, str] = {
|
|
437
|
+
"output_dir": str(resolved_output),
|
|
438
|
+
"poster_png": poster_outputs["poster_png"],
|
|
439
|
+
"poster_summary_json": poster_outputs["summary_json"],
|
|
440
|
+
"demo_summary_json": str(resolved_output / "demo_summary.json"),
|
|
441
|
+
"jury_talk_track": str(jury_brief_path),
|
|
442
|
+
"run_commands": str(commands_path),
|
|
443
|
+
"live_demo_script": str(live_demo_script_path),
|
|
444
|
+
}
|
|
445
|
+
for spec in specs:
|
|
446
|
+
artifact_paths[f"{spec.slug}_dir"] = run_outputs[spec.slug]["output_dir"]
|
|
447
|
+
artifact_paths.update(ai_outputs)
|
|
448
|
+
return artifact_paths
|
iints/cli/cli.py
CHANGED
|
@@ -25,6 +25,7 @@ import iints # Import the top-level SDK package
|
|
|
25
25
|
from iints.ai import prepare_ai_ready_artifacts
|
|
26
26
|
from iints.ai.cli import app as ai_app
|
|
27
27
|
from iints.analysis.baseline import run_baseline_comparison, write_baseline_comparison
|
|
28
|
+
from iints.analysis.booth_demo import build_booth_demo
|
|
28
29
|
from iints.analysis.carelink_workbench import build_carelink_workbench
|
|
29
30
|
from iints.analysis.poster import generate_results_poster
|
|
30
31
|
from iints.api.registry import list_algorithm_plugins
|
|
@@ -2674,6 +2675,71 @@ def poster(
|
|
|
2674
2675
|
)
|
|
2675
2676
|
|
|
2676
2677
|
|
|
2678
|
+
@app.command("demo-booth")
|
|
2679
|
+
def demo_booth(
|
|
2680
|
+
output_dir: Annotated[
|
|
2681
|
+
Path,
|
|
2682
|
+
typer.Option(help="Directory where the fair-ready demo bundle should be written."),
|
|
2683
|
+
] = Path("./results/booth_demo"),
|
|
2684
|
+
duration: Annotated[
|
|
2685
|
+
int,
|
|
2686
|
+
typer.Option(help="Simulation duration in minutes for each booth scenario."),
|
|
2687
|
+
] = 360,
|
|
2688
|
+
time_step: Annotated[
|
|
2689
|
+
int,
|
|
2690
|
+
typer.Option(help="Simulation step size in minutes."),
|
|
2691
|
+
] = 5,
|
|
2692
|
+
seed: Annotated[
|
|
2693
|
+
int,
|
|
2694
|
+
typer.Option(help="Deterministic random seed."),
|
|
2695
|
+
] = 42,
|
|
2696
|
+
prepare_ai: Annotated[
|
|
2697
|
+
bool,
|
|
2698
|
+
typer.Option(
|
|
2699
|
+
"--prepare-ai/--no-prepare-ai",
|
|
2700
|
+
help="Prepare AI-ready artifacts for the Supervisor Override run.",
|
|
2701
|
+
),
|
|
2702
|
+
] = True,
|
|
2703
|
+
) -> None:
|
|
2704
|
+
"""Build a full expo/jury demo bundle with runs, poster, and talk track."""
|
|
2705
|
+
console = Console()
|
|
2706
|
+
try:
|
|
2707
|
+
outputs = build_booth_demo(
|
|
2708
|
+
output_dir=output_dir,
|
|
2709
|
+
duration_minutes=duration,
|
|
2710
|
+
time_step=time_step,
|
|
2711
|
+
seed=seed,
|
|
2712
|
+
prepare_ai=prepare_ai,
|
|
2713
|
+
)
|
|
2714
|
+
except Exception as exc:
|
|
2715
|
+
console.print(f"[bold red]Booth demo failed:[/bold red] {exc}")
|
|
2716
|
+
raise typer.Exit(code=1)
|
|
2717
|
+
|
|
2718
|
+
table = Table(title="IINTS Booth Demo")
|
|
2719
|
+
table.add_column("Artifact", style="cyan")
|
|
2720
|
+
table.add_column("Path", overflow="fold")
|
|
2721
|
+
for key in [
|
|
2722
|
+
"poster_png",
|
|
2723
|
+
"poster_summary_json",
|
|
2724
|
+
"demo_summary_json",
|
|
2725
|
+
"jury_talk_track",
|
|
2726
|
+
"live_demo_script",
|
|
2727
|
+
"run_commands",
|
|
2728
|
+
"01_normal_run_dir",
|
|
2729
|
+
"02_meal_stress_test_dir",
|
|
2730
|
+
"03_supervisor_override_dir",
|
|
2731
|
+
"mdmp_cert",
|
|
2732
|
+
]:
|
|
2733
|
+
if key in outputs:
|
|
2734
|
+
table.add_row(key, outputs[key])
|
|
2735
|
+
console.print(table)
|
|
2736
|
+
if "live_demo_script" in outputs:
|
|
2737
|
+
console.print(f"[green]Live booth script:[/green] {outputs['live_demo_script']}")
|
|
2738
|
+
console.print(
|
|
2739
|
+
"[green]Next:[/green] open the poster and use the jury talk track to walk people through the story."
|
|
2740
|
+
)
|
|
2741
|
+
|
|
2742
|
+
|
|
2677
2743
|
@app.command()
|
|
2678
2744
|
def report(
|
|
2679
2745
|
results_csv: Annotated[Path, typer.Option(help="Path to a simulation results CSV")],
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: iints-sdk-python35
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.1
|
|
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
|
|
@@ -218,6 +218,48 @@ The poster shows:
|
|
|
218
218
|
|
|
219
219
|
If you omit `--run-dir`, the CLI auto-discovers the latest run bundles under `./results`.
|
|
220
220
|
|
|
221
|
+
## Fair / Jury Demo
|
|
222
|
+
|
|
223
|
+
If you want one clean live demo for a booth, jury, or pitch session, use the built-in booth flow:
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
./scripts/run_booth_demo.sh
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
This generates:
|
|
230
|
+
- three run bundles (`Normal Run`, `Meal Stress Test`, `Supervisor Override`)
|
|
231
|
+
- a ready-to-show poster PNG
|
|
232
|
+
- a markdown jury talk track
|
|
233
|
+
- a plain-text live demo script for the stand
|
|
234
|
+
- optional AI-ready artifacts for the safety case
|
|
235
|
+
|
|
236
|
+
You can also run it through the CLI:
|
|
237
|
+
|
|
238
|
+
```bash
|
|
239
|
+
iints demo-booth --output-dir results/booth_demo
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
## Updating The SDK
|
|
243
|
+
|
|
244
|
+
If another machine is missing newer commands like `iints ai ...` or `iints demo-booth`, upgrade inside the active virtual environment:
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
source .venv/bin/activate
|
|
248
|
+
python -m pip install -U pip
|
|
249
|
+
python -m pip install -U "iints-sdk-python35[mdmp]==1.3.1"
|
|
250
|
+
hash -r
|
|
251
|
+
python -c "import iints; print(iints.__version__)"
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
If that machine still behaves like an old install, run:
|
|
255
|
+
|
|
256
|
+
```bash
|
|
257
|
+
iints-sdk-doctor
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
Full guide:
|
|
261
|
+
- `docs/UPDATING.md`
|
|
262
|
+
|
|
221
263
|
Troubleshooting:
|
|
222
264
|
- If `iints ai ...` says `No such command 'ai'`, your environment usually still has a legacy `iints` package installed alongside `iints-sdk-python35`.
|
|
223
265
|
- Run `iints-sdk-doctor` first.
|
|
@@ -225,7 +267,7 @@ Troubleshooting:
|
|
|
225
267
|
|
|
226
268
|
```bash
|
|
227
269
|
python -m pip uninstall -y iints iints-sdk-python35
|
|
228
|
-
python -m pip install -U "iints-sdk-python35[mdmp]==1.
|
|
270
|
+
python -m pip install -U "iints-sdk-python35[mdmp]==1.3.1"
|
|
229
271
|
hash -r
|
|
230
272
|
```
|
|
231
273
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
iints/__init__.py,sha256=
|
|
1
|
+
iints/__init__.py,sha256=abawJ2sonvJcw1K4WT2JFR-GSq3il5pnp3_fFKwOxBw,6879
|
|
2
2
|
iints/highlevel.py,sha256=DX12LRmL6YaYY99P0c_P93xfHe4mZjqyLhTYuS6L6hI,20491
|
|
3
3
|
iints/metrics.py,sha256=O9hqOqJpUhUJDqsbfuqRMS9dkV97gzcgh3Y2jYUqHzg,907
|
|
4
4
|
iints/ai/__init__.py,sha256=nyRDcFfSHI4a3NbTvySipFc3_inqRMEsr6xIEipWuyo,575
|
|
@@ -12,9 +12,10 @@ iints/ai/backends/__init__.py,sha256=EAJRZS8G0DK7fffw_LHio9DkyYHwtzvz2Jo7AXk7pk4
|
|
|
12
12
|
iints/ai/backends/base.py,sha256=BLgP03X-jebYkF9D5n5crawoPBmy3RSh4q3jaT8a9XM,274
|
|
13
13
|
iints/ai/backends/mistral_api.py,sha256=dousHnzgzuik49822H8nCclYv5NoxHpTMLwtZPVj_TM,507
|
|
14
14
|
iints/ai/backends/ollama.py,sha256=3VWi37ueh7oeNK6tPrq3Ks8gbfQhzgNlMHHnV_6BxAY,12189
|
|
15
|
-
iints/analysis/__init__.py,sha256=
|
|
15
|
+
iints/analysis/__init__.py,sha256=qz0Su0PtICIBLlUgfq37YW4IkK6BuyfOWaqC6t4NKQM,640
|
|
16
16
|
iints/analysis/algorithm_xray.py,sha256=-AtXkZsgnsiFQ_K-IozjIDWkq-dDn0i0zmqWVMhINP4,15952
|
|
17
17
|
iints/analysis/baseline.py,sha256=PCFVb5vX0lYKChZvVk-8I_B5NLQQwGyx7Y6M3XjpIEY,3458
|
|
18
|
+
iints/analysis/booth_demo.py,sha256=oPFWNBjEbC1SXd209652dIzP97yl93vbV9qFC8Y5rek,17749
|
|
18
19
|
iints/analysis/carelink_workbench.py,sha256=SZLUamITycXgJpVUhAvsVmU1H_9vSNxp7zl4Ym2cadA,28241
|
|
19
20
|
iints/analysis/clinical_benchmark.py,sha256=FP3L2Ntub6vpWxCwI91BSQ-Y9pTuFqkt2SYjb91Q7DE,7766
|
|
20
21
|
iints/analysis/clinical_metrics.py,sha256=a9JNEV7Jzr_DZqS5o8UhHpB7TTkBZ9pp_OqSKN43OZc,18441
|
|
@@ -38,7 +39,7 @@ iints/api/registry.py,sha256=h2syJwacFbgrtgnVK20JwlXivvVO31zeJ_Ez4KBkn1g,3240
|
|
|
38
39
|
iints/api/template_algorithm.py,sha256=AFs9AymL3ddWAjgpOkF1Oa3TeOSg56siyDt_BmsAND8,9195
|
|
39
40
|
iints/assets/iints_logo.png,sha256=rWzP8XqIYDrPCTp378w73zA1snKCUHrZ76vwslro-uk,700372
|
|
40
41
|
iints/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
41
|
-
iints/cli/cli.py,sha256=
|
|
42
|
+
iints/cli/cli.py,sha256=hp2N_1DpRiHkOw6huGAVEOPn-K0sPGokbsxQw6rR0k0,220938
|
|
42
43
|
iints/core/__init__.py,sha256=rRH2lTmikavR7BgeJCUla0ZmPbZxATR6rEcSSv_tet4,28
|
|
43
44
|
iints/core/device.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
44
45
|
iints/core/device_manager.py,sha256=479_CNn6YescDLWDE7w1BbwuLwRUmCUOColAVTEWQc8,2078
|
|
@@ -144,9 +145,9 @@ iints/validation/schemas.py,sha256=uXhiPxyfyvOgCA83ZPBIzlITOu663fWctYxOMXUyf1I,4
|
|
|
144
145
|
iints/visualization/__init__.py,sha256=OdxVHDpY-9bDt8DTWWd-dspn1p0O9T908Cck-IGFaiM,640
|
|
145
146
|
iints/visualization/cockpit.py,sha256=Y7hoJXcTEWQ8yLiU5X5abT58uqGGsQllftXJwqerG1E,25057
|
|
146
147
|
iints/visualization/uncertainty_cloud.py,sha256=I5nNzSitgai21rkul31YNtJriSEmCeTsW0GWW2HUskY,19848
|
|
147
|
-
iints_sdk_python35-1.
|
|
148
|
-
iints_sdk_python35-1.
|
|
149
|
-
iints_sdk_python35-1.
|
|
150
|
-
iints_sdk_python35-1.
|
|
151
|
-
iints_sdk_python35-1.
|
|
152
|
-
iints_sdk_python35-1.
|
|
148
|
+
iints_sdk_python35-1.3.1.dist-info/licenses/LICENSE,sha256=b1luljj2mWWDW10t_qFIqd9Z6euXAcDBmIXowWuUlm4,1417
|
|
149
|
+
iints_sdk_python35-1.3.1.dist-info/METADATA,sha256=gghIM1hl0jJeFhkzn3i4azSfDoEBTAWiXB-vkSoOC7Y,12639
|
|
150
|
+
iints_sdk_python35-1.3.1.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
151
|
+
iints_sdk_python35-1.3.1.dist-info/entry_points.txt,sha256=aVioeLytTHG7WM7L3LIZ6XDJCKiSfqG-nVUQDVHPpQk,578
|
|
152
|
+
iints_sdk_python35-1.3.1.dist-info/top_level.txt,sha256=7Usr6NQKiC9SpNFyCis81MmgXy71lDCr5unR8BNXZ0E,6
|
|
153
|
+
iints_sdk_python35-1.3.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|