iints-sdk-python35 1.1.0__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 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
- __version__ = "0.1.22"
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
@@ -1,6 +1,8 @@
1
1
  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
+ from .model_catalog import LocalMistralModelProfile, list_local_mistral_models
5
+ from .prepare import prepare_ai_ready_artifacts
4
6
 
5
7
  __all__ = [
6
8
  "AIResponse",
@@ -10,4 +12,7 @@ __all__ = [
10
12
  "OllamaBackend",
11
13
  "GuardResult",
12
14
  "MDMPGuard",
15
+ "LocalMistralModelProfile",
16
+ "list_local_mistral_models",
17
+ "prepare_ai_ready_artifacts",
13
18
  ]
@@ -13,5 +13,5 @@ class MistralAPIBackend:
13
13
  def complete(self, *, system_prompt: str, user_prompt: str) -> str:
14
14
  raise RuntimeError(
15
15
  "Cloud fallback is not enabled in this SDK build yet. "
16
- "Use mode='local' with Ollama for Ministral."
16
+ "Use mode='local' with Ollama for the open Ministral 3 model."
17
17
  )
@@ -6,12 +6,19 @@ from urllib import error, request
6
6
 
7
7
 
8
8
  DEFAULT_OLLAMA_HOST = "http://127.0.0.1:11434"
9
- DEFAULT_MINISTRAL_MODEL = "mistral/ministral-8b-instruct"
9
+ DEFAULT_MINISTRAL_MODEL = "ministral-3:8b"
10
+ LEGACY_MINISTRAL_MODEL = "mistral/ministral-8b-instruct"
11
+ MIN_OLLAMA_VERSION_FOR_MINISTRAL_3 = (0, 13, 1)
10
12
  MINISTRAL_MODEL_ALIASES = (
11
13
  DEFAULT_MINISTRAL_MODEL,
14
+ "ministral-3",
15
+ "ministral-3:latest",
16
+ "ministral-3:8b",
17
+ "ministral-3:8b-instruct",
12
18
  "ministral",
13
19
  "ministral-8b",
14
20
  "ministral-8b-instruct",
21
+ LEGACY_MINISTRAL_MODEL,
15
22
  )
16
23
 
17
24
 
@@ -23,7 +30,7 @@ class OllamaBackend:
23
30
  *,
24
31
  model_name: str = DEFAULT_MINISTRAL_MODEL,
25
32
  base_url: str | None = None,
26
- timeout_seconds: float = 60.0,
33
+ timeout_seconds: float = 120.0,
27
34
  ) -> None:
28
35
  self.model_name = model_name
29
36
  self.base_url = (base_url or os.getenv("OLLAMA_HOST") or DEFAULT_OLLAMA_HOST).rstrip("/")
@@ -33,6 +40,28 @@ class OllamaBackend:
33
40
  def _pull_hint(self) -> str:
34
41
  return f"ollama pull {self.model_name}"
35
42
 
43
+ def _requires_ministral_3_runtime(self) -> bool:
44
+ requested = self.model_name.strip().lower()
45
+ return requested.startswith("ministral-3") or requested == "ministral"
46
+
47
+ @staticmethod
48
+ def _parse_version(raw_version: str) -> tuple[int, ...] | None:
49
+ value = raw_version.strip().lower().lstrip("v")
50
+ numeric_parts: list[int] = []
51
+ for part in value.split("."):
52
+ digits = ""
53
+ for char in part:
54
+ if char.isdigit():
55
+ digits += char
56
+ else:
57
+ break
58
+ if not digits:
59
+ break
60
+ numeric_parts.append(int(digits))
61
+ if not numeric_parts:
62
+ return None
63
+ return tuple(numeric_parts)
64
+
36
65
  def _request_json(
37
66
  self,
38
67
  path: str,
@@ -79,6 +108,27 @@ class OllamaBackend:
79
108
  return False
80
109
  return True
81
110
 
111
+ def server_version(self) -> str | None:
112
+ try:
113
+ response = self._request_json("/api/version", method="GET")
114
+ except Exception:
115
+ return None
116
+ raw_version = response.get("version")
117
+ if isinstance(raw_version, str) and raw_version.strip():
118
+ return raw_version.strip()
119
+ return None
120
+
121
+ def version_supported(self) -> tuple[bool | None, str | None]:
122
+ version = self.server_version()
123
+ if version is None:
124
+ return None, None
125
+ if not self._requires_ministral_3_runtime():
126
+ return True, version
127
+ parsed = self._parse_version(version)
128
+ if parsed is None:
129
+ return None, version
130
+ return parsed >= MIN_OLLAMA_VERSION_FOR_MINISTRAL_3, version
131
+
82
132
  def list_models(self) -> list[str]:
83
133
  response = self._request_json("/api/tags", method="GET")
84
134
  raw_models = response.get("models", [])
@@ -102,12 +152,24 @@ class OllamaBackend:
102
152
  return installed_lookup[self.model_name.lower()]
103
153
 
104
154
  requested = self.model_name.strip().lower()
105
- if requested in {"ministral", "ministral-8b", "ministral-8b-instruct"}:
155
+ if requested in {
156
+ "ministral",
157
+ "ministral-3",
158
+ "ministral-3:latest",
159
+ "ministral-3:8b",
160
+ "ministral-3:8b-instruct",
161
+ "ministral-8b",
162
+ "ministral-8b-instruct",
163
+ }:
106
164
  for alias in MINISTRAL_MODEL_ALIASES:
107
165
  resolved = installed_lookup.get(alias.lower())
108
166
  if resolved is not None:
109
167
  return resolved
110
168
 
169
+ for installed_name in installed:
170
+ lowered = installed_name.lower()
171
+ if "ministral-3" in lowered and "8b" in lowered:
172
+ return installed_name
111
173
  for installed_name in installed:
112
174
  lowered = installed_name.lower()
113
175
  if "ministral" in lowered and "8b" in lowered:
@@ -116,6 +178,14 @@ class OllamaBackend:
116
178
  return None
117
179
 
118
180
  def ensure_model_ready(self) -> str:
181
+ version_ok, version = self.version_supported()
182
+ if version_ok is False:
183
+ required_version = ".".join(str(part) for part in MIN_OLLAMA_VERSION_FOR_MINISTRAL_3)
184
+ raise RuntimeError(
185
+ "The open Ministral 3 local model requires a newer Ollama runtime.\n"
186
+ f"Detected Ollama: {version}\n"
187
+ f"Required Ollama: >= {required_version}"
188
+ )
119
189
  try:
120
190
  resolved = self.resolve_model_name()
121
191
  except RuntimeError:
@@ -137,6 +207,7 @@ class OllamaBackend:
137
207
  return resolved
138
208
 
139
209
  def healthcheck(self) -> dict[str, object]:
210
+ version_ok, version = self.version_supported()
140
211
  installed = self.list_models()
141
212
  resolved = self.resolve_model_name() if installed else None
142
213
  return {
@@ -148,6 +219,8 @@ class OllamaBackend:
148
219
  "ready": resolved is not None,
149
220
  "pull_command": None if resolved is not None else self._pull_hint(),
150
221
  "timeout_seconds": self.timeout_seconds,
222
+ "server_version": version,
223
+ "version_ok": version_ok,
151
224
  }
152
225
 
153
226
  def complete(self, *, system_prompt: str, user_prompt: str) -> str:
iints/ai/cli.py CHANGED
@@ -7,10 +7,13 @@ from typing import Any, Optional
7
7
  import typer
8
8
  from rich.console import Console
9
9
  from rich.panel import Panel
10
+ from rich.table import Table
10
11
  from typing_extensions import Annotated
11
12
 
12
13
  from .assistant import AIResponse, IINTSAssistant
13
14
  from .backends import DEFAULT_MINISTRAL_MODEL, OllamaBackend
15
+ from .model_catalog import list_local_mistral_models
16
+ from .prepare import prepare_ai_ready_artifacts
14
17
 
15
18
 
16
19
  app = typer.Typer(help="Research-only AI assistant commands gated by MDMP certification.")
@@ -34,6 +37,57 @@ def _load_json_payload(path: Path, label: str) -> Any:
34
37
  return payload
35
38
 
36
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
+
37
91
  def _write_output(path: Path | None, response: AIResponse) -> None:
38
92
  if path is None:
39
93
  return
@@ -64,6 +118,7 @@ def _render_local_check(console: Console, status: dict[str, object]) -> None:
64
118
  f"Endpoint: {status.get('base_url')}",
65
119
  f"Requested model: {status.get('requested_model')}",
66
120
  f"Resolved local model: {resolved_model}",
121
+ f"Server version: {status.get('server_version') or 'unknown'}",
67
122
  f"Timeout (s): {status.get('timeout_seconds')}",
68
123
  f"Installed models: {installed_text}",
69
124
  (
@@ -78,9 +133,81 @@ def _render_local_check(console: Console, status: dict[str, object]) -> None:
78
133
  )
79
134
  )
80
135
  if ready:
81
- console.print("[green]Local Ollama backend is ready for Ministral inference.[/green]")
136
+ console.print("[green]Local Ollama backend is ready for open Ministral 3 inference.[/green]")
82
137
  else:
83
138
  console.print("[bold red]Local Ollama backend is reachable, but the requested model is missing.[/bold red]")
139
+ if status.get("version_ok") is False:
140
+ console.print("[bold red]Ollama is too old for the open Ministral 3 runtime.[/bold red]")
141
+
142
+
143
+ @app.command("models")
144
+ def models() -> None:
145
+ console = Console()
146
+ table = Table(title="IINTS AI Local Mistral Model Guide")
147
+ table.add_column("Model Tag", style="cyan", no_wrap=True)
148
+ table.add_column("Best For", style="green")
149
+ table.add_column("Approx Download")
150
+ table.add_column("System RAM")
151
+ table.add_column("GPU VRAM")
152
+ table.add_column("Notes", overflow="fold")
153
+
154
+ for profile in list_local_mistral_models():
155
+ vram = f"{profile.recommended_vram_gb}+ GB" if profile.recommended_vram_gb is not None else "CPU-only"
156
+ table.add_row(
157
+ profile.tag,
158
+ profile.fit,
159
+ f"{profile.approx_download_gb:.1f} GB",
160
+ f"{profile.recommended_system_ram_gb}+ GB",
161
+ vram,
162
+ profile.notes,
163
+ )
164
+
165
+ console.print(table)
166
+ console.print(
167
+ "[dim]Tip:[/dim] start with "
168
+ f"`{DEFAULT_MINISTRAL_MODEL}` unless you know your hardware can comfortably run a larger local model."
169
+ )
170
+
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
+ )
84
211
 
85
212
 
86
213
  def _build_assistant(
@@ -135,8 +262,8 @@ def local_check(
135
262
 
136
263
  @app.command("explain")
137
264
  def explain(
138
- input_json: Annotated[Path, typer.Argument(help="JSON file with a single simulation step or decision context.")],
139
- 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,
140
267
  mode: Annotated[str, typer.Option(help="AI backend mode. Use 'local' for Ollama/Ministral.")] = "auto",
141
268
  model: Annotated[str, typer.Option(help="Ollama model name to use.")] = DEFAULT_MINISTRAL_MODEL,
142
269
  minimum_grade: Annotated[str, typer.Option(help="Minimum MDMP grade required to allow analysis.")] = "research_grade",
@@ -148,13 +275,20 @@ def explain(
148
275
  ) -> None:
149
276
  console = Console()
150
277
  try:
151
- payload = _load_json_payload(input_json, "Input JSON")
152
- assistant = _build_assistant(
278
+ resolved_input, resolved_cert, resolved_public_key = _resolve_cli_inputs(
279
+ task="explain",
280
+ input_path=input_json,
153
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,
154
288
  mode=mode,
155
289
  model=model,
156
290
  minimum_grade=minimum_grade,
157
- public_key=public_key,
291
+ public_key=resolved_public_key,
158
292
  trust_store=trust_store,
159
293
  ollama_host=ollama_host,
160
294
  timeout_seconds=timeout_seconds,
@@ -169,8 +303,8 @@ def explain(
169
303
 
170
304
  @app.command("trends")
171
305
  def trends(
172
- input_json: Annotated[Path, typer.Argument(help="JSON file with glucose trace data or a run payload.")],
173
- 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,
174
308
  mode: Annotated[str, typer.Option(help="AI backend mode. Use 'local' for Ollama/Ministral.")] = "auto",
175
309
  model: Annotated[str, typer.Option(help="Ollama model name to use.")] = DEFAULT_MINISTRAL_MODEL,
176
310
  minimum_grade: Annotated[str, typer.Option(help="Minimum MDMP grade required to allow analysis.")] = "research_grade",
@@ -182,13 +316,20 @@ def trends(
182
316
  ) -> None:
183
317
  console = Console()
184
318
  try:
185
- payload = _load_json_payload(input_json, "Input JSON")
186
- assistant = _build_assistant(
319
+ resolved_input, resolved_cert, resolved_public_key = _resolve_cli_inputs(
320
+ task="trends",
321
+ input_path=input_json,
187
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,
188
329
  mode=mode,
189
330
  model=model,
190
331
  minimum_grade=minimum_grade,
191
- public_key=public_key,
332
+ public_key=resolved_public_key,
192
333
  trust_store=trust_store,
193
334
  ollama_host=ollama_host,
194
335
  timeout_seconds=timeout_seconds,
@@ -203,8 +344,8 @@ def trends(
203
344
 
204
345
  @app.command("anomalies")
205
346
  def anomalies(
206
- input_json: Annotated[Path, typer.Argument(help="JSON file with simulation results or run summary.")],
207
- 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,
208
349
  mode: Annotated[str, typer.Option(help="AI backend mode. Use 'local' for Ollama/Ministral.")] = "auto",
209
350
  model: Annotated[str, typer.Option(help="Ollama model name to use.")] = DEFAULT_MINISTRAL_MODEL,
210
351
  minimum_grade: Annotated[str, typer.Option(help="Minimum MDMP grade required to allow analysis.")] = "research_grade",
@@ -216,13 +357,20 @@ def anomalies(
216
357
  ) -> None:
217
358
  console = Console()
218
359
  try:
219
- payload = _load_json_payload(input_json, "Input JSON")
220
- assistant = _build_assistant(
360
+ resolved_input, resolved_cert, resolved_public_key = _resolve_cli_inputs(
361
+ task="anomalies",
362
+ input_path=input_json,
221
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,
222
370
  mode=mode,
223
371
  model=model,
224
372
  minimum_grade=minimum_grade,
225
- public_key=public_key,
373
+ public_key=resolved_public_key,
226
374
  trust_store=trust_store,
227
375
  ollama_host=ollama_host,
228
376
  timeout_seconds=timeout_seconds,
@@ -237,8 +385,8 @@ def anomalies(
237
385
 
238
386
  @app.command("report")
239
387
  def report(
240
- input_json: Annotated[Path, typer.Argument(help="JSON file with run-level simulation outputs.")],
241
- 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,
242
390
  mode: Annotated[str, typer.Option(help="AI backend mode. Use 'local' for Ollama/Ministral.")] = "auto",
243
391
  model: Annotated[str, typer.Option(help="Ollama model name to use.")] = DEFAULT_MINISTRAL_MODEL,
244
392
  minimum_grade: Annotated[str, typer.Option(help="Minimum MDMP grade required to allow analysis.")] = "research_grade",
@@ -250,13 +398,20 @@ def report(
250
398
  ) -> None:
251
399
  console = Console()
252
400
  try:
253
- payload = _load_json_payload(input_json, "Input JSON")
254
- assistant = _build_assistant(
401
+ resolved_input, resolved_cert, resolved_public_key = _resolve_cli_inputs(
402
+ task="report",
403
+ input_path=input_json,
255
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,
256
411
  mode=mode,
257
412
  model=model,
258
413
  minimum_grade=minimum_grade,
259
- public_key=public_key,
414
+ public_key=resolved_public_key,
260
415
  trust_store=trust_store,
261
416
  ollama_host=ollama_host,
262
417
  timeout_seconds=timeout_seconds,
@@ -0,0 +1,55 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+
5
+ from .backends.ollama import DEFAULT_MINISTRAL_MODEL
6
+
7
+
8
+ @dataclass(frozen=True)
9
+ class LocalMistralModelProfile:
10
+ tag: str
11
+ label: str
12
+ approx_download_gb: float
13
+ recommended_system_ram_gb: int
14
+ recommended_vram_gb: int | None
15
+ fit: str
16
+ notes: str
17
+ aliases: tuple[str, ...] = ()
18
+
19
+
20
+ LOCAL_MISTRAL_MODEL_PROFILES: tuple[LocalMistralModelProfile, ...] = (
21
+ LocalMistralModelProfile(
22
+ tag="ministral-3:3b",
23
+ label="Ministral 3 3B",
24
+ approx_download_gb=3.0,
25
+ recommended_system_ram_gb=16,
26
+ recommended_vram_gb=6,
27
+ fit="Entry-level laptop / small edge box",
28
+ notes="Best starting point for CPU-only systems or modest GPUs. Fastest option, lowest memory pressure.",
29
+ aliases=("ministral-3:3b",),
30
+ ),
31
+ LocalMistralModelProfile(
32
+ tag=DEFAULT_MINISTRAL_MODEL,
33
+ label="Ministral 3 8B",
34
+ approx_download_gb=6.0,
35
+ recommended_system_ram_gb=24,
36
+ recommended_vram_gb=10,
37
+ fit="Balanced desktop / strong laptop",
38
+ notes="Recommended default for most users. Best trade-off between quality, speed, and local memory footprint.",
39
+ aliases=("ministral", "ministral-3", "ministral-3:8b"),
40
+ ),
41
+ LocalMistralModelProfile(
42
+ tag="ministral-3:14b",
43
+ label="Ministral 3 14B",
44
+ approx_download_gb=10.0,
45
+ recommended_system_ram_gb=32,
46
+ recommended_vram_gb=16,
47
+ fit="High-end workstation",
48
+ notes="Use when you have plenty of RAM or a strong GPU and want better reasoning depth at the cost of latency.",
49
+ aliases=("ministral-3:14b",),
50
+ ),
51
+ )
52
+
53
+
54
+ def list_local_mistral_models() -> list[LocalMistralModelProfile]:
55
+ return list(LOCAL_MISTRAL_MODEL_PROFILES)
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.0
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
@@ -81,7 +81,7 @@ cd iints_quickstart
81
81
  iints presets run --name baseline_t1d --algo algorithms/example_algorithm.py
82
82
  ```
83
83
 
84
- ## AI Assistant (Ministral via Ollama)
84
+ ## AI Assistant (Ministral 3 Open-Weight via Ollama)
85
85
 
86
86
  The SDK now includes a research-only AI assistant layer for explanations and run summaries.
87
87
  It is gated by MDMP verification before any LLM call is allowed.
@@ -95,30 +95,59 @@ python -m pip install -U pip
95
95
  python -m pip install -e ".[mdmp]"
96
96
  ```
97
97
 
98
- Run Ministral locally with Ollama:
98
+ Run the open local Mistral model locally with Ollama:
99
99
 
100
100
  ```bash
101
- ollama pull mistral/ministral-8b-instruct
102
- iints ai local-check --model ministral
101
+ python -m pip install -e ".[mdmp]"
102
+ ollama pull ministral-3:8b
103
+ iints ai models
104
+ ```
105
+
106
+ Recommended first-time setup:
107
+
108
+ ```bash
109
+ ollama pull ministral-3:8b
110
+ iints ai local-check --model ministral-3:8b
103
111
  ```
104
112
 
105
- Example commands:
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:
106
124
 
107
125
  ```bash
108
126
  iints ai explain results/step.json \
109
127
  --mdmp-cert results/report.signed.mdmp
110
-
111
- iints ai report results/simulation_run.json \
112
- --mdmp-cert results/report.signed.mdmp \
113
- --output results/ai_report.md
114
128
  ```
115
129
 
116
130
  Notes:
117
131
  - AI analysis is blocked if the MDMP artifact is invalid.
118
132
  - Minimum required MDMP grade defaults to `research_grade`.
133
+ - The SDK now targets the open local `Ministral 3` Ollama model by default.
134
+ - Users can choose a larger or smaller local Mistral-family model with `--model ...`.
119
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.
120
138
  - Output is research-only and not medical advice.
121
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
+
122
151
  ## MDMP (Short)
123
152
  MDMP is the data-quality protocol used by IINTS.
124
153
 
@@ -1,15 +1,17 @@
1
- iints/__init__.py,sha256=XsWIyn-Zkw9S0NMRJA0BpUTAxHtXAdU4Te4BBEox-_I,6047
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=Yg0XPOBnABwGgAx5ZJK6ycYOKx8tiCy72Nn6CCiY-gg,349
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=Tds-d8IENa4lwNiFZODA1NL-9nSCs8GlhQTC-4zjjog,12202
6
+ iints/ai/cli.py,sha256=_1ogEAb36BAt7sZ2CQSRKIJSpdn5xrlM7nNTtBIqfRo,18345
7
7
  iints/ai/mdmp_guard.py,sha256=BpFQX0oyP9WMCUZbFhhoBzomNeVKuI1HY1EFH9cG8EE,4249
8
+ iints/ai/model_catalog.py,sha256=gRW-i4eaXkrjX3mIKJlGzHqzU75lpIulEFKQsCX11CI,1804
9
+ iints/ai/prepare.py,sha256=z3y5elCAMv0p_aNq4gQfZA1uIT7_cX3FGRdzmoZoKho,12967
8
10
  iints/ai/prompts.py,sha256=pGp9tC1wBZXGG5duxfktaJEF4p_cvmR0zEIxmMTEAyE,2812
9
11
  iints/ai/backends/__init__.py,sha256=EAJRZS8G0DK7fffw_LHio9DkyYHwtzvz2Jo7AXk7pk4,303
10
12
  iints/ai/backends/base.py,sha256=BLgP03X-jebYkF9D5n5crawoPBmy3RSh4q3jaT8a9XM,274
11
- iints/ai/backends/mistral_api.py,sha256=CUFwyymCmaY_lTN0cP6ONpYGUT0us5rjfZS4v1hv2KE,490
12
- iints/ai/backends/ollama.py,sha256=dgSbKpG8fnLQczKbzqQExYMW1c48uStki8B5Dhm1baI,6067
13
+ iints/ai/backends/mistral_api.py,sha256=dousHnzgzuik49822H8nCclYv5NoxHpTMLwtZPVj_TM,507
14
+ iints/ai/backends/ollama.py,sha256=pH_UkrCmm6jI4_Hl-NM_7W4tXOwHZdZ0heeKph72NUk,8739
13
15
  iints/analysis/__init__.py,sha256=Qx49KDy0deoxSnVORJB10_BsdezZXKsuoXTR0KZRcqg,411
14
16
  iints/analysis/algorithm_xray.py,sha256=-AtXkZsgnsiFQ_K-IozjIDWkq-dDn0i0zmqWVMhINP4,15952
15
17
  iints/analysis/baseline.py,sha256=PCFVb5vX0lYKChZvVk-8I_B5NLQQwGyx7Y6M3XjpIEY,3458
@@ -34,7 +36,7 @@ iints/api/registry.py,sha256=h2syJwacFbgrtgnVK20JwlXivvVO31zeJ_Ez4KBkn1g,3240
34
36
  iints/api/template_algorithm.py,sha256=AFs9AymL3ddWAjgpOkF1Oa3TeOSg56siyDt_BmsAND8,9195
35
37
  iints/assets/iints_logo.png,sha256=rWzP8XqIYDrPCTp378w73zA1snKCUHrZ76vwslro-uk,700372
36
38
  iints/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
37
- iints/cli/cli.py,sha256=bTYXVneLUBKBr4p1jSwg0aNKT2ECLZA-PwJ9UggYIVg,208838
39
+ iints/cli/cli.py,sha256=oDg0rkD_zXu83oQHb6kTzjKzQBr7ETJA9-C5diPASg0,210187
38
40
  iints/core/__init__.py,sha256=rRH2lTmikavR7BgeJCUla0ZmPbZxATR6rEcSSv_tet4,28
39
41
  iints/core/device.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
40
42
  iints/core/device_manager.py,sha256=479_CNn6YescDLWDE7w1BbwuLwRUmCUOColAVTEWQc8,2078
@@ -140,9 +142,9 @@ iints/validation/schemas.py,sha256=uXhiPxyfyvOgCA83ZPBIzlITOu663fWctYxOMXUyf1I,4
140
142
  iints/visualization/__init__.py,sha256=OdxVHDpY-9bDt8DTWWd-dspn1p0O9T908Cck-IGFaiM,640
141
143
  iints/visualization/cockpit.py,sha256=Y7hoJXcTEWQ8yLiU5X5abT58uqGGsQllftXJwqerG1E,25057
142
144
  iints/visualization/uncertainty_cloud.py,sha256=I5nNzSitgai21rkul31YNtJriSEmCeTsW0GWW2HUskY,19848
143
- iints_sdk_python35-1.1.0.dist-info/licenses/LICENSE,sha256=b1luljj2mWWDW10t_qFIqd9Z6euXAcDBmIXowWuUlm4,1417
144
- iints_sdk_python35-1.1.0.dist-info/METADATA,sha256=TkfRZkb2KCZBvjdUCRMCY6OTGF7bJRDixc04rdIdCoU,7716
145
- iints_sdk_python35-1.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
146
- iints_sdk_python35-1.1.0.dist-info/entry_points.txt,sha256=ZlC9C1-rhefU6t-Wr7tT05LI5agdKnpsDJgXKxDO350,528
147
- iints_sdk_python35-1.1.0.dist-info/top_level.txt,sha256=7Usr6NQKiC9SpNFyCis81MmgXy71lDCr5unR8BNXZ0E,6
148
- iints_sdk_python35-1.1.0.dist-info/RECORD,,
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,,
@@ -1,5 +1,6 @@
1
1
  [console_scripts]
2
2
  iints = iints.cli.cli:app
3
+ iints-sdk-doctor = iints_sdk_python35_doctor:main
3
4
 
4
5
  [iints.algorithms]
5
6
  Correction Bolus = iints.core.algorithms.correction_bolus:CorrectionBolusAlgorithm