iints-sdk-python35 1.1.1__py3-none-any.whl → 1.1.2__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/__init__.py +2 -0
- iints/ai/cli.py +141 -20
- iints/ai/prepare.py +342 -0
- iints/cli/cli.py +29 -0
- {iints_sdk_python35-1.1.1.dist-info → iints_sdk_python35-1.1.2.dist-info}/METADATA +25 -6
- {iints_sdk_python35-1.1.1.dist-info → iints_sdk_python35-1.1.2.dist-info}/RECORD +11 -10
- {iints_sdk_python35-1.1.1.dist-info → iints_sdk_python35-1.1.2.dist-info}/entry_points.txt +1 -0
- {iints_sdk_python35-1.1.1.dist-info → iints_sdk_python35-1.1.2.dist-info}/WHEEL +0 -0
- {iints_sdk_python35-1.1.1.dist-info → iints_sdk_python35-1.1.2.dist-info}/licenses/LICENSE +0 -0
- {iints_sdk_python35-1.1.1.dist-info → iints_sdk_python35-1.1.2.dist-info}/top_level.txt +0 -0
iints/__init__.py
CHANGED
|
@@ -3,7 +3,15 @@
|
|
|
3
3
|
import pandas as pd # Required for type hints like pd.DataFrame
|
|
4
4
|
from typing import Optional
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
try:
|
|
7
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
8
|
+
except ImportError: # pragma: no cover - Python < 3.8 fallback
|
|
9
|
+
from importlib_metadata import PackageNotFoundError, version # type: ignore
|
|
10
|
+
|
|
11
|
+
try:
|
|
12
|
+
__version__ = version("iints-sdk-python35")
|
|
13
|
+
except PackageNotFoundError: # pragma: no cover - source tree fallback
|
|
14
|
+
__version__ = "1.1.2"
|
|
7
15
|
|
|
8
16
|
# Note to developers: this SDK is currently maintained by a single author.
|
|
9
17
|
# Please report bugs via GitHub issues and feel free to contribute fixes via PRs.
|
iints/ai/__init__.py
CHANGED
|
@@ -2,6 +2,7 @@ from .assistant import AIResponse, IINTSAssistant
|
|
|
2
2
|
from .backends import DEFAULT_MINISTRAL_MODEL, DEFAULT_OLLAMA_HOST, OllamaBackend
|
|
3
3
|
from .mdmp_guard import GuardResult, MDMPGuard
|
|
4
4
|
from .model_catalog import LocalMistralModelProfile, list_local_mistral_models
|
|
5
|
+
from .prepare import prepare_ai_ready_artifacts
|
|
5
6
|
|
|
6
7
|
__all__ = [
|
|
7
8
|
"AIResponse",
|
|
@@ -13,4 +14,5 @@ __all__ = [
|
|
|
13
14
|
"MDMPGuard",
|
|
14
15
|
"LocalMistralModelProfile",
|
|
15
16
|
"list_local_mistral_models",
|
|
17
|
+
"prepare_ai_ready_artifacts",
|
|
16
18
|
]
|
iints/ai/cli.py
CHANGED
|
@@ -13,6 +13,7 @@ from typing_extensions import Annotated
|
|
|
13
13
|
from .assistant import AIResponse, IINTSAssistant
|
|
14
14
|
from .backends import DEFAULT_MINISTRAL_MODEL, OllamaBackend
|
|
15
15
|
from .model_catalog import list_local_mistral_models
|
|
16
|
+
from .prepare import prepare_ai_ready_artifacts
|
|
16
17
|
|
|
17
18
|
|
|
18
19
|
app = typer.Typer(help="Research-only AI assistant commands gated by MDMP certification.")
|
|
@@ -36,6 +37,57 @@ def _load_json_payload(path: Path, label: str) -> Any:
|
|
|
36
37
|
return payload
|
|
37
38
|
|
|
38
39
|
|
|
40
|
+
def _default_prepared_payload(task: str, ai_dir: Path) -> Path:
|
|
41
|
+
candidates = {
|
|
42
|
+
"explain": ["step_riskiest.json", "step_latest.json"],
|
|
43
|
+
"trends": ["trends_payload.json"],
|
|
44
|
+
"anomalies": ["anomalies_payload.json"],
|
|
45
|
+
"report": ["report_payload.json"],
|
|
46
|
+
}.get(task, [])
|
|
47
|
+
for filename in candidates:
|
|
48
|
+
candidate = ai_dir / filename
|
|
49
|
+
if candidate.is_file():
|
|
50
|
+
return candidate
|
|
51
|
+
expected = ", ".join(candidates) if candidates else "prepared payload"
|
|
52
|
+
raise typer.BadParameter(
|
|
53
|
+
f"No prepared AI payload found in {ai_dir}. Expected one of: {expected}. "
|
|
54
|
+
"Run `iints ai prepare <run_dir>` first."
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _resolve_cli_inputs(
|
|
59
|
+
*,
|
|
60
|
+
task: str,
|
|
61
|
+
input_path: Path,
|
|
62
|
+
mdmp_cert: Path | None,
|
|
63
|
+
public_key: Path | None,
|
|
64
|
+
trust_store: Path | None,
|
|
65
|
+
) -> tuple[Path, Path, Path | None]:
|
|
66
|
+
resolved_input = input_path
|
|
67
|
+
resolved_cert = mdmp_cert
|
|
68
|
+
resolved_public_key = public_key
|
|
69
|
+
|
|
70
|
+
if input_path.is_dir():
|
|
71
|
+
ai_dir = input_path / "ai"
|
|
72
|
+
resolved_input = _default_prepared_payload(task, ai_dir)
|
|
73
|
+
if resolved_cert is None:
|
|
74
|
+
candidate_cert = ai_dir / "report.signed.mdmp"
|
|
75
|
+
if candidate_cert.is_file():
|
|
76
|
+
resolved_cert = candidate_cert
|
|
77
|
+
if resolved_public_key is None and trust_store is None:
|
|
78
|
+
candidate_public_key = ai_dir / "keys" / "mdmp_pub_v1.pem"
|
|
79
|
+
if candidate_public_key.is_file():
|
|
80
|
+
resolved_public_key = candidate_public_key
|
|
81
|
+
|
|
82
|
+
if resolved_cert is None:
|
|
83
|
+
raise typer.BadParameter(
|
|
84
|
+
"No MDMP certificate provided. Pass --mdmp-cert or run "
|
|
85
|
+
"`iints ai prepare <run_dir>` to generate a local development certificate."
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
return resolved_input, resolved_cert, resolved_public_key
|
|
89
|
+
|
|
90
|
+
|
|
39
91
|
def _write_output(path: Path | None, response: AIResponse) -> None:
|
|
40
92
|
if path is None:
|
|
41
93
|
return
|
|
@@ -117,6 +169,47 @@ def models() -> None:
|
|
|
117
169
|
)
|
|
118
170
|
|
|
119
171
|
|
|
172
|
+
@app.command("prepare")
|
|
173
|
+
def prepare(
|
|
174
|
+
run_dir: Annotated[Path, typer.Argument(help="Run output directory containing results.csv and run_metadata.json.")],
|
|
175
|
+
create_dev_mdmp_cert: Annotated[
|
|
176
|
+
bool,
|
|
177
|
+
typer.Option(
|
|
178
|
+
"--create-dev-mdmp-cert/--no-create-dev-mdmp-cert",
|
|
179
|
+
help="Generate a local development MDMP certificate and keypair for AI commands.",
|
|
180
|
+
),
|
|
181
|
+
] = True,
|
|
182
|
+
grade: Annotated[str, typer.Option(help="Grade to embed in the local development MDMP certificate.")] = "research_grade",
|
|
183
|
+
expires_days: Annotated[int, typer.Option(help="Certificate expiry window in days for local development certs.")] = 30,
|
|
184
|
+
key_dir: Annotated[Optional[Path], typer.Option(help="Optional directory to store the generated local MDMP keypair.")] = None,
|
|
185
|
+
) -> None:
|
|
186
|
+
console = Console()
|
|
187
|
+
try:
|
|
188
|
+
outputs = prepare_ai_ready_artifacts(
|
|
189
|
+
run_dir,
|
|
190
|
+
create_dev_mdmp_cert=create_dev_mdmp_cert,
|
|
191
|
+
grade=grade,
|
|
192
|
+
expires_days=expires_days,
|
|
193
|
+
key_dir=key_dir,
|
|
194
|
+
)
|
|
195
|
+
except Exception as exc:
|
|
196
|
+
console.print(f"[bold red]Error:[/bold red] {exc}")
|
|
197
|
+
raise typer.Exit(code=1)
|
|
198
|
+
|
|
199
|
+
table = Table(title="IINTS AI Prepared Artifacts")
|
|
200
|
+
table.add_column("Artifact", style="cyan")
|
|
201
|
+
table.add_column("Path", overflow="fold")
|
|
202
|
+
for key, value in outputs.items():
|
|
203
|
+
table.add_row(key, value)
|
|
204
|
+
console.print(table)
|
|
205
|
+
console.print("[green]Prepared AI payloads are ready.[/green]")
|
|
206
|
+
if "mdmp_cert" in outputs:
|
|
207
|
+
console.print(
|
|
208
|
+
"[green]You can now run:[/green] "
|
|
209
|
+
f"`iints ai report {run_dir}` or `iints ai explain {run_dir}`"
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
|
|
120
213
|
def _build_assistant(
|
|
121
214
|
*,
|
|
122
215
|
mdmp_cert: Path,
|
|
@@ -169,8 +262,8 @@ def local_check(
|
|
|
169
262
|
|
|
170
263
|
@app.command("explain")
|
|
171
264
|
def explain(
|
|
172
|
-
input_json: Annotated[Path, typer.Argument(help="JSON file with a single simulation step or decision context.")],
|
|
173
|
-
mdmp_cert: Annotated[Path, typer.Option(help="Signed MDMP artifact required before AI analysis can run.")],
|
|
265
|
+
input_json: Annotated[Path, typer.Argument(help="Prepared run directory or JSON file with a single simulation step or decision context.")],
|
|
266
|
+
mdmp_cert: Annotated[Optional[Path], typer.Option(help="Signed MDMP artifact required before AI analysis can run.")] = None,
|
|
174
267
|
mode: Annotated[str, typer.Option(help="AI backend mode. Use 'local' for Ollama/Ministral.")] = "auto",
|
|
175
268
|
model: Annotated[str, typer.Option(help="Ollama model name to use.")] = DEFAULT_MINISTRAL_MODEL,
|
|
176
269
|
minimum_grade: Annotated[str, typer.Option(help="Minimum MDMP grade required to allow analysis.")] = "research_grade",
|
|
@@ -182,13 +275,20 @@ def explain(
|
|
|
182
275
|
) -> None:
|
|
183
276
|
console = Console()
|
|
184
277
|
try:
|
|
185
|
-
|
|
186
|
-
|
|
278
|
+
resolved_input, resolved_cert, resolved_public_key = _resolve_cli_inputs(
|
|
279
|
+
task="explain",
|
|
280
|
+
input_path=input_json,
|
|
187
281
|
mdmp_cert=mdmp_cert,
|
|
282
|
+
public_key=public_key,
|
|
283
|
+
trust_store=trust_store,
|
|
284
|
+
)
|
|
285
|
+
payload = _load_json_payload(resolved_input, "Input JSON")
|
|
286
|
+
assistant = _build_assistant(
|
|
287
|
+
mdmp_cert=resolved_cert,
|
|
188
288
|
mode=mode,
|
|
189
289
|
model=model,
|
|
190
290
|
minimum_grade=minimum_grade,
|
|
191
|
-
public_key=
|
|
291
|
+
public_key=resolved_public_key,
|
|
192
292
|
trust_store=trust_store,
|
|
193
293
|
ollama_host=ollama_host,
|
|
194
294
|
timeout_seconds=timeout_seconds,
|
|
@@ -203,8 +303,8 @@ def explain(
|
|
|
203
303
|
|
|
204
304
|
@app.command("trends")
|
|
205
305
|
def trends(
|
|
206
|
-
input_json: Annotated[Path, typer.Argument(help="JSON file with glucose trace data or a run payload.")],
|
|
207
|
-
mdmp_cert: Annotated[Path, typer.Option(help="Signed MDMP artifact required before AI analysis can run.")],
|
|
306
|
+
input_json: Annotated[Path, typer.Argument(help="Prepared run directory or JSON file with glucose trace data or a run payload.")],
|
|
307
|
+
mdmp_cert: Annotated[Optional[Path], typer.Option(help="Signed MDMP artifact required before AI analysis can run.")] = None,
|
|
208
308
|
mode: Annotated[str, typer.Option(help="AI backend mode. Use 'local' for Ollama/Ministral.")] = "auto",
|
|
209
309
|
model: Annotated[str, typer.Option(help="Ollama model name to use.")] = DEFAULT_MINISTRAL_MODEL,
|
|
210
310
|
minimum_grade: Annotated[str, typer.Option(help="Minimum MDMP grade required to allow analysis.")] = "research_grade",
|
|
@@ -216,13 +316,20 @@ def trends(
|
|
|
216
316
|
) -> None:
|
|
217
317
|
console = Console()
|
|
218
318
|
try:
|
|
219
|
-
|
|
220
|
-
|
|
319
|
+
resolved_input, resolved_cert, resolved_public_key = _resolve_cli_inputs(
|
|
320
|
+
task="trends",
|
|
321
|
+
input_path=input_json,
|
|
221
322
|
mdmp_cert=mdmp_cert,
|
|
323
|
+
public_key=public_key,
|
|
324
|
+
trust_store=trust_store,
|
|
325
|
+
)
|
|
326
|
+
payload = _load_json_payload(resolved_input, "Input JSON")
|
|
327
|
+
assistant = _build_assistant(
|
|
328
|
+
mdmp_cert=resolved_cert,
|
|
222
329
|
mode=mode,
|
|
223
330
|
model=model,
|
|
224
331
|
minimum_grade=minimum_grade,
|
|
225
|
-
public_key=
|
|
332
|
+
public_key=resolved_public_key,
|
|
226
333
|
trust_store=trust_store,
|
|
227
334
|
ollama_host=ollama_host,
|
|
228
335
|
timeout_seconds=timeout_seconds,
|
|
@@ -237,8 +344,8 @@ def trends(
|
|
|
237
344
|
|
|
238
345
|
@app.command("anomalies")
|
|
239
346
|
def anomalies(
|
|
240
|
-
input_json: Annotated[Path, typer.Argument(help="JSON file with simulation results or run summary.")],
|
|
241
|
-
mdmp_cert: Annotated[Path, typer.Option(help="Signed MDMP artifact required before AI analysis can run.")],
|
|
347
|
+
input_json: Annotated[Path, typer.Argument(help="Prepared run directory or JSON file with simulation results or run summary.")],
|
|
348
|
+
mdmp_cert: Annotated[Optional[Path], typer.Option(help="Signed MDMP artifact required before AI analysis can run.")] = None,
|
|
242
349
|
mode: Annotated[str, typer.Option(help="AI backend mode. Use 'local' for Ollama/Ministral.")] = "auto",
|
|
243
350
|
model: Annotated[str, typer.Option(help="Ollama model name to use.")] = DEFAULT_MINISTRAL_MODEL,
|
|
244
351
|
minimum_grade: Annotated[str, typer.Option(help="Minimum MDMP grade required to allow analysis.")] = "research_grade",
|
|
@@ -250,13 +357,20 @@ def anomalies(
|
|
|
250
357
|
) -> None:
|
|
251
358
|
console = Console()
|
|
252
359
|
try:
|
|
253
|
-
|
|
254
|
-
|
|
360
|
+
resolved_input, resolved_cert, resolved_public_key = _resolve_cli_inputs(
|
|
361
|
+
task="anomalies",
|
|
362
|
+
input_path=input_json,
|
|
255
363
|
mdmp_cert=mdmp_cert,
|
|
364
|
+
public_key=public_key,
|
|
365
|
+
trust_store=trust_store,
|
|
366
|
+
)
|
|
367
|
+
payload = _load_json_payload(resolved_input, "Input JSON")
|
|
368
|
+
assistant = _build_assistant(
|
|
369
|
+
mdmp_cert=resolved_cert,
|
|
256
370
|
mode=mode,
|
|
257
371
|
model=model,
|
|
258
372
|
minimum_grade=minimum_grade,
|
|
259
|
-
public_key=
|
|
373
|
+
public_key=resolved_public_key,
|
|
260
374
|
trust_store=trust_store,
|
|
261
375
|
ollama_host=ollama_host,
|
|
262
376
|
timeout_seconds=timeout_seconds,
|
|
@@ -271,8 +385,8 @@ def anomalies(
|
|
|
271
385
|
|
|
272
386
|
@app.command("report")
|
|
273
387
|
def report(
|
|
274
|
-
input_json: Annotated[Path, typer.Argument(help="JSON file with run-level simulation outputs.")],
|
|
275
|
-
mdmp_cert: Annotated[Path, typer.Option(help="Signed MDMP artifact required before AI analysis can run.")],
|
|
388
|
+
input_json: Annotated[Path, typer.Argument(help="Prepared run directory or JSON file with run-level simulation outputs.")],
|
|
389
|
+
mdmp_cert: Annotated[Optional[Path], typer.Option(help="Signed MDMP artifact required before AI analysis can run.")] = None,
|
|
276
390
|
mode: Annotated[str, typer.Option(help="AI backend mode. Use 'local' for Ollama/Ministral.")] = "auto",
|
|
277
391
|
model: Annotated[str, typer.Option(help="Ollama model name to use.")] = DEFAULT_MINISTRAL_MODEL,
|
|
278
392
|
minimum_grade: Annotated[str, typer.Option(help="Minimum MDMP grade required to allow analysis.")] = "research_grade",
|
|
@@ -284,13 +398,20 @@ def report(
|
|
|
284
398
|
) -> None:
|
|
285
399
|
console = Console()
|
|
286
400
|
try:
|
|
287
|
-
|
|
288
|
-
|
|
401
|
+
resolved_input, resolved_cert, resolved_public_key = _resolve_cli_inputs(
|
|
402
|
+
task="report",
|
|
403
|
+
input_path=input_json,
|
|
289
404
|
mdmp_cert=mdmp_cert,
|
|
405
|
+
public_key=public_key,
|
|
406
|
+
trust_store=trust_store,
|
|
407
|
+
)
|
|
408
|
+
payload = _load_json_payload(resolved_input, "Input JSON")
|
|
409
|
+
assistant = _build_assistant(
|
|
410
|
+
mdmp_cert=resolved_cert,
|
|
290
411
|
mode=mode,
|
|
291
412
|
model=model,
|
|
292
413
|
minimum_grade=minimum_grade,
|
|
293
|
-
public_key=
|
|
414
|
+
public_key=resolved_public_key,
|
|
294
415
|
trust_store=trust_store,
|
|
295
416
|
ollama_host=ollama_host,
|
|
296
417
|
timeout_seconds=timeout_seconds,
|
iints/ai/prepare.py
ADDED
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from datetime import datetime, timezone
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any, Optional
|
|
6
|
+
import importlib
|
|
7
|
+
import json
|
|
8
|
+
import math
|
|
9
|
+
|
|
10
|
+
import pandas as pd
|
|
11
|
+
|
|
12
|
+
from iints.utils.run_io import compute_sha256
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _now_utc() -> str:
|
|
16
|
+
return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _read_json(path: Path) -> dict[str, Any]:
|
|
20
|
+
payload = json.loads(path.read_text(encoding="utf-8"))
|
|
21
|
+
if not isinstance(payload, dict):
|
|
22
|
+
raise ValueError(f"Expected JSON object in {path}")
|
|
23
|
+
return payload
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _normalize_value(value: Any) -> Any:
|
|
27
|
+
if value is None:
|
|
28
|
+
return None
|
|
29
|
+
if isinstance(value, (str, bool, int)):
|
|
30
|
+
return value
|
|
31
|
+
if isinstance(value, float):
|
|
32
|
+
if math.isnan(value) or math.isinf(value):
|
|
33
|
+
return None
|
|
34
|
+
return round(value, 4)
|
|
35
|
+
if hasattr(value, "item"):
|
|
36
|
+
return _normalize_value(value.item())
|
|
37
|
+
return value
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _normalize_record(record: dict[str, Any]) -> dict[str, Any]:
|
|
41
|
+
return {key: _normalize_value(value) for key, value in record.items()}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _normalize_series_record(record: Any) -> dict[str, Any]:
|
|
45
|
+
if not isinstance(record, dict):
|
|
46
|
+
return {}
|
|
47
|
+
return {str(key): _normalize_value(value) for key, value in record.items()}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _glucose_column(df: pd.DataFrame) -> str:
|
|
51
|
+
for candidate in ("glucose_actual_mgdl", "glucose_to_algo_mgdl", "cgm"):
|
|
52
|
+
if candidate in df.columns:
|
|
53
|
+
return candidate
|
|
54
|
+
raise ValueError("Results CSV does not contain a supported glucose column.")
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _bool_sum(df: pd.DataFrame, column: str) -> int:
|
|
58
|
+
if column not in df.columns:
|
|
59
|
+
return 0
|
|
60
|
+
return int(df[column].fillna(False).astype(bool).sum())
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _safe_sum(df: pd.DataFrame, column: str) -> float:
|
|
64
|
+
if column not in df.columns:
|
|
65
|
+
return 0.0
|
|
66
|
+
return float(pd.to_numeric(df[column], errors="coerce").fillna(0.0).sum())
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _time_in_band_pct(series: pd.Series, low: float, high: float) -> float:
|
|
70
|
+
clean = pd.to_numeric(series, errors="coerce").dropna()
|
|
71
|
+
if clean.empty:
|
|
72
|
+
return 0.0
|
|
73
|
+
mask = (clean >= low) & (clean <= high)
|
|
74
|
+
return float(mask.mean() * 100.0)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _sample_trace(df: pd.DataFrame, *, max_rows: int = 48) -> list[dict[str, Any]]:
|
|
78
|
+
interesting_columns = [
|
|
79
|
+
"time_minutes",
|
|
80
|
+
"glucose_actual_mgdl",
|
|
81
|
+
"glucose_to_algo_mgdl",
|
|
82
|
+
"glucose_trend_mgdl_min",
|
|
83
|
+
"predicted_glucose_30min",
|
|
84
|
+
"algo_recommended_insulin_units",
|
|
85
|
+
"delivered_insulin_units",
|
|
86
|
+
"safety_triggered",
|
|
87
|
+
"safety_reason",
|
|
88
|
+
]
|
|
89
|
+
present = [column for column in interesting_columns if column in df.columns]
|
|
90
|
+
if not present:
|
|
91
|
+
return []
|
|
92
|
+
if len(df) <= max_rows:
|
|
93
|
+
sampled = df[present]
|
|
94
|
+
else:
|
|
95
|
+
step = max(1, len(df) // max_rows)
|
|
96
|
+
sampled = df.iloc[::step][present].head(max_rows)
|
|
97
|
+
return [_normalize_series_record(record) for record in sampled.to_dict(orient="records")]
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _position_for_label(df: pd.DataFrame, label: Any) -> int:
|
|
101
|
+
location = df.index.get_loc(label)
|
|
102
|
+
if isinstance(location, int):
|
|
103
|
+
return location
|
|
104
|
+
raise ValueError(f"Could not resolve row position for label: {label!r}")
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _select_step_payload(df: pd.DataFrame) -> tuple[dict[str, Any], dict[str, Any]]:
|
|
108
|
+
if df.empty:
|
|
109
|
+
raise ValueError("Results CSV is empty.")
|
|
110
|
+
|
|
111
|
+
glucose_column = _glucose_column(df)
|
|
112
|
+
risk_position = len(df.index) - 1
|
|
113
|
+
selection_reason = "latest_step"
|
|
114
|
+
|
|
115
|
+
if "safety_triggered" in df.columns and df["safety_triggered"].fillna(False).astype(bool).any():
|
|
116
|
+
safety_rows = df[df["safety_triggered"].fillna(False).astype(bool)]
|
|
117
|
+
risk_position = _position_for_label(df, safety_rows.index[0])
|
|
118
|
+
selection_reason = "first_safety_trigger"
|
|
119
|
+
else:
|
|
120
|
+
glucose_values = pd.to_numeric(df[glucose_column], errors="coerce")
|
|
121
|
+
if (glucose_values < 70).any():
|
|
122
|
+
risk_label = glucose_values.idxmin()
|
|
123
|
+
risk_position = _position_for_label(df, risk_label)
|
|
124
|
+
selection_reason = "lowest_glucose"
|
|
125
|
+
elif "predicted_glucose_30min" in df.columns:
|
|
126
|
+
predicted = pd.to_numeric(df["predicted_glucose_30min"], errors="coerce")
|
|
127
|
+
if (predicted > 180).any():
|
|
128
|
+
risk_label = predicted.idxmax()
|
|
129
|
+
risk_position = _position_for_label(df, risk_label)
|
|
130
|
+
selection_reason = "highest_predicted_glucose_30min"
|
|
131
|
+
latest_position = len(df.index) - 1
|
|
132
|
+
|
|
133
|
+
def _row_at(position: int) -> Optional[dict[str, Any]]:
|
|
134
|
+
if position < 0 or position >= len(df.index):
|
|
135
|
+
return None
|
|
136
|
+
return _normalize_series_record(df.iloc[position].to_dict())
|
|
137
|
+
|
|
138
|
+
risk_payload = {
|
|
139
|
+
"selection_reason": selection_reason,
|
|
140
|
+
"selected_step": _row_at(risk_position),
|
|
141
|
+
"previous_step": _row_at(risk_position - 1),
|
|
142
|
+
"next_step": _row_at(risk_position + 1),
|
|
143
|
+
}
|
|
144
|
+
latest_payload = {
|
|
145
|
+
"selection_reason": "latest_step",
|
|
146
|
+
"selected_step": _row_at(latest_position),
|
|
147
|
+
"previous_step": _row_at(latest_position - 1),
|
|
148
|
+
"next_step": None,
|
|
149
|
+
}
|
|
150
|
+
return risk_payload, latest_payload
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _build_summary(df: pd.DataFrame, run_metadata: dict[str, Any], audit_summary: dict[str, Any]) -> dict[str, Any]:
|
|
154
|
+
glucose_column = _glucose_column(df)
|
|
155
|
+
glucose = pd.to_numeric(df[glucose_column], errors="coerce").dropna()
|
|
156
|
+
if glucose.empty:
|
|
157
|
+
raise ValueError("Results CSV glucose series is empty.")
|
|
158
|
+
|
|
159
|
+
duration_minutes = run_metadata.get("config", {}).get("duration_minutes")
|
|
160
|
+
if duration_minutes is None and "time_minutes" in df.columns:
|
|
161
|
+
duration_minutes = _normalize_value(pd.to_numeric(df["time_minutes"], errors="coerce").max())
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
"steps": int(len(df)),
|
|
165
|
+
"duration_minutes": _normalize_value(duration_minutes),
|
|
166
|
+
"mean_glucose_mgdl": _normalize_value(float(glucose.mean())),
|
|
167
|
+
"min_glucose_mgdl": _normalize_value(float(glucose.min())),
|
|
168
|
+
"max_glucose_mgdl": _normalize_value(float(glucose.max())),
|
|
169
|
+
"time_in_range_70_180_pct": _normalize_value(_time_in_band_pct(glucose, 70.0, 180.0)),
|
|
170
|
+
"time_below_70_pct": _normalize_value(float((glucose < 70.0).mean() * 100.0)),
|
|
171
|
+
"time_above_180_pct": _normalize_value(float((glucose > 180.0).mean() * 100.0)),
|
|
172
|
+
"delivered_insulin_total_units": _normalize_value(_safe_sum(df, "delivered_insulin_units")),
|
|
173
|
+
"recommended_insulin_total_units": _normalize_value(_safe_sum(df, "algo_recommended_insulin_units")),
|
|
174
|
+
"safety_trigger_count": _bool_sum(df, "safety_triggered"),
|
|
175
|
+
"audit_override_count": int(audit_summary.get("total_overrides", 0)),
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def _build_payloads(
|
|
180
|
+
*,
|
|
181
|
+
run_dir: Path,
|
|
182
|
+
results_df: pd.DataFrame,
|
|
183
|
+
run_metadata: dict[str, Any],
|
|
184
|
+
run_manifest: dict[str, Any],
|
|
185
|
+
audit_summary: dict[str, Any],
|
|
186
|
+
baseline_comparison: dict[str, Any] | None,
|
|
187
|
+
) -> dict[str, dict[str, Any]]:
|
|
188
|
+
summary = _build_summary(results_df, run_metadata, audit_summary)
|
|
189
|
+
risk_payload, latest_payload = _select_step_payload(results_df)
|
|
190
|
+
trace_sample = _sample_trace(results_df)
|
|
191
|
+
|
|
192
|
+
common = {
|
|
193
|
+
"generated_at_utc": _now_utc(),
|
|
194
|
+
"run_dir": str(run_dir),
|
|
195
|
+
"run_id": run_metadata.get("run_id"),
|
|
196
|
+
"sdk_version": run_metadata.get("sdk_version"),
|
|
197
|
+
"algorithm": run_metadata.get("config", {}).get("algorithm", {}),
|
|
198
|
+
"scenario": run_metadata.get("config", {}).get("scenario"),
|
|
199
|
+
"summary": summary,
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
payloads: dict[str, dict[str, Any]] = {
|
|
203
|
+
"report_payload.json": {
|
|
204
|
+
**common,
|
|
205
|
+
"artifacts": {
|
|
206
|
+
"run_metadata": str(run_dir / "run_metadata.json"),
|
|
207
|
+
"run_manifest": str(run_dir / "run_manifest.json"),
|
|
208
|
+
"results_csv": str(run_dir / "results.csv"),
|
|
209
|
+
"audit_summary": str(run_dir / "audit" / "audit_summary.json"),
|
|
210
|
+
"baseline_comparison": str(run_dir / "baseline" / "baseline_comparison.json"),
|
|
211
|
+
},
|
|
212
|
+
"audit_summary": audit_summary,
|
|
213
|
+
"baseline_comparison": baseline_comparison,
|
|
214
|
+
"trace_sample": trace_sample,
|
|
215
|
+
"run_manifest": run_manifest,
|
|
216
|
+
},
|
|
217
|
+
"anomalies_payload.json": {
|
|
218
|
+
**common,
|
|
219
|
+
"audit_summary": audit_summary,
|
|
220
|
+
"safety_events": [
|
|
221
|
+
record
|
|
222
|
+
for record in trace_sample
|
|
223
|
+
if bool(record.get("safety_triggered"))
|
|
224
|
+
],
|
|
225
|
+
},
|
|
226
|
+
"trends_payload.json": {
|
|
227
|
+
**common,
|
|
228
|
+
"trace_sample": trace_sample,
|
|
229
|
+
"baseline_comparison": baseline_comparison,
|
|
230
|
+
},
|
|
231
|
+
"step_riskiest.json": {
|
|
232
|
+
**common,
|
|
233
|
+
**risk_payload,
|
|
234
|
+
},
|
|
235
|
+
"step_latest.json": {
|
|
236
|
+
**common,
|
|
237
|
+
**latest_payload,
|
|
238
|
+
},
|
|
239
|
+
}
|
|
240
|
+
return payloads
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def _load_mdmp_signer_tools() -> tuple[type[Any], Any]:
|
|
244
|
+
try:
|
|
245
|
+
module = importlib.import_module("mdmp_core")
|
|
246
|
+
except Exception as exc:
|
|
247
|
+
raise ImportError(
|
|
248
|
+
"Local AI certification requires the optional standalone MDMP package.\n"
|
|
249
|
+
"Install with: pip install 'iints-sdk-python35[mdmp]'"
|
|
250
|
+
) from exc
|
|
251
|
+
|
|
252
|
+
signer_cls = getattr(module, "MDMPSigner", None)
|
|
253
|
+
keygen_fn = getattr(module, "generate_keypair", None)
|
|
254
|
+
if signer_cls is None or keygen_fn is None:
|
|
255
|
+
raise ImportError("mdmp_core is installed but does not expose MDMPSigner/generate_keypair.")
|
|
256
|
+
return signer_cls, keygen_fn
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def _write_json(path: Path, payload: dict[str, Any]) -> None:
|
|
260
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
261
|
+
path.write_text(json.dumps(payload, indent=2, sort_keys=True), encoding="utf-8")
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def prepare_ai_ready_artifacts(
|
|
265
|
+
run_dir: str | Path,
|
|
266
|
+
*,
|
|
267
|
+
create_dev_mdmp_cert: bool = True,
|
|
268
|
+
grade: str = "research_grade",
|
|
269
|
+
expires_days: int = 30,
|
|
270
|
+
key_dir: str | Path | None = None,
|
|
271
|
+
) -> dict[str, str]:
|
|
272
|
+
bundle_dir = Path(run_dir).expanduser().resolve()
|
|
273
|
+
if not bundle_dir.is_dir():
|
|
274
|
+
raise FileNotFoundError(f"Run directory not found: {bundle_dir}")
|
|
275
|
+
|
|
276
|
+
results_csv = bundle_dir / "results.csv"
|
|
277
|
+
run_metadata_path = bundle_dir / "run_metadata.json"
|
|
278
|
+
run_manifest_path = bundle_dir / "run_manifest.json"
|
|
279
|
+
|
|
280
|
+
for required in (results_csv, run_metadata_path, run_manifest_path):
|
|
281
|
+
if not required.is_file():
|
|
282
|
+
raise FileNotFoundError(f"Required run artifact missing: {required}")
|
|
283
|
+
|
|
284
|
+
results_df = pd.read_csv(results_csv)
|
|
285
|
+
run_metadata = _read_json(run_metadata_path)
|
|
286
|
+
run_manifest = _read_json(run_manifest_path)
|
|
287
|
+
|
|
288
|
+
audit_summary_path = bundle_dir / "audit" / "audit_summary.json"
|
|
289
|
+
baseline_path = bundle_dir / "baseline" / "baseline_comparison.json"
|
|
290
|
+
audit_summary = _read_json(audit_summary_path) if audit_summary_path.is_file() else {}
|
|
291
|
+
baseline_comparison = _read_json(baseline_path) if baseline_path.is_file() else None
|
|
292
|
+
|
|
293
|
+
ai_dir = bundle_dir / "ai"
|
|
294
|
+
payloads = _build_payloads(
|
|
295
|
+
run_dir=bundle_dir,
|
|
296
|
+
results_df=results_df,
|
|
297
|
+
run_metadata=run_metadata,
|
|
298
|
+
run_manifest=run_manifest,
|
|
299
|
+
audit_summary=audit_summary,
|
|
300
|
+
baseline_comparison=baseline_comparison,
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
written: dict[str, str] = {}
|
|
304
|
+
for filename, payload in payloads.items():
|
|
305
|
+
target = ai_dir / filename
|
|
306
|
+
_write_json(target, payload)
|
|
307
|
+
written[filename.removesuffix(".json")] = str(target)
|
|
308
|
+
|
|
309
|
+
if create_dev_mdmp_cert:
|
|
310
|
+
signer_cls, keygen_fn = _load_mdmp_signer_tools()
|
|
311
|
+
resolved_key_dir = Path(key_dir).expanduser().resolve() if key_dir else ai_dir / "keys"
|
|
312
|
+
private_key_path = resolved_key_dir / "mdmp_private_v1.pem"
|
|
313
|
+
public_key_path = resolved_key_dir / "mdmp_pub_v1.pem"
|
|
314
|
+
if not private_key_path.is_file() or not public_key_path.is_file():
|
|
315
|
+
keygen_fn(output_dir=resolved_key_dir)
|
|
316
|
+
|
|
317
|
+
cert_payload = {
|
|
318
|
+
"mdmp_object": "iints_ai_local_cert",
|
|
319
|
+
"spec_version": "1.0",
|
|
320
|
+
"grade": grade,
|
|
321
|
+
"generated_at_utc": _now_utc(),
|
|
322
|
+
"run_id": run_metadata.get("run_id"),
|
|
323
|
+
"run_dir": str(bundle_dir),
|
|
324
|
+
"sdk_version": run_metadata.get("sdk_version"),
|
|
325
|
+
"purpose": "local_research_ai",
|
|
326
|
+
"results_csv_sha256": f"sha256:{compute_sha256(results_csv)}",
|
|
327
|
+
"run_manifest_sha256": f"sha256:{compute_sha256(run_manifest_path)}",
|
|
328
|
+
"notes": "Local development certificate generated by IINTS AI prepare.",
|
|
329
|
+
}
|
|
330
|
+
signer = signer_cls(
|
|
331
|
+
private_key_path=private_key_path,
|
|
332
|
+
signed_by="IINTS-Local-AI",
|
|
333
|
+
key_id="iints_local_ai_v1",
|
|
334
|
+
)
|
|
335
|
+
signed_cert = signer.sign_card(cert_payload, expires_days=expires_days)
|
|
336
|
+
cert_path = ai_dir / "report.signed.mdmp"
|
|
337
|
+
_write_json(cert_path, signed_cert)
|
|
338
|
+
written["mdmp_cert"] = str(cert_path)
|
|
339
|
+
written["mdmp_public_key"] = str(public_key_path)
|
|
340
|
+
written["mdmp_private_key"] = str(private_key_path)
|
|
341
|
+
|
|
342
|
+
return written
|
iints/cli/cli.py
CHANGED
|
@@ -22,6 +22,7 @@ from rich.table import Table # type: ignore # For comparison table
|
|
|
22
22
|
from rich.panel import Panel # type: ignore # For nicer auto-doc output
|
|
23
23
|
|
|
24
24
|
import iints # Import the top-level SDK package
|
|
25
|
+
from iints.ai import prepare_ai_ready_artifacts
|
|
25
26
|
from iints.ai.cli import app as ai_app
|
|
26
27
|
from iints.analysis.baseline import run_baseline_comparison, write_baseline_comparison
|
|
27
28
|
from iints.api.registry import list_algorithm_plugins
|
|
@@ -278,6 +279,30 @@ def _write_certification_summary(
|
|
|
278
279
|
return summary_path
|
|
279
280
|
|
|
280
281
|
|
|
282
|
+
def _maybe_prepare_ai_artifacts(output_dir: Path, console: Console) -> None:
|
|
283
|
+
try:
|
|
284
|
+
outputs = prepare_ai_ready_artifacts(output_dir, create_dev_mdmp_cert=True)
|
|
285
|
+
console.print(f"[green]AI-ready artifacts:[/green] {output_dir / 'ai'}")
|
|
286
|
+
if "mdmp_cert" in outputs:
|
|
287
|
+
console.print(f"[green]AI quick start:[/green] iints ai report {output_dir}")
|
|
288
|
+
return
|
|
289
|
+
except ImportError as exc:
|
|
290
|
+
console.print(f"[yellow]AI dev certificate skipped:[/yellow] {exc}")
|
|
291
|
+
except Exception as exc:
|
|
292
|
+
console.print(f"[yellow]AI-ready export skipped:[/yellow] {exc}")
|
|
293
|
+
return
|
|
294
|
+
|
|
295
|
+
try:
|
|
296
|
+
prepare_ai_ready_artifacts(output_dir, create_dev_mdmp_cert=False)
|
|
297
|
+
console.print(f"[green]AI-ready payloads:[/green] {output_dir / 'ai'}")
|
|
298
|
+
console.print(
|
|
299
|
+
"[yellow]Tip:[/yellow] Install the MDMP extra or rerun "
|
|
300
|
+
f"`iints ai prepare {output_dir}` to generate a local development certificate."
|
|
301
|
+
)
|
|
302
|
+
except Exception as exc:
|
|
303
|
+
console.print(f"[yellow]AI-ready payload export skipped:[/yellow] {exc}")
|
|
304
|
+
|
|
305
|
+
|
|
281
306
|
def _get_preset(name: str) -> Dict[str, Any]:
|
|
282
307
|
presets = _load_presets()
|
|
283
308
|
for preset in presets:
|
|
@@ -1698,9 +1723,11 @@ def presets_run(
|
|
|
1698
1723
|
run_manifest_path = output_dir / "run_manifest.json"
|
|
1699
1724
|
write_json(run_manifest_path, run_manifest)
|
|
1700
1725
|
console.print(f"Run manifest: {run_manifest_path}")
|
|
1726
|
+
_maybe_prepare_ai_artifacts(output_dir, console)
|
|
1701
1727
|
signature_path = maybe_sign_manifest(run_manifest_path)
|
|
1702
1728
|
if signature_path:
|
|
1703
1729
|
console.print(f"Run manifest signature: {signature_path}")
|
|
1730
|
+
_maybe_prepare_ai_artifacts(output_dir, console)
|
|
1704
1731
|
|
|
1705
1732
|
|
|
1706
1733
|
@presets_app.command("create")
|
|
@@ -2295,6 +2322,8 @@ def run_full(
|
|
|
2295
2322
|
console.print(f"Profiling report: {outputs['profiling_path']}")
|
|
2296
2323
|
if "run_manifest_signature" in outputs:
|
|
2297
2324
|
console.print(f"Run manifest signature: {outputs['run_manifest_signature']}")
|
|
2325
|
+
if "output_dir" in outputs:
|
|
2326
|
+
_maybe_prepare_ai_artifacts(Path(outputs["output_dir"]), console)
|
|
2298
2327
|
|
|
2299
2328
|
|
|
2300
2329
|
@app.command("run-parallel")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: iints-sdk-python35
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.2
|
|
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
|
|
@@ -110,15 +110,21 @@ ollama pull ministral-3:8b
|
|
|
110
110
|
iints ai local-check --model ministral-3:8b
|
|
111
111
|
```
|
|
112
112
|
|
|
113
|
-
|
|
113
|
+
Recommended flow:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
iints quickstart --project-name iints_quickstart
|
|
117
|
+
cd iints_quickstart
|
|
118
|
+
iints presets run --name baseline_t1d --algo algorithms/example_algorithm.py
|
|
119
|
+
iints ai prepare results/<run_id>
|
|
120
|
+
iints ai report results/<run_id>
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Direct JSON mode still works if you already have your own payloads and signed MDMP artifact:
|
|
114
124
|
|
|
115
125
|
```bash
|
|
116
126
|
iints ai explain results/step.json \
|
|
117
127
|
--mdmp-cert results/report.signed.mdmp
|
|
118
|
-
|
|
119
|
-
iints ai report results/simulation_run.json \
|
|
120
|
-
--mdmp-cert results/report.signed.mdmp \
|
|
121
|
-
--output results/ai_report.md
|
|
122
128
|
```
|
|
123
129
|
|
|
124
130
|
Notes:
|
|
@@ -127,8 +133,21 @@ Notes:
|
|
|
127
133
|
- The SDK now targets the open local `Ministral 3` Ollama model by default.
|
|
128
134
|
- Users can choose a larger or smaller local Mistral-family model with `--model ...`.
|
|
129
135
|
- Large JSON payloads are clipped automatically before prompt generation to keep local inference stable.
|
|
136
|
+
- `iints ai prepare <run_dir>` now creates AI-ready JSON payloads and, when MDMP is installed, a local development certificate plus keypair in `<run_dir>/ai/`.
|
|
137
|
+
- After `iints ai prepare`, you can point `iints ai explain|trends|anomalies|report` directly at the run directory.
|
|
130
138
|
- Output is research-only and not medical advice.
|
|
131
139
|
|
|
140
|
+
Troubleshooting:
|
|
141
|
+
- If `iints ai ...` says `No such command 'ai'`, your environment usually still has a legacy `iints` package installed alongside `iints-sdk-python35`.
|
|
142
|
+
- Run `iints-sdk-doctor` first.
|
|
143
|
+
- If it reports a conflict, repair the environment with:
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
python -m pip uninstall -y iints iints-sdk-python35
|
|
147
|
+
python -m pip install -U "iints-sdk-python35[mdmp]==1.1.2"
|
|
148
|
+
hash -r
|
|
149
|
+
```
|
|
150
|
+
|
|
132
151
|
## MDMP (Short)
|
|
133
152
|
MDMP is the data-quality protocol used by IINTS.
|
|
134
153
|
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
iints/__init__.py,sha256=
|
|
1
|
+
iints/__init__.py,sha256=wfAcfS7htgnV4JD-R8_WyKZHOwR8Z98vy-oLYxu0-rE,6391
|
|
2
2
|
iints/highlevel.py,sha256=DX12LRmL6YaYY99P0c_P93xfHe4mZjqyLhTYuS6L6hI,20491
|
|
3
3
|
iints/metrics.py,sha256=O9hqOqJpUhUJDqsbfuqRMS9dkV97gzcgh3Y2jYUqHzg,907
|
|
4
|
-
iints/ai/__init__.py,sha256=
|
|
4
|
+
iints/ai/__init__.py,sha256=nyRDcFfSHI4a3NbTvySipFc3_inqRMEsr6xIEipWuyo,575
|
|
5
5
|
iints/ai/assistant.py,sha256=0Ye1IaWEYg2rZnk3ny8f0GMoYqOWIa7U_GsV-sWrxtU,4346
|
|
6
|
-
iints/ai/cli.py,sha256=
|
|
6
|
+
iints/ai/cli.py,sha256=_1ogEAb36BAt7sZ2CQSRKIJSpdn5xrlM7nNTtBIqfRo,18345
|
|
7
7
|
iints/ai/mdmp_guard.py,sha256=BpFQX0oyP9WMCUZbFhhoBzomNeVKuI1HY1EFH9cG8EE,4249
|
|
8
8
|
iints/ai/model_catalog.py,sha256=gRW-i4eaXkrjX3mIKJlGzHqzU75lpIulEFKQsCX11CI,1804
|
|
9
|
+
iints/ai/prepare.py,sha256=z3y5elCAMv0p_aNq4gQfZA1uIT7_cX3FGRdzmoZoKho,12967
|
|
9
10
|
iints/ai/prompts.py,sha256=pGp9tC1wBZXGG5duxfktaJEF4p_cvmR0zEIxmMTEAyE,2812
|
|
10
11
|
iints/ai/backends/__init__.py,sha256=EAJRZS8G0DK7fffw_LHio9DkyYHwtzvz2Jo7AXk7pk4,303
|
|
11
12
|
iints/ai/backends/base.py,sha256=BLgP03X-jebYkF9D5n5crawoPBmy3RSh4q3jaT8a9XM,274
|
|
@@ -35,7 +36,7 @@ iints/api/registry.py,sha256=h2syJwacFbgrtgnVK20JwlXivvVO31zeJ_Ez4KBkn1g,3240
|
|
|
35
36
|
iints/api/template_algorithm.py,sha256=AFs9AymL3ddWAjgpOkF1Oa3TeOSg56siyDt_BmsAND8,9195
|
|
36
37
|
iints/assets/iints_logo.png,sha256=rWzP8XqIYDrPCTp378w73zA1snKCUHrZ76vwslro-uk,700372
|
|
37
38
|
iints/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
38
|
-
iints/cli/cli.py,sha256=
|
|
39
|
+
iints/cli/cli.py,sha256=oDg0rkD_zXu83oQHb6kTzjKzQBr7ETJA9-C5diPASg0,210187
|
|
39
40
|
iints/core/__init__.py,sha256=rRH2lTmikavR7BgeJCUla0ZmPbZxATR6rEcSSv_tet4,28
|
|
40
41
|
iints/core/device.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
41
42
|
iints/core/device_manager.py,sha256=479_CNn6YescDLWDE7w1BbwuLwRUmCUOColAVTEWQc8,2078
|
|
@@ -141,9 +142,9 @@ iints/validation/schemas.py,sha256=uXhiPxyfyvOgCA83ZPBIzlITOu663fWctYxOMXUyf1I,4
|
|
|
141
142
|
iints/visualization/__init__.py,sha256=OdxVHDpY-9bDt8DTWWd-dspn1p0O9T908Cck-IGFaiM,640
|
|
142
143
|
iints/visualization/cockpit.py,sha256=Y7hoJXcTEWQ8yLiU5X5abT58uqGGsQllftXJwqerG1E,25057
|
|
143
144
|
iints/visualization/uncertainty_cloud.py,sha256=I5nNzSitgai21rkul31YNtJriSEmCeTsW0GWW2HUskY,19848
|
|
144
|
-
iints_sdk_python35-1.1.
|
|
145
|
-
iints_sdk_python35-1.1.
|
|
146
|
-
iints_sdk_python35-1.1.
|
|
147
|
-
iints_sdk_python35-1.1.
|
|
148
|
-
iints_sdk_python35-1.1.
|
|
149
|
-
iints_sdk_python35-1.1.
|
|
145
|
+
iints_sdk_python35-1.1.2.dist-info/licenses/LICENSE,sha256=b1luljj2mWWDW10t_qFIqd9Z6euXAcDBmIXowWuUlm4,1417
|
|
146
|
+
iints_sdk_python35-1.1.2.dist-info/METADATA,sha256=F-4k2b--KpKAGQpS6Kw8LP_AW3A1zzqATS7lgs_Mx8I,8887
|
|
147
|
+
iints_sdk_python35-1.1.2.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
148
|
+
iints_sdk_python35-1.1.2.dist-info/entry_points.txt,sha256=aVioeLytTHG7WM7L3LIZ6XDJCKiSfqG-nVUQDVHPpQk,578
|
|
149
|
+
iints_sdk_python35-1.1.2.dist-info/top_level.txt,sha256=7Usr6NQKiC9SpNFyCis81MmgXy71lDCr5unR8BNXZ0E,6
|
|
150
|
+
iints_sdk_python35-1.1.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|