iints-sdk-python35 1.1.3__py3-none-any.whl → 1.3.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.
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.1.3"
14
+ __version__ = "1.3.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.
@@ -56,17 +56,24 @@ from .data.importer import (
56
56
  export_demo_csv,
57
57
  export_standard_csv,
58
58
  guess_column_mapping,
59
+ import_carelink_csv,
60
+ import_carelink_timeline,
59
61
  import_cgm_csv,
60
62
  import_cgm_dataframe,
63
+ load_carelink_event_log,
61
64
  load_demo_dataframe,
62
65
  scenario_from_csv,
63
66
  scenario_from_dataframe,
67
+ summarize_carelink_csv,
64
68
  )
65
69
  from .data.nightscout import NightscoutConfig, import_nightscout
66
70
  from .data.tidepool import TidepoolClient, load_openapi_spec
67
71
  from .data.guardians import mdmp_gate, MDMPGateError
68
72
  from .data.synthetic_mirror import generate_synthetic_mirror, SyntheticMirrorArtifact
69
73
  from .analysis.metrics import generate_benchmark_metrics # Added for benchmark
74
+ from .analysis.booth_demo import build_booth_demo
75
+ from .analysis.carelink_workbench import build_carelink_workbench
76
+ from .analysis.poster import generate_results_poster
70
77
  from .analysis.reporting import ClinicalReportGenerator
71
78
  from .analysis.edge_efficiency import EnergyEstimate, estimate_energy_per_decision
72
79
  from .ai import AIResponse, IINTSAssistant, MDMPGuard
@@ -160,11 +167,15 @@ __all__ = [
160
167
  "export_demo_csv",
161
168
  "export_standard_csv",
162
169
  "guess_column_mapping",
170
+ "import_carelink_csv",
171
+ "import_carelink_timeline",
163
172
  "import_cgm_csv",
164
173
  "import_cgm_dataframe",
174
+ "load_carelink_event_log",
165
175
  "load_demo_dataframe",
166
176
  "scenario_from_csv",
167
177
  "scenario_from_dataframe",
178
+ "summarize_carelink_csv",
168
179
  "NightscoutConfig",
169
180
  "import_nightscout",
170
181
  "TidepoolClient",
@@ -175,6 +186,8 @@ __all__ = [
175
186
  "SyntheticMirrorArtifact",
176
187
  # Analysis Metrics
177
188
  "generate_benchmark_metrics",
189
+ "build_booth_demo",
190
+ "build_carelink_workbench",
178
191
  "ClinicalReportGenerator",
179
192
  "EnergyEstimate",
180
193
  "estimate_energy_per_decision",
@@ -185,6 +198,7 @@ __all__ = [
185
198
  "generate_report",
186
199
  "generate_quickstart_report",
187
200
  "generate_demo_report",
201
+ "generate_results_poster",
188
202
  # High-level API
189
203
  "run_simulation",
190
204
  "run_full",
iints/ai/prompts.py CHANGED
@@ -8,8 +8,9 @@ TaskName = Literal["explain_decision", "analyze_trends", "detect_anomalies", "ge
8
8
  MAX_PROMPT_PAYLOAD_CHARS = 12000
9
9
 
10
10
  SYSTEM_PROMPT = (
11
- "You are the IINTS-AF research assistant for closed-loop insulin delivery simulations. "
12
- "Explain simulation behavior clearly, conservatively, and in plain language. "
11
+ "You are the IINTS-AF research assistant for closed-loop insulin delivery simulations "
12
+ "and imported glucose datasets. "
13
+ "Explain glycemic behavior clearly, conservatively, and in plain language. "
13
14
  "Do not give medical advice, treatment instructions, or patient-specific recommendations. "
14
15
  "State uncertainty when the input is incomplete. "
15
16
  "For research use only."
@@ -17,39 +18,39 @@ SYSTEM_PROMPT = (
17
18
 
18
19
  TASK_TEMPLATES: dict[TaskName, str] = {
19
20
  "explain_decision": (
20
- "Given this single simulation step, explain:\n"
21
- "1. What the algorithm decided and why\n"
22
- "2. Whether the independent safety supervisor likely intervened\n"
23
- "3. Whether the decision appears safe in context\n\n"
21
+ "Given this single decision step or noteworthy glucose snapshot, explain:\n"
22
+ "1. What is happening in the data and why it stands out\n"
23
+ "2. Whether there are safety signals, supervision, or notable context clues\n"
24
+ "3. What a research user should pay attention to next\n\n"
24
25
  "Respond in 3 short paragraphs.\n\n"
25
- "Simulation step JSON:\n{data}"
26
+ "Input JSON:\n{data}"
26
27
  ),
27
28
  "analyze_trends": (
28
- "Review this glucose-oriented simulation payload and summarize the main glycemic trends.\n"
29
+ "Review this glucose-oriented payload and summarize the main glycemic trends.\n"
29
30
  "Focus on direction, stability, excursions, and likely triggers.\n"
30
31
  "Respond with:\n"
31
32
  "- Trend summary\n"
32
33
  "- Main risk signals\n"
33
34
  "- Short operational takeaway\n\n"
34
- "Simulation payload JSON:\n{data}"
35
+ "Payload JSON:\n{data}"
35
36
  ),
36
37
  "detect_anomalies": (
37
- "Inspect this run summary and identify unusual patterns, inconsistent values, or clinically relevant anomalies.\n"
38
+ "Inspect this run or imported-data summary and identify unusual patterns, inconsistent values, or clinically relevant anomalies.\n"
38
39
  "Respond with:\n"
39
40
  "- Detected anomalies\n"
40
41
  "- Why each anomaly matters\n"
41
42
  "- Whether follow-up validation is recommended\n\n"
42
- "Run summary JSON:\n{data}"
43
+ "Summary JSON:\n{data}"
43
44
  ),
44
45
  "generate_report": (
45
- "Write a concise markdown report for this IINTS-AF simulation run.\n"
46
+ "Write a concise markdown report for this IINTS-AF simulation run or imported personal glucose dataset.\n"
46
47
  "Include sections:\n"
47
48
  "1. Executive summary\n"
48
49
  "2. Glycemic behavior\n"
49
- "3. Safety and supervisor behavior\n"
50
- "4. Notable events or anomalies\n"
50
+ "3. Safety, supervision, or device behavior\n"
51
+ "4. Notable events, patterns, or anomalies\n"
51
52
  "5. Research-only conclusion\n\n"
52
- "Simulation run JSON:\n{data}"
53
+ "Input JSON:\n{data}"
53
54
  ),
54
55
  }
55
56
 
@@ -1,12 +1,18 @@
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
4
+ from .carelink_workbench import build_carelink_workbench
5
+ from .poster import generate_results_poster
3
6
  from .reporting import ClinicalReportGenerator
4
7
 
5
8
  __all__ = [
9
+ "build_booth_demo",
10
+ "build_carelink_workbench",
6
11
  "ClinicalMetricsCalculator",
7
12
  "ClinicalMetricsResult",
8
13
  "ClinicalReportGenerator",
9
14
  "compute_metrics",
15
+ "generate_results_poster",
10
16
  "run_baseline_comparison",
11
17
  "write_baseline_comparison",
12
18
  ]
@@ -0,0 +1,439 @@
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
+ "## Run from source tree\n\n"
235
+ "```bash\n"
236
+ f"PYTHONPATH=src python3 {example_script} --output-dir {output_dir}\n"
237
+ "```\n\n"
238
+ "## Run via installed CLI\n\n"
239
+ "```bash\n"
240
+ f"iints demo-booth --output-dir {output_dir}\n"
241
+ "```\n\n"
242
+ "## Optional local AI explanation\n\n"
243
+ "```bash\n"
244
+ "iints ai local-check --model ministral-3:3b\n"
245
+ f"iints ai report {supervisor_dir} --model ministral-3:3b\n"
246
+ f"iints ai explain {supervisor_dir} --model ministral-3:3b\n"
247
+ "```\n"
248
+ )
249
+
250
+
251
+ def _build_live_demo_script_text(
252
+ *,
253
+ output_dir: Path,
254
+ poster_png: Path,
255
+ run_outputs: dict[str, dict[str, Any]],
256
+ ) -> str:
257
+ return (
258
+ "IINTS-AF BOOTH LIVE DEMO SCRIPT\n"
259
+ "===============================\n\n"
260
+ "1. WHAT CODE TO SHOW FIRST\n"
261
+ "- Show examples/demos/06_booth_demo.py first.\n"
262
+ " Reason: it is the shortest, clearest orchestration file and shows the whole story in one screen.\n"
263
+ "- If someone asks how it really works, open src/iints/analysis/booth_demo.py.\n"
264
+ " Reason: that file defines the three scenarios and writes the poster, talk track, and run bundle outputs.\n\n"
265
+ "2. LIVE COMMAND TO RUN\n"
266
+ "- From the repository root, run:\n"
267
+ " ./scripts/run_booth_demo.sh\n"
268
+ "- Or use the installed CLI:\n"
269
+ " iints demo-booth --output-dir results/booth_demo\n\n"
270
+ "3. WHAT TO SAY WHILE IT RUNS\n"
271
+ "- IINTS-AF is a safety-first SDK for testing insulin-delivery algorithms before real-world use.\n"
272
+ "- This demo generates three reproducible scenarios: a normal run, a stress test, and a supervisor override case.\n"
273
+ "- Every scenario produces real artifacts: CSV, PDF, audit trail, baseline comparison, and a manifest.\n\n"
274
+ "4. WHAT TO OPEN AFTER THE RUN\n"
275
+ f"- Open the poster: {poster_png}\n"
276
+ f"- Normal run folder: {run_outputs['01_normal_run']['output_dir']}\n"
277
+ f"- Meal stress folder: {run_outputs['02_meal_stress_test']['output_dir']}\n"
278
+ f"- Supervisor folder: {run_outputs['03_supervisor_override']['output_dir']}\n\n"
279
+ "5. JURY WALKTHROUGH\n"
280
+ "- Panel 1: Normal Run\n"
281
+ " Say: this is the control case. The algorithm keeps glucose in a clinically readable range.\n"
282
+ "- Panel 2: Meal Stress Test\n"
283
+ " Say: here we add larger disturbances and show that the system stays explainable under stress.\n"
284
+ "- Panel 3: Supervisor Override\n"
285
+ " Say: here we intentionally use a bad AI policy so the safety supervisor can prove it blocks unsafe insulin.\n\n"
286
+ "6. OPTIONAL AI STEP\n"
287
+ "- If Ollama is ready, run:\n"
288
+ " iints ai local-check --model ministral-3:3b\n"
289
+ f" iints ai report {run_outputs['03_supervisor_override']['output_dir']} --model ministral-3:3b\n"
290
+ f" iints ai explain {run_outputs['03_supervisor_override']['output_dir']} --model ministral-3:3b\n"
291
+ "- Say: the local model explains the result, but only after the SDK has prepared the run artifacts.\n\n"
292
+ "7. IF THE JURY ASKS WHY THIS MATTERS\n"
293
+ "- It is not just a graph generator.\n"
294
+ "- It is a reproducible safety workflow: simulate, stress, audit, visualize, explain.\n"
295
+ "- The supervisor case is the key proof that the SDK is safety-first, not just AI-first.\n\n"
296
+ "8. HANDY FILES\n"
297
+ f"- Poster PNG: {poster_png}\n"
298
+ f"- Jury markdown: {output_dir / 'JURY_TALK_TRACK.md'}\n"
299
+ f"- Demo commands: {output_dir / 'run_commands.md'}\n"
300
+ f"- Demo summary JSON: {output_dir / 'demo_summary.json'}\n"
301
+ )
302
+
303
+
304
+ def build_booth_demo(
305
+ output_dir: str | Path = "./results/booth_demo",
306
+ *,
307
+ duration_minutes: int = 360,
308
+ time_step: int = 5,
309
+ seed: int = 42,
310
+ prepare_ai: bool = True,
311
+ create_dev_mdmp_cert: bool = True,
312
+ ) -> dict[str, str]:
313
+ """
314
+ Create a fair-ready demo bundle with three scenario runs, a poster, and jury notes.
315
+ """
316
+ resolved_output = Path(output_dir).expanduser().resolve()
317
+ resolved_output.mkdir(parents=True, exist_ok=True)
318
+
319
+ specs = _scenario_specs()
320
+ run_outputs: dict[str, dict[str, Any]] = {}
321
+ run_dirs: list[Path] = []
322
+ labels: list[str] = []
323
+
324
+ for spec in specs:
325
+ run_dir = resolved_output / spec.slug
326
+ outputs = run_full(
327
+ algorithm=spec.algorithm_factory(),
328
+ scenario=spec.scenario,
329
+ patient_config="default_patient",
330
+ duration_minutes=duration_minutes,
331
+ time_step=time_step,
332
+ seed=seed,
333
+ output_dir=run_dir,
334
+ enable_profiling=False,
335
+ )
336
+ run_outputs[spec.slug] = {
337
+ "label": spec.label,
338
+ "headline": spec.headline,
339
+ "jury_takeaway": spec.jury_takeaway,
340
+ "output_dir": str(run_dir),
341
+ "results_csv": str(outputs["results_csv"]),
342
+ "report_pdf": str(outputs["report_pdf"]),
343
+ "run_manifest_path": str(outputs["run_manifest_path"]),
344
+ }
345
+ run_dirs.append(run_dir)
346
+ labels.append(spec.label)
347
+
348
+ poster_outputs = generate_results_poster(
349
+ run_dirs=run_dirs,
350
+ labels=labels,
351
+ output_path=resolved_output / "booth_demo_poster.png",
352
+ summary_output_path=resolved_output / "booth_demo_poster.json",
353
+ poster_title="288 Decisions. Every Day. We Test Them All.",
354
+ subtitle="Normal control, stress handling, and safety override in one fair-ready story.",
355
+ )
356
+
357
+ ai_outputs: dict[str, str] = {}
358
+ ai_status = "AI preparation was skipped."
359
+ if prepare_ai:
360
+ supervisor_dir = run_dirs[-1]
361
+ try:
362
+ ai_outputs = prepare_ai_ready_artifacts(
363
+ supervisor_dir,
364
+ create_dev_mdmp_cert=create_dev_mdmp_cert,
365
+ )
366
+ if "mdmp_cert" in ai_outputs:
367
+ ai_status = (
368
+ "AI-ready payloads and a local development MDMP certificate were generated for the "
369
+ "Supervisor Override run."
370
+ )
371
+ else:
372
+ ai_status = "AI-ready payloads were generated for the Supervisor Override run."
373
+ except Exception as exc:
374
+ ai_status = f"AI preparation did not block the demo, but it could not finish cleanly: {exc}"
375
+
376
+ summary_payload = {
377
+ "output_dir": str(resolved_output),
378
+ "duration_minutes": duration_minutes,
379
+ "time_step_minutes": time_step,
380
+ "seed": seed,
381
+ "poster_png": poster_outputs["poster_png"],
382
+ "poster_summary_json": poster_outputs["summary_json"],
383
+ "ai_status": ai_status,
384
+ "scenarios": [
385
+ {
386
+ "slug": spec.slug,
387
+ "label": spec.label,
388
+ "headline": spec.headline,
389
+ "jury_takeaway": spec.jury_takeaway,
390
+ "scenario_name": spec.scenario["scenario_name"],
391
+ **run_outputs[spec.slug],
392
+ }
393
+ for spec in specs
394
+ ],
395
+ }
396
+ _write_json(resolved_output / "demo_summary.json", summary_payload)
397
+
398
+ example_script = Path("examples/demos/06_booth_demo.py")
399
+ commands_markdown = _build_commands_markdown(
400
+ output_dir=resolved_output,
401
+ example_script=example_script,
402
+ run_outputs=run_outputs,
403
+ )
404
+ commands_path = resolved_output / "run_commands.md"
405
+ _write_text(commands_path, commands_markdown)
406
+
407
+ live_demo_script_text = _build_live_demo_script_text(
408
+ output_dir=resolved_output,
409
+ poster_png=Path(poster_outputs["poster_png"]),
410
+ run_outputs=run_outputs,
411
+ )
412
+ live_demo_script_path = resolved_output / "BEURS_LIVE_DEMO_SCRIPT.txt"
413
+ _write_text(live_demo_script_path, live_demo_script_text)
414
+
415
+ jury_brief = _build_jury_brief(
416
+ output_dir=resolved_output,
417
+ scenario_specs=specs,
418
+ poster_png=Path(poster_outputs["poster_png"]),
419
+ poster_summary_json=Path(poster_outputs["summary_json"]),
420
+ run_outputs=run_outputs,
421
+ ai_outputs=ai_outputs,
422
+ ai_status=ai_status,
423
+ )
424
+ jury_brief_path = resolved_output / "JURY_TALK_TRACK.md"
425
+ _write_text(jury_brief_path, jury_brief)
426
+
427
+ artifact_paths: dict[str, str] = {
428
+ "output_dir": str(resolved_output),
429
+ "poster_png": poster_outputs["poster_png"],
430
+ "poster_summary_json": poster_outputs["summary_json"],
431
+ "demo_summary_json": str(resolved_output / "demo_summary.json"),
432
+ "jury_talk_track": str(jury_brief_path),
433
+ "run_commands": str(commands_path),
434
+ "live_demo_script": str(live_demo_script_path),
435
+ }
436
+ for spec in specs:
437
+ artifact_paths[f"{spec.slug}_dir"] = run_outputs[spec.slug]["output_dir"]
438
+ artifact_paths.update(ai_outputs)
439
+ return artifact_paths