iints-sdk-python35 1.5.1__py3-none-any.whl → 1.5.3__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 +84 -7
- iints/analysis/__init__.py +88 -5
- iints/analysis/baseline.py +2 -1
- iints/analysis/booth_demo.py +286 -0
- iints/analysis/eucys_results.py +549 -0
- iints/analysis/study_analysis.py +727 -159
- iints/analysis/study_engine.py +441 -0
- iints/analysis/study_poster.py +267 -73
- iints/analysis/study_protocol.py +128 -164
- iints/cli/cli.py +1837 -147
- iints/cli/patient_cli.py +535 -0
- iints/data/nightscout.py +4 -0
- iints/data/registry.py +36 -1
- iints/data/tidepool.py +5 -0
- iints/highlevel.py +9 -3
- iints/live_patient/__init__.py +40 -0
- iints/live_patient/api.py +407 -0
- iints/live_patient/daemon.py +82 -0
- iints/live_patient/edge_benchmark.py +160 -0
- iints/live_patient/edge_ops.py +666 -0
- iints/live_patient/runtime.py +967 -0
- iints/live_patient/service_export.py +471 -0
- iints/live_patient/uno_q.py +413 -0
- iints/scenarios/study_pack.py +4 -7
- iints/templates/uno_q/README.md +110 -0
- iints/templates/uno_q/iints_supervisor_bridge.ino +70 -0
- iints/utils/csv_safety.py +31 -0
- iints/utils/url_safety.py +33 -0
- {iints_sdk_python35-1.5.1.dist-info → iints_sdk_python35-1.5.3.dist-info}/METADATA +31 -7
- {iints_sdk_python35-1.5.1.dist-info → iints_sdk_python35-1.5.3.dist-info}/RECORD +36 -21
- {iints_sdk_python35-1.5.1.dist-info → iints_sdk_python35-1.5.3.dist-info}/WHEEL +0 -0
- {iints_sdk_python35-1.5.1.dist-info → iints_sdk_python35-1.5.3.dist-info}/entry_points.txt +0 -0
- {iints_sdk_python35-1.5.1.dist-info → iints_sdk_python35-1.5.3.dist-info}/licenses/LICENSE +0 -0
- {iints_sdk_python35-1.5.1.dist-info → iints_sdk_python35-1.5.3.dist-info}/licenses/LICENSE-MIT-IINTS-LEGACY +0 -0
- {iints_sdk_python35-1.5.1.dist-info → iints_sdk_python35-1.5.3.dist-info}/licenses/NOTICE +0 -0
- {iints_sdk_python35-1.5.1.dist-info → iints_sdk_python35-1.5.3.dist-info}/top_level.txt +0 -0
iints/__init__.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# src/iints/__init__.py
|
|
2
2
|
|
|
3
3
|
import pandas as pd # Required for type hints like pd.DataFrame
|
|
4
|
-
from typing import Optional
|
|
4
|
+
from typing import Any, Optional
|
|
5
5
|
|
|
6
6
|
try:
|
|
7
7
|
from importlib.metadata import PackageNotFoundError, version
|
|
@@ -11,11 +11,18 @@ 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.5.
|
|
14
|
+
__version__ = "1.5.3"
|
|
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.
|
|
18
18
|
|
|
19
|
+
|
|
20
|
+
def _missing_reports_dependency(feature: str, exc: Exception) -> None:
|
|
21
|
+
raise ImportError(
|
|
22
|
+
f"{feature} requires the optional reporting stack. Install "
|
|
23
|
+
f"'iints-sdk-python35[reports]' or 'iints-sdk-python35[full]'."
|
|
24
|
+
) from exc
|
|
25
|
+
|
|
19
26
|
# API Components for Algorithm Development
|
|
20
27
|
from .api.base_algorithm import (
|
|
21
28
|
InsulinAlgorithm,
|
|
@@ -72,16 +79,75 @@ from .data.guardians import mdmp_gate, MDMPGateError
|
|
|
72
79
|
from .data.synthetic_mirror import generate_synthetic_mirror, SyntheticMirrorArtifact
|
|
73
80
|
from .data.study_corruption import AVAILABLE_STUDY_CORRUPTIONS, apply_study_corruptions, write_corrupted_study_csv
|
|
74
81
|
from .analysis.metrics import generate_benchmark_metrics # Added for benchmark
|
|
75
|
-
from .analysis.booth_demo import build_booth_demo
|
|
76
|
-
from .analysis.carelink_workbench import build_carelink_workbench
|
|
77
|
-
from .analysis.poster import generate_results_poster
|
|
78
|
-
from .analysis.reporting import ClinicalReportGenerator
|
|
79
82
|
from .analysis.study_protocol import build_study_protocol_payload, render_study_protocol_markdown, write_study_protocol_bundle
|
|
80
83
|
from .analysis.edge_efficiency import EnergyEstimate, estimate_energy_per_decision
|
|
81
84
|
from .ai import AIResponse, IINTSAssistant, MDMPGuard
|
|
82
|
-
from .
|
|
85
|
+
from .live_patient import (
|
|
86
|
+
create_edge_bundle,
|
|
87
|
+
export_edge_setup,
|
|
88
|
+
LivePatientDaemon,
|
|
89
|
+
PatientRuntimeConfig,
|
|
90
|
+
create_patient_app,
|
|
91
|
+
export_uno_q_bridge,
|
|
92
|
+
get_runtime_scenario_profile,
|
|
93
|
+
list_runtime_scenario_profiles,
|
|
94
|
+
run_edge_benchmark,
|
|
95
|
+
summarize_edge_workspace,
|
|
96
|
+
write_edge_update_script,
|
|
97
|
+
)
|
|
83
98
|
from .scenarios import ScenarioGeneratorConfig, generate_random_scenario
|
|
84
99
|
|
|
100
|
+
|
|
101
|
+
def run_simulation(*args: Any, **kwargs: Any) -> Any:
|
|
102
|
+
from .highlevel import run_simulation as _run_simulation
|
|
103
|
+
|
|
104
|
+
return _run_simulation(*args, **kwargs)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def run_full(*args: Any, **kwargs: Any) -> Any:
|
|
108
|
+
from .highlevel import run_full as _run_full
|
|
109
|
+
|
|
110
|
+
return _run_full(*args, **kwargs)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def run_population(*args: Any, **kwargs: Any) -> Any:
|
|
114
|
+
from .highlevel import run_population as _run_population
|
|
115
|
+
|
|
116
|
+
return _run_population(*args, **kwargs)
|
|
117
|
+
|
|
118
|
+
try:
|
|
119
|
+
from .analysis.booth_demo import build_booth_demo
|
|
120
|
+
except Exception as exc: # pragma: no cover - optional reports stack
|
|
121
|
+
_build_booth_demo_exc = exc
|
|
122
|
+
|
|
123
|
+
def build_booth_demo(*args, **kwargs): # type: ignore[misc,no-redef]
|
|
124
|
+
_missing_reports_dependency("build_booth_demo()", _build_booth_demo_exc)
|
|
125
|
+
|
|
126
|
+
try:
|
|
127
|
+
from .analysis.carelink_workbench import build_carelink_workbench
|
|
128
|
+
except Exception as exc: # pragma: no cover - optional reports stack
|
|
129
|
+
_build_carelink_workbench_exc = exc
|
|
130
|
+
|
|
131
|
+
def build_carelink_workbench(*args, **kwargs): # type: ignore[misc,no-redef]
|
|
132
|
+
_missing_reports_dependency("build_carelink_workbench()", _build_carelink_workbench_exc)
|
|
133
|
+
|
|
134
|
+
try:
|
|
135
|
+
from .analysis.poster import generate_results_poster
|
|
136
|
+
except Exception as exc: # pragma: no cover - optional reports stack
|
|
137
|
+
_generate_results_poster_exc = exc
|
|
138
|
+
|
|
139
|
+
def generate_results_poster(*args, **kwargs): # type: ignore[misc,no-redef]
|
|
140
|
+
_missing_reports_dependency("generate_results_poster()", _generate_results_poster_exc)
|
|
141
|
+
|
|
142
|
+
try:
|
|
143
|
+
from .analysis.reporting import ClinicalReportGenerator
|
|
144
|
+
except Exception as exc: # pragma: no cover - optional reports stack
|
|
145
|
+
_clinical_report_generator_exc = exc
|
|
146
|
+
|
|
147
|
+
class ClinicalReportGenerator: # type: ignore[no-redef]
|
|
148
|
+
def __init__(self, *args, **kwargs):
|
|
149
|
+
_missing_reports_dependency("ClinicalReportGenerator", _clinical_report_generator_exc)
|
|
150
|
+
|
|
85
151
|
# Population testing
|
|
86
152
|
from .population import (
|
|
87
153
|
PopulationGenerator,
|
|
@@ -202,6 +268,17 @@ __all__ = [
|
|
|
202
268
|
"AIResponse",
|
|
203
269
|
"IINTSAssistant",
|
|
204
270
|
"MDMPGuard",
|
|
271
|
+
"create_edge_bundle",
|
|
272
|
+
"export_edge_setup",
|
|
273
|
+
"LivePatientDaemon",
|
|
274
|
+
"PatientRuntimeConfig",
|
|
275
|
+
"create_patient_app",
|
|
276
|
+
"export_uno_q_bridge",
|
|
277
|
+
"get_runtime_scenario_profile",
|
|
278
|
+
"list_runtime_scenario_profiles",
|
|
279
|
+
"run_edge_benchmark",
|
|
280
|
+
"summarize_edge_workspace",
|
|
281
|
+
"write_edge_update_script",
|
|
205
282
|
# Reporting
|
|
206
283
|
"generate_report",
|
|
207
284
|
"generate_quickstart_report",
|
iints/analysis/__init__.py
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
from .clinical_metrics import ClinicalMetricsCalculator, ClinicalMetricsResult
|
|
2
2
|
from .baseline import compute_metrics, run_baseline_comparison, write_baseline_comparison
|
|
3
|
-
from .
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
from .study_engine import (
|
|
4
|
+
StudyAlgorithmSpec,
|
|
5
|
+
StudyArmSpec,
|
|
6
|
+
StudyDesignPayload,
|
|
7
|
+
StudyMatrixRow,
|
|
8
|
+
StudyProfileSpec,
|
|
9
|
+
build_algorithm_registry,
|
|
10
|
+
build_study_design_payload,
|
|
11
|
+
resolve_profile_specs,
|
|
12
|
+
)
|
|
8
13
|
from .study_protocol import (
|
|
9
14
|
build_study_protocol_payload,
|
|
10
15
|
render_study_protocol_markdown,
|
|
@@ -20,6 +25,69 @@ from .study_analysis import (
|
|
|
20
25
|
StudyRunSummary,
|
|
21
26
|
StudySummary,
|
|
22
27
|
)
|
|
28
|
+
from .eucys_results import (
|
|
29
|
+
build_eucys_abstract_draft_markdown,
|
|
30
|
+
build_eucys_filled_abstract_markdown,
|
|
31
|
+
build_eucys_jury_qa_markdown,
|
|
32
|
+
build_eucys_limitations_and_ethics_markdown,
|
|
33
|
+
build_eucys_poster_outline_markdown,
|
|
34
|
+
generate_eucys_main_figure,
|
|
35
|
+
generate_eucys_results_bundle,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _missing_reports_dependency(feature: str, exc: Exception) -> None:
|
|
40
|
+
raise ImportError(
|
|
41
|
+
f"{feature} requires the optional reporting stack. Install "
|
|
42
|
+
f"'iints-sdk-python35[reports]' or 'iints-sdk-python35[full]'."
|
|
43
|
+
) from exc
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
from .booth_demo import build_booth_demo
|
|
48
|
+
except Exception as exc: # pragma: no cover - optional reports stack
|
|
49
|
+
_build_booth_demo_exc = exc
|
|
50
|
+
|
|
51
|
+
def build_booth_demo(*args, **kwargs): # type: ignore[misc,no-redef]
|
|
52
|
+
_missing_reports_dependency("build_booth_demo()", _build_booth_demo_exc)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
try:
|
|
56
|
+
from .carelink_workbench import build_carelink_workbench
|
|
57
|
+
except Exception as exc: # pragma: no cover - optional reports stack
|
|
58
|
+
_build_carelink_workbench_exc = exc
|
|
59
|
+
|
|
60
|
+
def build_carelink_workbench(*args, **kwargs): # type: ignore[misc,no-redef]
|
|
61
|
+
_missing_reports_dependency("build_carelink_workbench()", _build_carelink_workbench_exc)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
from .poster import generate_results_poster
|
|
66
|
+
except Exception as exc: # pragma: no cover - optional reports stack
|
|
67
|
+
_generate_results_poster_exc = exc
|
|
68
|
+
|
|
69
|
+
def generate_results_poster(*args, **kwargs): # type: ignore[misc,no-redef]
|
|
70
|
+
_missing_reports_dependency("generate_results_poster()", _generate_results_poster_exc)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
try:
|
|
74
|
+
from .reporting import ClinicalReportGenerator
|
|
75
|
+
except Exception as exc: # pragma: no cover - optional reports stack
|
|
76
|
+
_clinical_report_generator_exc = exc
|
|
77
|
+
|
|
78
|
+
class ClinicalReportGenerator: # type: ignore[no-redef]
|
|
79
|
+
def __init__(self, *args, **kwargs):
|
|
80
|
+
_missing_reports_dependency("ClinicalReportGenerator", _clinical_report_generator_exc)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
try:
|
|
84
|
+
from .study_poster import generate_study_poster
|
|
85
|
+
except Exception as exc: # pragma: no cover - optional reports stack
|
|
86
|
+
_generate_study_poster_exc = exc
|
|
87
|
+
|
|
88
|
+
def generate_study_poster(*args, **kwargs): # type: ignore[misc,no-redef]
|
|
89
|
+
_missing_reports_dependency("generate_study_poster()", _generate_study_poster_exc)
|
|
90
|
+
|
|
23
91
|
|
|
24
92
|
__all__ = [
|
|
25
93
|
"analyze_run_directory",
|
|
@@ -31,15 +99,30 @@ __all__ = [
|
|
|
31
99
|
"ClinicalReportGenerator",
|
|
32
100
|
"compute_metrics",
|
|
33
101
|
"compare_studies",
|
|
102
|
+
"build_eucys_abstract_draft_markdown",
|
|
103
|
+
"build_eucys_filled_abstract_markdown",
|
|
104
|
+
"build_eucys_jury_qa_markdown",
|
|
105
|
+
"build_eucys_limitations_and_ethics_markdown",
|
|
106
|
+
"build_eucys_poster_outline_markdown",
|
|
107
|
+
"generate_eucys_main_figure",
|
|
108
|
+
"resolve_profile_specs",
|
|
109
|
+
"generate_eucys_results_bundle",
|
|
34
110
|
"generate_results_poster",
|
|
35
111
|
"generate_study_poster",
|
|
112
|
+
"build_algorithm_registry",
|
|
113
|
+
"build_study_design_payload",
|
|
36
114
|
"build_study_protocol_payload",
|
|
37
115
|
"render_study_protocol_markdown",
|
|
38
116
|
"write_study_protocol_bundle",
|
|
39
117
|
"load_study_summary",
|
|
40
118
|
"quality_badges_for_metrics",
|
|
41
119
|
"run_baseline_comparison",
|
|
120
|
+
"StudyAlgorithmSpec",
|
|
121
|
+
"StudyArmSpec",
|
|
42
122
|
"StudyComparison",
|
|
123
|
+
"StudyDesignPayload",
|
|
124
|
+
"StudyMatrixRow",
|
|
125
|
+
"StudyProfileSpec",
|
|
43
126
|
"StudyRunSummary",
|
|
44
127
|
"StudySummary",
|
|
45
128
|
"write_baseline_comparison",
|
iints/analysis/baseline.py
CHANGED
|
@@ -12,6 +12,7 @@ from iints.core.algorithms.pid_controller import PIDController
|
|
|
12
12
|
from iints.core.algorithms.standard_pump_algo import StandardPumpAlgorithm
|
|
13
13
|
from iints.core.patient.models import PatientModel
|
|
14
14
|
from iints.core.simulator import Simulator
|
|
15
|
+
from iints.utils.csv_safety import sanitize_csv_dataframe
|
|
15
16
|
from iints.validation import build_stress_events
|
|
16
17
|
|
|
17
18
|
|
|
@@ -88,5 +89,5 @@ def write_baseline_comparison(comparison: Dict[str, Any], output_dir: Path) -> D
|
|
|
88
89
|
json_path = output_dir / "baseline_comparison.json"
|
|
89
90
|
csv_path = output_dir / "baseline_comparison.csv"
|
|
90
91
|
json_path.write_text(json.dumps(comparison, indent=2))
|
|
91
|
-
pd.DataFrame(comparison.get("rows", [])).to_csv(csv_path, index=False)
|
|
92
|
+
sanitize_csv_dataframe(pd.DataFrame(comparison.get("rows", []))).to_csv(csv_path, index=False)
|
|
92
93
|
return {"json": str(json_path), "csv": str(csv_path)}
|
iints/analysis/booth_demo.py
CHANGED
|
@@ -9,6 +9,7 @@ from iints.ai.prepare import prepare_ai_ready_artifacts
|
|
|
9
9
|
from iints.analysis.poster import generate_results_poster
|
|
10
10
|
from iints.core.algorithms.mock_algorithms import RunawayAIAlgorithm
|
|
11
11
|
from iints.core.algorithms.pid_controller import PIDController
|
|
12
|
+
from iints.core.safety.config import SafetyConfig
|
|
12
13
|
from iints.highlevel import run_full
|
|
13
14
|
|
|
14
15
|
|
|
@@ -22,6 +23,17 @@ class BoothScenarioSpec:
|
|
|
22
23
|
algorithm_factory: Callable[[], Any]
|
|
23
24
|
|
|
24
25
|
|
|
26
|
+
@dataclass(frozen=True)
|
|
27
|
+
class ShowcaseRunSpec:
|
|
28
|
+
run_dir: Path
|
|
29
|
+
algorithm_factory: Callable[[], Any]
|
|
30
|
+
algorithm_name: str
|
|
31
|
+
algorithm_role: str
|
|
32
|
+
study_arm: str
|
|
33
|
+
condition_group: str
|
|
34
|
+
supervisor_enabled: bool
|
|
35
|
+
|
|
36
|
+
|
|
25
37
|
def _scenario_specs() -> list[BoothScenarioSpec]:
|
|
26
38
|
return [
|
|
27
39
|
BoothScenarioSpec(
|
|
@@ -138,6 +150,269 @@ def _write_text(path: Path, content: str) -> None:
|
|
|
138
150
|
path.write_text(content, encoding="utf-8")
|
|
139
151
|
|
|
140
152
|
|
|
153
|
+
def _booth_profile_id(patient_config: str | Path | dict[str, Any]) -> str:
|
|
154
|
+
if isinstance(patient_config, dict):
|
|
155
|
+
return str(patient_config.get("patient_name") or patient_config.get("profile_id") or "booth_demo_patient")
|
|
156
|
+
if isinstance(patient_config, Path):
|
|
157
|
+
return patient_config.stem
|
|
158
|
+
return Path(str(patient_config)).stem if str(patient_config).endswith(".json") else str(patient_config)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _booth_supervisor_off_safety_config() -> SafetyConfig:
|
|
162
|
+
return SafetyConfig(
|
|
163
|
+
min_glucose=10.0,
|
|
164
|
+
max_glucose=1000.0,
|
|
165
|
+
max_glucose_delta_per_5_min=250.0,
|
|
166
|
+
hypoglycemia_threshold=-1000.0,
|
|
167
|
+
severe_hypoglycemia_threshold=-1000.0,
|
|
168
|
+
hyperglycemia_threshold=10000.0,
|
|
169
|
+
max_insulin_per_bolus=1000.0,
|
|
170
|
+
glucose_rate_alarm=-1000.0,
|
|
171
|
+
max_insulin_per_hour=1000.0,
|
|
172
|
+
max_iob=1000.0,
|
|
173
|
+
trend_stop=-1000.0,
|
|
174
|
+
hypo_cutoff=-1000.0,
|
|
175
|
+
predicted_hypoglycemia_threshold=-1000.0,
|
|
176
|
+
predictor_uncertainty_gate_enabled=False,
|
|
177
|
+
predictor_ood_gate_enabled=False,
|
|
178
|
+
contract_enabled=False,
|
|
179
|
+
critical_glucose_threshold=-1000.0,
|
|
180
|
+
critical_glucose_duration_minutes=100000,
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def _annotate_showcase_run(
|
|
185
|
+
run_dir: Path,
|
|
186
|
+
*,
|
|
187
|
+
study_arm: str,
|
|
188
|
+
condition_group: str,
|
|
189
|
+
algorithm_name: str,
|
|
190
|
+
algorithm_role: str,
|
|
191
|
+
profile_id: str,
|
|
192
|
+
scenario_slug: str,
|
|
193
|
+
supervisor_enabled: bool,
|
|
194
|
+
) -> None:
|
|
195
|
+
from iints.analysis.study_engine import slugify_study_token
|
|
196
|
+
|
|
197
|
+
algorithm_id = slugify_study_token(algorithm_name)
|
|
198
|
+
for candidate in (run_dir / "run_metadata.json", run_dir / "config.json"):
|
|
199
|
+
payload: dict[str, Any]
|
|
200
|
+
if candidate.is_file():
|
|
201
|
+
payload = json.loads(candidate.read_text(encoding="utf-8"))
|
|
202
|
+
else:
|
|
203
|
+
payload = {}
|
|
204
|
+
|
|
205
|
+
if candidate.name == "run_metadata.json":
|
|
206
|
+
config = payload.get("config", {}) if isinstance(payload.get("config"), dict) else {}
|
|
207
|
+
else:
|
|
208
|
+
config = payload if isinstance(payload, dict) else {}
|
|
209
|
+
|
|
210
|
+
config["study_condition"] = study_arm
|
|
211
|
+
config["study_arm"] = study_arm
|
|
212
|
+
config["condition_group"] = condition_group
|
|
213
|
+
config["study_protocol_preset"] = "showcase_demo"
|
|
214
|
+
config["algorithm_id"] = algorithm_id
|
|
215
|
+
config["algorithm_role"] = algorithm_role
|
|
216
|
+
config["profile_id"] = profile_id
|
|
217
|
+
config["scenario_slug"] = scenario_slug
|
|
218
|
+
config["supervisor_enabled"] = supervisor_enabled
|
|
219
|
+
config["corruption_modes"] = []
|
|
220
|
+
scenario_payload = config.get("scenario", {}) if isinstance(config.get("scenario"), dict) else {}
|
|
221
|
+
scenario_payload["condition_group"] = condition_group
|
|
222
|
+
scenario_payload["study_arm"] = study_arm
|
|
223
|
+
scenario_payload["study_protocol_preset"] = "showcase_demo"
|
|
224
|
+
scenario_payload["scenario_slug"] = scenario_slug
|
|
225
|
+
scenario_payload["supervisor_enabled"] = supervisor_enabled
|
|
226
|
+
config["scenario"] = scenario_payload
|
|
227
|
+
|
|
228
|
+
if candidate.name == "run_metadata.json":
|
|
229
|
+
payload["config"] = config
|
|
230
|
+
payload["algorithm_id"] = algorithm_id
|
|
231
|
+
payload["algorithm_role"] = algorithm_role
|
|
232
|
+
payload["profile_id"] = profile_id
|
|
233
|
+
else:
|
|
234
|
+
payload = config
|
|
235
|
+
|
|
236
|
+
candidate.write_text(json.dumps(payload, indent=2), encoding="utf-8")
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def _write_showcase_research_sync(
|
|
240
|
+
*,
|
|
241
|
+
output_dir: Path,
|
|
242
|
+
patient_config: str | Path | dict[str, Any],
|
|
243
|
+
duration_minutes: int,
|
|
244
|
+
time_step: int,
|
|
245
|
+
seed: int,
|
|
246
|
+
ai_status: str,
|
|
247
|
+
) -> dict[str, str]:
|
|
248
|
+
from iints.analysis.study_analysis import analyze_study_directory, compare_studies
|
|
249
|
+
from iints.analysis.study_poster import generate_study_poster
|
|
250
|
+
|
|
251
|
+
profile_id = _booth_profile_id(patient_config)
|
|
252
|
+
showcase_dir = output_dir / "showcase_study"
|
|
253
|
+
baseline_dir = showcase_dir / "baseline_reference" / "pid_supervisor_override"
|
|
254
|
+
candidate_on_dir = showcase_dir / "candidate_safety_on" / "runaway_supervisor_override"
|
|
255
|
+
candidate_off_dir = showcase_dir / "candidate_safety_off" / "runaway_supervisor_override"
|
|
256
|
+
|
|
257
|
+
supervisor_spec = next(spec for spec in _scenario_specs() if spec.slug == "03_supervisor_override")
|
|
258
|
+
showcase_runs = [
|
|
259
|
+
ShowcaseRunSpec(
|
|
260
|
+
run_dir=baseline_dir,
|
|
261
|
+
algorithm_factory=PIDController,
|
|
262
|
+
algorithm_name="PID Controller",
|
|
263
|
+
algorithm_role="baseline",
|
|
264
|
+
study_arm="showcase_baseline_vs_candidate",
|
|
265
|
+
condition_group="showcase_baseline_vs_candidate",
|
|
266
|
+
supervisor_enabled=True,
|
|
267
|
+
),
|
|
268
|
+
ShowcaseRunSpec(
|
|
269
|
+
run_dir=candidate_on_dir,
|
|
270
|
+
algorithm_factory=supervisor_spec.algorithm_factory,
|
|
271
|
+
algorithm_name="Runaway AI Candidate",
|
|
272
|
+
algorithm_role="candidate",
|
|
273
|
+
study_arm="showcase_baseline_vs_candidate",
|
|
274
|
+
condition_group="showcase_baseline_vs_candidate",
|
|
275
|
+
supervisor_enabled=True,
|
|
276
|
+
),
|
|
277
|
+
ShowcaseRunSpec(
|
|
278
|
+
run_dir=candidate_off_dir,
|
|
279
|
+
algorithm_factory=supervisor_spec.algorithm_factory,
|
|
280
|
+
algorithm_name="Runaway AI Candidate",
|
|
281
|
+
algorithm_role="candidate",
|
|
282
|
+
study_arm="showcase_candidate_safety_off",
|
|
283
|
+
condition_group="showcase_candidate_safety_off",
|
|
284
|
+
supervisor_enabled=False,
|
|
285
|
+
),
|
|
286
|
+
]
|
|
287
|
+
|
|
288
|
+
for run_spec in showcase_runs:
|
|
289
|
+
run_full(
|
|
290
|
+
algorithm=run_spec.algorithm_factory(),
|
|
291
|
+
scenario=supervisor_spec.scenario,
|
|
292
|
+
patient_config=patient_config,
|
|
293
|
+
duration_minutes=duration_minutes,
|
|
294
|
+
time_step=time_step,
|
|
295
|
+
seed=seed,
|
|
296
|
+
output_dir=run_spec.run_dir,
|
|
297
|
+
safety_config=None if run_spec.supervisor_enabled else _booth_supervisor_off_safety_config(),
|
|
298
|
+
enable_profiling=False,
|
|
299
|
+
)
|
|
300
|
+
_annotate_showcase_run(
|
|
301
|
+
run_spec.run_dir,
|
|
302
|
+
study_arm=run_spec.study_arm,
|
|
303
|
+
condition_group=run_spec.condition_group,
|
|
304
|
+
algorithm_name=run_spec.algorithm_name,
|
|
305
|
+
algorithm_role=run_spec.algorithm_role,
|
|
306
|
+
profile_id=profile_id,
|
|
307
|
+
scenario_slug="showcase_supervisor_override",
|
|
308
|
+
supervisor_enabled=run_spec.supervisor_enabled,
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
summary = analyze_study_directory(showcase_dir)
|
|
312
|
+
payload = summary.to_dict()
|
|
313
|
+
summary_json = showcase_dir / "showcase_study_summary.json"
|
|
314
|
+
summary_md = showcase_dir / "showcase_study_summary.md"
|
|
315
|
+
summary_json.write_text(json.dumps(payload, indent=2), encoding="utf-8")
|
|
316
|
+
summary_md.write_text(
|
|
317
|
+
"# Showcase Research Sync\n\n"
|
|
318
|
+
"This mini-study mirrors the benchmark story used elsewhere in the SDK.\n\n"
|
|
319
|
+
f"- Profile: `{profile_id}`\n"
|
|
320
|
+
"- Scenario: `showcase_supervisor_override`\n"
|
|
321
|
+
"- Baseline: `PID Controller`\n"
|
|
322
|
+
"- Candidate: `Runaway AI Candidate`\n"
|
|
323
|
+
"- Safety comparison: candidate with supervisor on vs candidate with supervisor off\n",
|
|
324
|
+
encoding="utf-8",
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
poster_outputs = generate_study_poster(
|
|
328
|
+
summary,
|
|
329
|
+
output_path=showcase_dir / "showcase_study_poster.png",
|
|
330
|
+
title="IINTS Showcase Benchmark Sync",
|
|
331
|
+
subtitle="Baseline vs candidate, plus safety-on vs safety-off, with the same metrics used in study bundles.",
|
|
332
|
+
summary_output_path=showcase_dir / "showcase_study_poster.json",
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
baseline_vs_candidate = compare_studies(baseline_dir, candidate_on_dir, left_label="PID baseline", right_label="Candidate safety on").to_dict()
|
|
336
|
+
safety_on_vs_off = compare_studies(candidate_on_dir, candidate_off_dir, left_label="Candidate safety on", right_label="Candidate safety off").to_dict()
|
|
337
|
+
comparisons_dir = showcase_dir / "comparisons"
|
|
338
|
+
comparisons_dir.mkdir(parents=True, exist_ok=True)
|
|
339
|
+
baseline_vs_candidate_json = comparisons_dir / "baseline_vs_candidate.json"
|
|
340
|
+
safety_on_vs_off_json = comparisons_dir / "candidate_safety_on_vs_off.json"
|
|
341
|
+
baseline_vs_candidate_json.write_text(json.dumps(baseline_vs_candidate, indent=2), encoding="utf-8")
|
|
342
|
+
safety_on_vs_off_json.write_text(json.dumps(safety_on_vs_off, indent=2), encoding="utf-8")
|
|
343
|
+
|
|
344
|
+
baseline_delta = baseline_vs_candidate.get("delta", {})
|
|
345
|
+
safety_delta = safety_on_vs_off.get("delta", {})
|
|
346
|
+
explanation_lines = [
|
|
347
|
+
"# Showcase Explanation Panel",
|
|
348
|
+
"",
|
|
349
|
+
"Use this panel when you want the booth story to match the benchmark language used in the scientific workflow.",
|
|
350
|
+
"",
|
|
351
|
+
"## Baseline vs candidate",
|
|
352
|
+
"",
|
|
353
|
+
"- Baseline: `PID Controller`",
|
|
354
|
+
"- Candidate: `Runaway AI Candidate`",
|
|
355
|
+
f"- TIR delta (baseline - candidate): `{baseline_delta.get('mean_tir_70_180')}`",
|
|
356
|
+
f"- Intervention delta (baseline - candidate): `{baseline_delta.get('mean_supervisor_interventions')}`",
|
|
357
|
+
"",
|
|
358
|
+
"## Safety on vs safety off",
|
|
359
|
+
"",
|
|
360
|
+
"- Same candidate algorithm, same profile, same scenario",
|
|
361
|
+
f"- TIR delta (safety on - safety off): `{safety_delta.get('mean_tir_70_180')}`",
|
|
362
|
+
f"- Severe hypo delta: `{safety_delta.get('severe_hypo_runs')}`",
|
|
363
|
+
f"- Early termination delta: `{safety_delta.get('terminated_early_runs')}`",
|
|
364
|
+
"",
|
|
365
|
+
"## Plain-language talking points",
|
|
366
|
+
"",
|
|
367
|
+
"- This panel compares a stable baseline with a deliberately bad candidate.",
|
|
368
|
+
"- Then it compares the same candidate with the safety layer on and off.",
|
|
369
|
+
"- That makes the public demo line up with the benchmark logic used in the full study engine.",
|
|
370
|
+
"",
|
|
371
|
+
]
|
|
372
|
+
explanation_panel = showcase_dir / "SHOWCASE_EXPLANATION_PANEL.md"
|
|
373
|
+
_write_text(explanation_panel, "\n".join(explanation_lines))
|
|
374
|
+
|
|
375
|
+
sync_lines = [
|
|
376
|
+
"# Showcase Research Sync",
|
|
377
|
+
"",
|
|
378
|
+
"This artifact links the fair demo to the scientific benchmark language used elsewhere in the SDK.",
|
|
379
|
+
"",
|
|
380
|
+
"## What this mirrors",
|
|
381
|
+
"",
|
|
382
|
+
"- Baseline vs candidate comparison",
|
|
383
|
+
"- Safety-on vs safety-off comparison",
|
|
384
|
+
"- The same TIR, hypo, intervention, uncertainty, and calibration vocabulary used in `run-study` bundles",
|
|
385
|
+
"",
|
|
386
|
+
"## Files",
|
|
387
|
+
"",
|
|
388
|
+
f"- Summary JSON: `{summary_json}`",
|
|
389
|
+
f"- Poster PNG: `{poster_outputs['poster_png']}`",
|
|
390
|
+
f"- Baseline vs candidate: `{baseline_vs_candidate_json}`",
|
|
391
|
+
f"- Safety on vs off: `{safety_on_vs_off_json}`",
|
|
392
|
+
"",
|
|
393
|
+
"## AI explanation note",
|
|
394
|
+
"",
|
|
395
|
+
ai_status,
|
|
396
|
+
"",
|
|
397
|
+
"If local AI is available, use the candidate safety-on run as the explanation target so the live explanation matches the benchmark story.",
|
|
398
|
+
"",
|
|
399
|
+
]
|
|
400
|
+
sync_markdown = showcase_dir / "SHOWCASE_RESEARCH_SYNC.md"
|
|
401
|
+
_write_text(sync_markdown, "\n".join(sync_lines))
|
|
402
|
+
|
|
403
|
+
return {
|
|
404
|
+
"showcase_study_dir": str(showcase_dir),
|
|
405
|
+
"showcase_study_summary_json": str(summary_json),
|
|
406
|
+
"showcase_study_summary_md": str(summary_md),
|
|
407
|
+
"showcase_study_poster_png": str(poster_outputs["poster_png"]),
|
|
408
|
+
"showcase_study_poster_json": str(poster_outputs["poster_summary_json"]),
|
|
409
|
+
"showcase_baseline_vs_candidate_json": str(baseline_vs_candidate_json),
|
|
410
|
+
"showcase_safety_on_vs_off_json": str(safety_on_vs_off_json),
|
|
411
|
+
"showcase_research_sync_md": str(sync_markdown),
|
|
412
|
+
"showcase_explanation_panel_md": str(explanation_panel),
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
|
|
141
416
|
def _build_jury_brief(
|
|
142
417
|
*,
|
|
143
418
|
output_dir: Path,
|
|
@@ -386,6 +661,15 @@ def build_booth_demo(
|
|
|
386
661
|
except Exception as exc:
|
|
387
662
|
ai_status = f"AI preparation did not block the demo, but it could not finish cleanly: {exc}"
|
|
388
663
|
|
|
664
|
+
showcase_outputs = _write_showcase_research_sync(
|
|
665
|
+
output_dir=resolved_output,
|
|
666
|
+
patient_config=patient_config,
|
|
667
|
+
duration_minutes=duration_minutes,
|
|
668
|
+
time_step=time_step,
|
|
669
|
+
seed=seed,
|
|
670
|
+
ai_status=ai_status,
|
|
671
|
+
)
|
|
672
|
+
|
|
389
673
|
summary_payload = {
|
|
390
674
|
"output_dir": str(resolved_output),
|
|
391
675
|
"patient_config": str(patient_config),
|
|
@@ -406,6 +690,7 @@ def build_booth_demo(
|
|
|
406
690
|
}
|
|
407
691
|
for spec in specs
|
|
408
692
|
],
|
|
693
|
+
"showcase_sync": showcase_outputs,
|
|
409
694
|
}
|
|
410
695
|
_write_json(resolved_output / "demo_summary.json", summary_payload)
|
|
411
696
|
|
|
@@ -450,4 +735,5 @@ def build_booth_demo(
|
|
|
450
735
|
for spec in specs:
|
|
451
736
|
artifact_paths[f"{spec.slug}_dir"] = run_outputs[spec.slug]["output_dir"]
|
|
452
737
|
artifact_paths.update(ai_outputs)
|
|
738
|
+
artifact_paths.update(showcase_outputs)
|
|
453
739
|
return artifact_paths
|