iints-sdk-python35 1.4.0__py3-none-any.whl → 1.5.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 +9 -1
- iints/ai/assistant.py +3 -0
- iints/ai/cli.py +60 -0
- iints/ai/prepare.py +27 -0
- iints/ai/prompts.py +20 -1
- iints/analysis/__init__.py +28 -0
- iints/analysis/booth_demo.py +3 -0
- iints/analysis/carelink_workbench.py +17 -0
- iints/analysis/study_analysis.py +721 -0
- iints/analysis/study_poster.py +150 -0
- iints/analysis/study_protocol.py +310 -0
- iints/cli/cli.py +846 -15
- iints/data/__init__.py +20 -0
- iints/data/certify.py +80 -0
- iints/data/study_corruption.py +267 -0
- iints/scenarios/__init__.py +16 -1
- iints/scenarios/study_pack.py +293 -0
- iints/templates/demos/live_stage_demo.py +2 -0
- iints_sdk_python35-1.5.1.dist-info/METADATA +99 -0
- {iints_sdk_python35-1.4.0.dist-info → iints_sdk_python35-1.5.1.dist-info}/RECORD +27 -21
- {iints_sdk_python35-1.4.0.dist-info → iints_sdk_python35-1.5.1.dist-info}/entry_points.txt +0 -1
- mdmp_core/cli.py +2 -1
- iints_sdk_python35-1.4.0.dist-info/METADATA +0 -423
- {iints_sdk_python35-1.4.0.dist-info → iints_sdk_python35-1.5.1.dist-info}/WHEEL +0 -0
- {iints_sdk_python35-1.4.0.dist-info → iints_sdk_python35-1.5.1.dist-info}/licenses/LICENSE +0 -0
- {iints_sdk_python35-1.4.0.dist-info → iints_sdk_python35-1.5.1.dist-info}/licenses/LICENSE-MIT-IINTS-LEGACY +0 -0
- {iints_sdk_python35-1.4.0.dist-info → iints_sdk_python35-1.5.1.dist-info}/licenses/NOTICE +0 -0
- {iints_sdk_python35-1.4.0.dist-info → iints_sdk_python35-1.5.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.5.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.
|
|
@@ -70,11 +70,13 @@ from .data.nightscout import NightscoutConfig, import_nightscout
|
|
|
70
70
|
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
|
+
from .data.study_corruption import AVAILABLE_STUDY_CORRUPTIONS, apply_study_corruptions, write_corrupted_study_csv
|
|
73
74
|
from .analysis.metrics import generate_benchmark_metrics # Added for benchmark
|
|
74
75
|
from .analysis.booth_demo import build_booth_demo
|
|
75
76
|
from .analysis.carelink_workbench import build_carelink_workbench
|
|
76
77
|
from .analysis.poster import generate_results_poster
|
|
77
78
|
from .analysis.reporting import ClinicalReportGenerator
|
|
79
|
+
from .analysis.study_protocol import build_study_protocol_payload, render_study_protocol_markdown, write_study_protocol_bundle
|
|
78
80
|
from .analysis.edge_efficiency import EnergyEstimate, estimate_energy_per_decision
|
|
79
81
|
from .ai import AIResponse, IINTSAssistant, MDMPGuard
|
|
80
82
|
from .highlevel import run_simulation, run_full, run_population
|
|
@@ -184,10 +186,16 @@ __all__ = [
|
|
|
184
186
|
"MDMPGateError",
|
|
185
187
|
"generate_synthetic_mirror",
|
|
186
188
|
"SyntheticMirrorArtifact",
|
|
189
|
+
"AVAILABLE_STUDY_CORRUPTIONS",
|
|
190
|
+
"apply_study_corruptions",
|
|
191
|
+
"write_corrupted_study_csv",
|
|
187
192
|
# Analysis Metrics
|
|
188
193
|
"generate_benchmark_metrics",
|
|
189
194
|
"build_booth_demo",
|
|
190
195
|
"build_carelink_workbench",
|
|
196
|
+
"build_study_protocol_payload",
|
|
197
|
+
"render_study_protocol_markdown",
|
|
198
|
+
"write_study_protocol_bundle",
|
|
191
199
|
"ClinicalReportGenerator",
|
|
192
200
|
"EnergyEstimate",
|
|
193
201
|
"estimate_energy_per_decision",
|
iints/ai/assistant.py
CHANGED
iints/ai/cli.py
CHANGED
|
@@ -43,6 +43,7 @@ def _default_prepared_payload(task: str, ai_dir: Path) -> Path:
|
|
|
43
43
|
"trends": ["trends_payload.json"],
|
|
44
44
|
"anomalies": ["anomalies_payload.json"],
|
|
45
45
|
"report": ["report_payload.json"],
|
|
46
|
+
"review": ["review_payload.json", "report_payload.json"],
|
|
46
47
|
}.get(task, [])
|
|
47
48
|
for filename in candidates:
|
|
48
49
|
candidate = ai_dir / filename
|
|
@@ -69,6 +70,15 @@ def _resolve_cli_inputs(
|
|
|
69
70
|
|
|
70
71
|
if input_path.is_dir():
|
|
71
72
|
ai_dir = input_path / "ai"
|
|
73
|
+
if (
|
|
74
|
+
not ai_dir.exists()
|
|
75
|
+
or not any((ai_dir / candidate).is_file() for candidate in ("report_payload.json", "trends_payload.json", "anomalies_payload.json", "step_riskiest.json", "review_payload.json"))
|
|
76
|
+
or (resolved_cert is None and not (ai_dir / "report.signed.mdmp").is_file())
|
|
77
|
+
):
|
|
78
|
+
prepare_ai_ready_artifacts(
|
|
79
|
+
input_path,
|
|
80
|
+
create_dev_mdmp_cert=resolved_cert is None,
|
|
81
|
+
)
|
|
72
82
|
resolved_input = _default_prepared_payload(task, ai_dir)
|
|
73
83
|
if resolved_cert is None:
|
|
74
84
|
candidate_cert = ai_dir / "report.signed.mdmp"
|
|
@@ -95,6 +105,14 @@ def _write_output(path: Path | None, response: AIResponse) -> None:
|
|
|
95
105
|
path.write_text(response.text + "\n", encoding="utf-8")
|
|
96
106
|
|
|
97
107
|
|
|
108
|
+
def _default_output_for_review(input_path: Path, output: Path | None) -> Path | None:
|
|
109
|
+
if output is not None:
|
|
110
|
+
return output
|
|
111
|
+
if input_path.is_dir():
|
|
112
|
+
return input_path / "ai" / "realism_review.md"
|
|
113
|
+
return None
|
|
114
|
+
|
|
115
|
+
|
|
98
116
|
def _render_response(console: Console, title: str, response: AIResponse, output: Path | None) -> None:
|
|
99
117
|
console.print(Panel(response.text, title=title, border_style="cyan"))
|
|
100
118
|
console.print(
|
|
@@ -438,3 +456,45 @@ def report(
|
|
|
438
456
|
except Exception as exc:
|
|
439
457
|
console.print(f"[bold red]Error:[/bold red] {exc}")
|
|
440
458
|
raise typer.Exit(code=1)
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
@app.command("review")
|
|
462
|
+
def review(
|
|
463
|
+
input_json: Annotated[Path, typer.Argument(help="Prepared run directory or JSON file with run-level simulation outputs.")],
|
|
464
|
+
mdmp_cert: Annotated[Optional[Path], typer.Option(help="Signed MDMP artifact required before AI analysis can run.")] = None,
|
|
465
|
+
mode: Annotated[str, typer.Option(help="AI backend mode. Use 'local' for Ollama/Ministral.")] = "auto",
|
|
466
|
+
model: Annotated[str, typer.Option(help="Ollama model name to use.")] = DEFAULT_MINISTRAL_MODEL,
|
|
467
|
+
minimum_grade: Annotated[str, typer.Option(help="Minimum MDMP grade required to allow analysis.")] = "research_grade",
|
|
468
|
+
public_key: Annotated[Optional[Path], typer.Option(help="Explicit MDMP public key for verification.")] = None,
|
|
469
|
+
trust_store: Annotated[Optional[Path], typer.Option(help="MDMP trust store for verification.")] = None,
|
|
470
|
+
ollama_host: Annotated[Optional[str], typer.Option(help="Override the Ollama base URL.")] = None,
|
|
471
|
+
timeout_seconds: Annotated[float, typer.Option(help="HTTP timeout for Ollama generation requests.")] = 120.0,
|
|
472
|
+
output: Annotated[Optional[Path], typer.Option(help="Optional file path to save the realism review.")] = None,
|
|
473
|
+
) -> None:
|
|
474
|
+
console = Console()
|
|
475
|
+
try:
|
|
476
|
+
resolved_input, resolved_cert, resolved_public_key = _resolve_cli_inputs(
|
|
477
|
+
task="review",
|
|
478
|
+
input_path=input_json,
|
|
479
|
+
mdmp_cert=mdmp_cert,
|
|
480
|
+
public_key=public_key,
|
|
481
|
+
trust_store=trust_store,
|
|
482
|
+
)
|
|
483
|
+
payload = _load_json_payload(resolved_input, "Input JSON")
|
|
484
|
+
assistant = _build_assistant(
|
|
485
|
+
mdmp_cert=resolved_cert,
|
|
486
|
+
mode=mode,
|
|
487
|
+
model=model,
|
|
488
|
+
minimum_grade=minimum_grade,
|
|
489
|
+
public_key=resolved_public_key,
|
|
490
|
+
trust_store=trust_store,
|
|
491
|
+
ollama_host=ollama_host,
|
|
492
|
+
timeout_seconds=timeout_seconds,
|
|
493
|
+
)
|
|
494
|
+
resolved_output = _default_output_for_review(input_json, output)
|
|
495
|
+
response = assistant.review_realism(payload)
|
|
496
|
+
_write_output(resolved_output, response)
|
|
497
|
+
_render_response(console, "IINTS AI Realism Review", response, resolved_output)
|
|
498
|
+
except Exception as exc:
|
|
499
|
+
console.print(f"[bold red]Error:[/bold red] {exc}")
|
|
500
|
+
raise typer.Exit(code=1)
|
iints/ai/prepare.py
CHANGED
|
@@ -97,6 +97,13 @@ def _sample_trace(df: pd.DataFrame, *, max_rows: int = 48) -> list[dict[str, Any
|
|
|
97
97
|
return [_normalize_series_record(record) for record in sampled.to_dict(orient="records")]
|
|
98
98
|
|
|
99
99
|
|
|
100
|
+
def _max_glucose_delta_per_step(df: pd.DataFrame, glucose_column: str) -> float:
|
|
101
|
+
clean = pd.to_numeric(df[glucose_column], errors="coerce").dropna()
|
|
102
|
+
if clean.empty or len(clean) < 2:
|
|
103
|
+
return 0.0
|
|
104
|
+
return float(clean.diff().abs().dropna().max())
|
|
105
|
+
|
|
106
|
+
|
|
100
107
|
def _position_for_label(df: pd.DataFrame, label: Any) -> int:
|
|
101
108
|
location = df.index.get_loc(label)
|
|
102
109
|
if isinstance(location, int):
|
|
@@ -166,9 +173,12 @@ def _build_summary(df: pd.DataFrame, run_metadata: dict[str, Any], audit_summary
|
|
|
166
173
|
"mean_glucose_mgdl": _normalize_value(float(glucose.mean())),
|
|
167
174
|
"min_glucose_mgdl": _normalize_value(float(glucose.min())),
|
|
168
175
|
"max_glucose_mgdl": _normalize_value(float(glucose.max())),
|
|
176
|
+
"max_glucose_delta_per_step_mgdl": _normalize_value(_max_glucose_delta_per_step(df, glucose_column)),
|
|
169
177
|
"time_in_range_70_180_pct": _normalize_value(_time_in_band_pct(glucose, 70.0, 180.0)),
|
|
170
178
|
"time_below_70_pct": _normalize_value(float((glucose < 70.0).mean() * 100.0)),
|
|
179
|
+
"time_below_54_pct": _normalize_value(float((glucose < 54.0).mean() * 100.0)),
|
|
171
180
|
"time_above_180_pct": _normalize_value(float((glucose > 180.0).mean() * 100.0)),
|
|
181
|
+
"time_above_250_pct": _normalize_value(float((glucose > 250.0).mean() * 100.0)),
|
|
172
182
|
"delivered_insulin_total_units": _normalize_value(_safe_sum(df, "delivered_insulin_units")),
|
|
173
183
|
"recommended_insulin_total_units": _normalize_value(_safe_sum(df, "algo_recommended_insulin_units")),
|
|
174
184
|
"safety_trigger_count": _bool_sum(df, "safety_triggered"),
|
|
@@ -228,6 +238,23 @@ def _build_payloads(
|
|
|
228
238
|
"trace_sample": trace_sample,
|
|
229
239
|
"baseline_comparison": baseline_comparison,
|
|
230
240
|
},
|
|
241
|
+
"review_payload.json": {
|
|
242
|
+
**common,
|
|
243
|
+
"audit_summary": audit_summary,
|
|
244
|
+
"baseline_comparison": baseline_comparison,
|
|
245
|
+
"trace_sample": trace_sample,
|
|
246
|
+
"review_focus": {
|
|
247
|
+
"goal": "Judge whether the simulation or imported dataset looks physiologically plausible and internally coherent.",
|
|
248
|
+
"checks": [
|
|
249
|
+
"glucose range plausibility",
|
|
250
|
+
"excursion size and recovery behavior",
|
|
251
|
+
"time in range and severe hypo/hyper exposure",
|
|
252
|
+
"insulin delivery realism relative to observed glucose behavior",
|
|
253
|
+
"safety trigger and override consistency",
|
|
254
|
+
],
|
|
255
|
+
},
|
|
256
|
+
"run_manifest": run_manifest,
|
|
257
|
+
},
|
|
231
258
|
"step_riskiest.json": {
|
|
232
259
|
**common,
|
|
233
260
|
**risk_payload,
|
iints/ai/prompts.py
CHANGED
|
@@ -4,7 +4,13 @@ import json
|
|
|
4
4
|
from typing import Any, Literal
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
TaskName = Literal[
|
|
7
|
+
TaskName = Literal[
|
|
8
|
+
"explain_decision",
|
|
9
|
+
"analyze_trends",
|
|
10
|
+
"detect_anomalies",
|
|
11
|
+
"generate_report",
|
|
12
|
+
"review_realism",
|
|
13
|
+
]
|
|
8
14
|
MAX_PROMPT_PAYLOAD_CHARS = 12000
|
|
9
15
|
|
|
10
16
|
SYSTEM_PROMPT = (
|
|
@@ -52,6 +58,19 @@ TASK_TEMPLATES: dict[TaskName, str] = {
|
|
|
52
58
|
"5. Research-only conclusion\n\n"
|
|
53
59
|
"Input JSON:\n{data}"
|
|
54
60
|
),
|
|
61
|
+
"review_realism": (
|
|
62
|
+
"Review this simulation or imported-data payload and judge whether the results look physiologically plausible for research use.\n"
|
|
63
|
+
"Be conservative and do not overclaim. If the payload is incomplete, say so clearly.\n"
|
|
64
|
+
"Respond in markdown with these sections:\n"
|
|
65
|
+
"1. Overall realism verdict (Likely realistic / Needs review / Likely unrealistic)\n"
|
|
66
|
+
"2. What looks realistic\n"
|
|
67
|
+
"3. What looks suspicious\n"
|
|
68
|
+
"4. Priority fixes\n"
|
|
69
|
+
"5. What to improve next\n"
|
|
70
|
+
"6. Suggested follow-up validation checks\n\n"
|
|
71
|
+
"Focus on glycemic ranges, excursion patterns, insulin behavior, safety overrides, and whether the data looks internally coherent.\n\n"
|
|
72
|
+
"Input JSON:\n{data}"
|
|
73
|
+
),
|
|
55
74
|
}
|
|
56
75
|
|
|
57
76
|
|
iints/analysis/__init__.py
CHANGED
|
@@ -4,15 +4,43 @@ from .booth_demo import build_booth_demo
|
|
|
4
4
|
from .carelink_workbench import build_carelink_workbench
|
|
5
5
|
from .poster import generate_results_poster
|
|
6
6
|
from .reporting import ClinicalReportGenerator
|
|
7
|
+
from .study_poster import generate_study_poster
|
|
8
|
+
from .study_protocol import (
|
|
9
|
+
build_study_protocol_payload,
|
|
10
|
+
render_study_protocol_markdown,
|
|
11
|
+
write_study_protocol_bundle,
|
|
12
|
+
)
|
|
13
|
+
from .study_analysis import (
|
|
14
|
+
analyze_run_directory,
|
|
15
|
+
analyze_study_directory,
|
|
16
|
+
compare_studies,
|
|
17
|
+
load_study_summary,
|
|
18
|
+
quality_badges_for_metrics,
|
|
19
|
+
StudyComparison,
|
|
20
|
+
StudyRunSummary,
|
|
21
|
+
StudySummary,
|
|
22
|
+
)
|
|
7
23
|
|
|
8
24
|
__all__ = [
|
|
25
|
+
"analyze_run_directory",
|
|
26
|
+
"analyze_study_directory",
|
|
9
27
|
"build_booth_demo",
|
|
10
28
|
"build_carelink_workbench",
|
|
11
29
|
"ClinicalMetricsCalculator",
|
|
12
30
|
"ClinicalMetricsResult",
|
|
13
31
|
"ClinicalReportGenerator",
|
|
14
32
|
"compute_metrics",
|
|
33
|
+
"compare_studies",
|
|
15
34
|
"generate_results_poster",
|
|
35
|
+
"generate_study_poster",
|
|
36
|
+
"build_study_protocol_payload",
|
|
37
|
+
"render_study_protocol_markdown",
|
|
38
|
+
"write_study_protocol_bundle",
|
|
39
|
+
"load_study_summary",
|
|
40
|
+
"quality_badges_for_metrics",
|
|
16
41
|
"run_baseline_comparison",
|
|
42
|
+
"StudyComparison",
|
|
43
|
+
"StudyRunSummary",
|
|
44
|
+
"StudySummary",
|
|
17
45
|
"write_baseline_comparison",
|
|
18
46
|
]
|
iints/analysis/booth_demo.py
CHANGED
|
@@ -196,6 +196,7 @@ def _build_jury_brief(
|
|
|
196
196
|
"```bash",
|
|
197
197
|
"iints ai local-check --model ministral-3:3b",
|
|
198
198
|
f"iints ai report {run_outputs['03_supervisor_override']['output_dir']} --model ministral-3:3b",
|
|
199
|
+
f"iints ai review {run_outputs['03_supervisor_override']['output_dir']} --model ministral-3:3b",
|
|
199
200
|
f"iints ai explain {run_outputs['03_supervisor_override']['output_dir']} --model ministral-3:3b",
|
|
200
201
|
"```",
|
|
201
202
|
"",
|
|
@@ -247,6 +248,7 @@ def _build_commands_markdown(
|
|
|
247
248
|
"```bash\n"
|
|
248
249
|
"iints ai local-check --model ministral-3:3b\n"
|
|
249
250
|
f"iints ai report {supervisor_dir} --model ministral-3:3b\n"
|
|
251
|
+
f"iints ai review {supervisor_dir} --model ministral-3:3b\n"
|
|
250
252
|
f"iints ai explain {supervisor_dir} --model ministral-3:3b\n"
|
|
251
253
|
"```\n"
|
|
252
254
|
)
|
|
@@ -296,6 +298,7 @@ def _build_live_demo_script_text(
|
|
|
296
298
|
"- If Ollama is ready, run:\n"
|
|
297
299
|
" iints ai local-check --model ministral-3:3b\n"
|
|
298
300
|
f" iints ai report {run_outputs['03_supervisor_override']['output_dir']} --model ministral-3:3b\n"
|
|
301
|
+
f" iints ai review {run_outputs['03_supervisor_override']['output_dir']} --model ministral-3:3b\n"
|
|
299
302
|
f" iints ai explain {run_outputs['03_supervisor_override']['output_dir']} --model ministral-3:3b\n"
|
|
300
303
|
"- Say: the local model explains the result, but only after the SDK has prepared the run artifacts.\n\n"
|
|
301
304
|
"7. IF THE JURY ASKS WHY THIS MATTERS\n"
|
|
@@ -202,6 +202,23 @@ def _prepare_ai_payloads(
|
|
|
202
202
|
"profile_24h": profile_records,
|
|
203
203
|
"trace_sample": trace_sample,
|
|
204
204
|
},
|
|
205
|
+
"review_payload.json": {
|
|
206
|
+
**common,
|
|
207
|
+
"daily_summary": daily_records,
|
|
208
|
+
"profile_24h": profile_records,
|
|
209
|
+
"trace_sample": trace_sample,
|
|
210
|
+
"top_alerts": alert_counts,
|
|
211
|
+
"top_sensor_exceptions": sensor_exception_counts,
|
|
212
|
+
"review_focus": {
|
|
213
|
+
"goal": "Judge whether the imported glucose history looks internally coherent and physiologically plausible.",
|
|
214
|
+
"checks": [
|
|
215
|
+
"time in range versus extreme exposure",
|
|
216
|
+
"daily variability and day-to-day stability",
|
|
217
|
+
"consistency between glucose, carbs, and insulin logs",
|
|
218
|
+
"frequency of alerts and sensor exceptions",
|
|
219
|
+
],
|
|
220
|
+
},
|
|
221
|
+
},
|
|
205
222
|
"anomalies_payload.json": {
|
|
206
223
|
**common,
|
|
207
224
|
"lowest_readings": [
|