iints-sdk-python35 1.5.18__py3-none-any.whl → 1.5.20__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.
Files changed (51) hide show
  1. iints/__init__.py +1 -1
  2. iints/cli/cli.py +321 -26
  3. iints/core/patient/hovorka_model.py +11 -5
  4. iints/core/patient/models.py +5 -2
  5. iints/core/simulator.py +4 -1
  6. iints/data/__init__.py +16 -0
  7. iints/data/certify.py +139 -1
  8. iints/data/contracts/diabetes_cgm_mdmp_contract.yaml +60 -0
  9. iints/data/runner.py +34 -0
  10. iints/highlevel.py +3 -2
  11. iints/mdmp/backend.py +29 -0
  12. iints/research/alphafold_engine.py +91 -0
  13. iints/research/anatomy.py +187 -0
  14. iints/research/genetics.py +178 -0
  15. iints/research/genomics_engine.py +185 -0
  16. iints/research/jetson_hf_trainer.py +14 -12
  17. iints/research/pharmacology.py +163 -0
  18. iints/research/physiology.py +114 -0
  19. iints/research/structure.py +387 -0
  20. iints/research/tissue_stressor.py +131 -0
  21. iints_desktop/__init__.py +14 -0
  22. iints_desktop/app.py +341 -0
  23. iints_desktop/assets/alphafold/AF-P01275-F1-model_v4.cif +2474 -0
  24. iints_desktop/assets/alphafold/AF-P01308-F1-model_v4.cif +1618 -0
  25. iints_desktop/assets/alphafold/AF-P06213-F1-model_v6.cif +15926 -0
  26. iints_desktop/assets/alphafold/AF-P14672-F1-model_v6.cif +5912 -0
  27. iints_desktop/assets/alphafold/AF-P47871-F1-model_v6.cif +5762 -0
  28. iints_desktop/assets/alphafold/gcgr_3D.png +0 -0
  29. iints_desktop/assets/alphafold/glucagon_3D.png +0 -0
  30. iints_desktop/assets/alphafold/glut4_3D.png +0 -0
  31. iints_desktop/assets/alphafold/insr_3D.png +0 -0
  32. iints_desktop/assets/alphafold/insulin_3D.png +0 -0
  33. iints_desktop/cocoa_app.py +317 -0
  34. iints_desktop/engine.py +398 -0
  35. iints_desktop/fetcher.py +64 -0
  36. iints_desktop/launcher.py +42 -0
  37. iints_desktop/local_ai.py +320 -0
  38. iints_desktop/mdmp.py +92 -0
  39. iints_desktop/molecule_viewer.py +224 -0
  40. iints_desktop/molecules.py +257 -0
  41. iints_desktop/qt_app.py +3091 -0
  42. iints_desktop/render_3dmol.py +65 -0
  43. iints_desktop/results.py +163 -0
  44. {iints_sdk_python35-1.5.18.dist-info → iints_sdk_python35-1.5.20.dist-info}/METADATA +34 -66
  45. {iints_sdk_python35-1.5.18.dist-info → iints_sdk_python35-1.5.20.dist-info}/RECORD +51 -19
  46. {iints_sdk_python35-1.5.18.dist-info → iints_sdk_python35-1.5.20.dist-info}/WHEEL +1 -1
  47. {iints_sdk_python35-1.5.18.dist-info → iints_sdk_python35-1.5.20.dist-info}/entry_points.txt +4 -0
  48. {iints_sdk_python35-1.5.18.dist-info → iints_sdk_python35-1.5.20.dist-info}/top_level.txt +1 -0
  49. {iints_sdk_python35-1.5.18.dist-info → iints_sdk_python35-1.5.20.dist-info}/licenses/LICENSE +0 -0
  50. {iints_sdk_python35-1.5.18.dist-info → iints_sdk_python35-1.5.20.dist-info}/licenses/LICENSE-MIT-IINTS-LEGACY +0 -0
  51. {iints_sdk_python35-1.5.18.dist-info → iints_sdk_python35-1.5.20.dist-info}/licenses/NOTICE +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.5.18"
14
+ __version__ = "1.5.20"
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.
iints/cli/cli.py CHANGED
@@ -109,6 +109,11 @@ from iints.data.realism_validator import (
109
109
  validate_realism_dataset,
110
110
  write_realism_report,
111
111
  )
112
+ from iints.data.certify import (
113
+ certification_payload,
114
+ write_mdmp_certificate,
115
+ write_standard_diabetes_contract,
116
+ )
112
117
  from iints.data.realism_governance import review_real_data_realism
113
118
  from iints.data.realism_dashboard import write_realism_dashboard
114
119
  from iints.demo_assets import export_live_stage_demo
@@ -4528,19 +4533,19 @@ def new_algo(
4528
4533
  # Replace placeholders
4529
4534
  # We expect the template class name to be {{ALGO_NAME}} and author {{AUTHOR_NAME}}
4530
4535
  # But wait, the file I wrote uses {{ALGO_NAME}} as a class name, which is valid syntax only if I replace it.
4531
- # The file on disk is valid python ONLY if those tokens are valid.
4536
+ # The file on disk is valid python ONLY if those tokens are valid.
4532
4537
  # Actually, in the previous step I wrote {{ALGO_NAME}} literally into the python file.
4533
- # That makes the template file itself invalid python syntax until replaced.
4534
- # That's fine for a template file, but might confuse linters.
4538
+ # That makes the template file itself invalid python syntax until replaced.
4539
+ # That's fine for a template file, but might confuse linters.
4535
4540
  # Ideally it's a .txt or .tmpl, but .py is fine if we accept it's a template.
4536
-
4541
+
4537
4542
  final_content = template_content.replace("{{ALGO_NAME}}", f"{name}Algorithm")
4538
4543
  final_content = final_content.replace("{{AUTHOR_NAME}}", author)
4539
4544
 
4540
4545
  output_file = output_dir / f"{name.lower().replace(' ', '_')}_algorithm.py"
4541
4546
  with open(output_file, "w") as f:
4542
4547
  f.write(final_content)
4543
-
4548
+
4544
4549
  typer.echo(f"Successfully created new algorithm template: {output_file}")
4545
4550
 
4546
4551
 
@@ -7909,9 +7914,26 @@ def data_contract_template(
7909
7914
  @data_app.command(name="certify-template")
7910
7915
  def data_certify_template(
7911
7916
  output_path: Annotated[Path, typer.Option(help="Where to write the starter certification contract YAML")] = Path("data_contract.yaml"),
7917
+ profile: Annotated[
7918
+ str,
7919
+ typer.Option(help="Template profile: diabetes (default) or basic"),
7920
+ ] = "diabetes",
7912
7921
  ):
7913
7922
  """Write a starter IINTS data-certification contract."""
7914
- data_contract_template(output_path=output_path)
7923
+ console = Console()
7924
+ normalized = profile.strip().lower()
7925
+ if normalized in {"diabetes", "diabetes-cgm", "clinical-diabetes", "standard"}:
7926
+ write_standard_diabetes_contract(output_path)
7927
+ console.print(f"[green]Diabetes MDMP contract written:[/green] {output_path}")
7928
+ console.print("[cyan]Profile:[/cyan] diabetes-cgm research contract")
7929
+ return
7930
+ if normalized not in {"basic", "legacy"}:
7931
+ console.print("[bold red]Unknown profile. Use 'diabetes' or 'basic'.[/bold red]")
7932
+ raise typer.Exit(code=1)
7933
+ template = _build_mdmp_core_contract_template() if active_mdmp_backend() == "mdmp_core" else _build_data_contract_template()
7934
+ output_path.parent.mkdir(parents=True, exist_ok=True)
7935
+ output_path.write_text(yaml.safe_dump(template, sort_keys=False), encoding="utf-8")
7936
+ console.print(f"[green]Basic certification contract written:[/green] {output_path}")
7915
7937
 
7916
7938
 
7917
7939
  @data_app.command(name="contract-run", hidden=True, deprecated=True)
@@ -7921,6 +7943,16 @@ def data_contract_run(
7921
7943
  output_json: Annotated[Optional[Path], typer.Option(help="Optional output report JSON path")] = None,
7922
7944
  apply_builtin_transforms: Annotated[bool, typer.Option(help="Apply built-in unit conversion transforms from the contract")] = True,
7923
7945
  fail_on_noncompliant: Annotated[bool, typer.Option(help="Exit code 1 when compliance checks fail")] = False,
7946
+ quick: Annotated[bool, typer.Option("--quick", help="Quick scan: only load the first --quick-rows rows for large datasets")] = False,
7947
+ quick_rows: Annotated[int, typer.Option(help="Rows loaded when --quick is enabled")] = 5000,
7948
+ certificate_output: Annotated[Optional[Path], typer.Option(help="Optional MDMP certificate JSON output path")] = None,
7949
+ signing_key: Annotated[Optional[Path], typer.Option(help="Optional Ed25519 private key PEM for externally verifiable certificates")] = None,
7950
+ signing_key_id: Annotated[str, typer.Option(help="Key id written into a signed certificate")] = "iints_local_mdmp_v1",
7951
+ signed_by: Annotated[str, typer.Option(help="Signer label for generated MDMP certificates")] = "IINTS-AF Local MDMP",
7952
+ signing_key_passphrase_env: Annotated[
7953
+ Optional[str],
7954
+ typer.Option(help="Environment variable containing the private-key passphrase, if encrypted"),
7955
+ ] = None,
7924
7956
  min_mdmp_grade: Annotated[
7925
7957
  Optional[str],
7926
7958
  typer.Option(help="Optional MDMP grade gate (draft, research_grade, clinical_grade)"),
@@ -7937,7 +7969,7 @@ def data_contract_run(
7937
7969
 
7938
7970
  try:
7939
7971
  contract = load_mdmp_contract(contract_path)
7940
- df = pd.read_csv(input_csv)
7972
+ df = pd.read_csv(input_csv, nrows=max(1, int(quick_rows)) if quick else None)
7941
7973
  report = run_mdmp_validation(
7942
7974
  contract,
7943
7975
  df,
@@ -7956,6 +7988,9 @@ def data_contract_run(
7956
7988
  summary.add_row("Backend", active_mdmp_backend())
7957
7989
  summary.add_row("MDMP grade", report.mdmp_grade)
7958
7990
  summary.add_row("MDMP protocol", report.mdmp_protocol_version)
7991
+ summary.add_row("Scan mode", "quick" if quick else "full")
7992
+ if quick:
7993
+ summary.add_row("Quick rows limit", str(max(1, int(quick_rows))))
7959
7994
  summary.add_row(
7960
7995
  "Certified",
7961
7996
  "yes" if report.certified_for_medical_research else "no",
@@ -7977,12 +8012,34 @@ def data_contract_run(
7977
8012
  check.detail,
7978
8013
  )
7979
8014
  console.print(checks_table)
8015
+ payload = certification_payload(
8016
+ report,
8017
+ quick=quick,
8018
+ quick_rows=max(1, int(quick_rows)) if quick else None,
8019
+ input_path=input_csv,
8020
+ )
7980
8021
 
7981
8022
  if output_json is not None:
7982
8023
  output_json.parent.mkdir(parents=True, exist_ok=True)
7983
- output_json.write_text(json.dumps(report.to_dict(), indent=2))
8024
+ output_json.write_text(json.dumps(payload, indent=2), encoding="utf-8")
7984
8025
  console.print(f"[green]Contract report written:[/green] {output_json}")
7985
8026
 
8027
+ if certificate_output is not None:
8028
+ certificate_path = write_mdmp_certificate(
8029
+ payload,
8030
+ certificate_output,
8031
+ issued_by=signed_by,
8032
+ signing_key=signing_key,
8033
+ key_id=signing_key_id,
8034
+ passphrase_env=signing_key_passphrase_env,
8035
+ )
8036
+ console.print(f"[green]MDMP certificate written:[/green] {certificate_path}")
8037
+ if signing_key is None:
8038
+ console.print(
8039
+ "[yellow]Certificate is unsigned SHA-256 evidence only. "
8040
+ "Pass --signing-key for externally verifiable Ed25519 signing.[/yellow]"
8041
+ )
8042
+
7986
8043
  if min_mdmp_grade is not None:
7987
8044
  normalized = min_mdmp_grade.strip().lower()
7988
8045
  if normalized not in MDMP_GRADE_ORDER:
@@ -8006,6 +8063,16 @@ def data_certify(
8006
8063
  output_json: Annotated[Optional[Path], typer.Option(help="Optional output report JSON path")] = None,
8007
8064
  apply_builtin_transforms: Annotated[bool, typer.Option(help="Apply built-in unit conversion transforms from the contract")] = True,
8008
8065
  fail_on_noncompliant: Annotated[bool, typer.Option(help="Exit code 1 when compliance checks fail")] = False,
8066
+ quick: Annotated[bool, typer.Option("--quick", help="Quick scan: only load the first --quick-rows rows for large datasets")] = False,
8067
+ quick_rows: Annotated[int, typer.Option(help="Rows loaded when --quick is enabled")] = 5000,
8068
+ certificate_output: Annotated[Optional[Path], typer.Option(help="Optional MDMP certificate JSON output path")] = None,
8069
+ signing_key: Annotated[Optional[Path], typer.Option(help="Optional Ed25519 private key PEM for externally verifiable certificates")] = None,
8070
+ signing_key_id: Annotated[str, typer.Option(help="Key id written into a signed certificate")] = "iints_local_mdmp_v1",
8071
+ signed_by: Annotated[str, typer.Option(help="Signer label for generated MDMP certificates")] = "IINTS-AF Local MDMP",
8072
+ signing_key_passphrase_env: Annotated[
8073
+ Optional[str],
8074
+ typer.Option(help="Environment variable containing the private-key passphrase, if encrypted"),
8075
+ ] = None,
8009
8076
  min_mdmp_grade: Annotated[
8010
8077
  Optional[str],
8011
8078
  typer.Option(help="Optional certification grade gate (draft, research_grade, clinical_grade, ai_ready)"),
@@ -8018,10 +8085,62 @@ def data_certify(
8018
8085
  output_json=output_json,
8019
8086
  apply_builtin_transforms=apply_builtin_transforms,
8020
8087
  fail_on_noncompliant=fail_on_noncompliant,
8088
+ quick=quick,
8089
+ quick_rows=quick_rows,
8090
+ certificate_output=certificate_output,
8091
+ signing_key=signing_key,
8092
+ signing_key_id=signing_key_id,
8093
+ signed_by=signed_by,
8094
+ signing_key_passphrase_env=signing_key_passphrase_env,
8021
8095
  min_mdmp_grade=min_mdmp_grade,
8022
8096
  )
8023
8097
 
8024
8098
 
8099
+ @data_app.command(name="mdmp-keygen")
8100
+ def data_mdmp_keygen(
8101
+ output_dir: Annotated[Path, typer.Option(help="Directory for the MDMP Ed25519 keypair")] = Path("certs/mdmp"),
8102
+ private_name: Annotated[str, typer.Option(help="Private key filename")] = "iints_mdmp_private.pem",
8103
+ public_name: Annotated[str, typer.Option(help="Public key filename")] = "iints_mdmp_public.pem",
8104
+ passphrase: Annotated[Optional[str], typer.Option(help="Optional private-key passphrase (prefer env/file)")] = None,
8105
+ passphrase_env: Annotated[Optional[str], typer.Option(help="Environment variable containing private-key passphrase")] = None,
8106
+ passphrase_file: Annotated[Optional[Path], typer.Option(help="File containing private-key passphrase")] = None,
8107
+ ) -> None:
8108
+ """Generate an Ed25519 keypair for signed MDMP certificates."""
8109
+
8110
+ console = Console()
8111
+ secret = _resolve_secret_option(
8112
+ console=console,
8113
+ label="passphrase",
8114
+ direct_value=passphrase,
8115
+ env_name=passphrase_env,
8116
+ file_path=passphrase_file,
8117
+ )
8118
+ try:
8119
+ from mdmp_core.crypto import generate_keypair
8120
+
8121
+ result = generate_keypair(
8122
+ output_dir=output_dir,
8123
+ private_name=private_name,
8124
+ public_name=public_name,
8125
+ passphrase=secret,
8126
+ )
8127
+ except Exception as exc:
8128
+ console.print(f"[bold red]MDMP key generation failed:[/bold red] {exc}")
8129
+ raise typer.Exit(code=1)
8130
+
8131
+ table = Table(title="MDMP Signing Keypair")
8132
+ table.add_column("Field", style="cyan")
8133
+ table.add_column("Value")
8134
+ table.add_row("Private key", str(result["private_key"]))
8135
+ table.add_row("Public key", str(result["public_key"]))
8136
+ table.add_row("Encrypted", "yes" if result.get("private_key_encrypted") else "no")
8137
+ console.print(table)
8138
+ console.print(
8139
+ "[dim]Use the private key with `iints data certify --signing-key ... --certificate-output ...`. "
8140
+ "Share the public key with reviewers who need to verify certificates.[/dim]"
8141
+ )
8142
+
8143
+
8025
8144
  @data_app.command(name="eu-ai-pact-review")
8026
8145
  def data_eu_ai_pact_review(
8027
8146
  report_json: Annotated[Path, typer.Argument(help="Path to an MDMP/data certification JSON report")],
@@ -8367,6 +8486,35 @@ def data_synthetic_mirror(
8367
8486
  if fail_on_noncompliant and not artifact.validation.is_compliant:
8368
8487
  raise typer.Exit(code=1)
8369
8488
 
8489
+ @data_app.command(name="pull-hf")
8490
+ def data_pull_hf(
8491
+ repo: Annotated[str, typer.Option("--repo", help="Hugging Face dataset repository (e.g. MaxPrestige/Synthetic-Diabetes-Dataset)")],
8492
+ output: Annotated[Path, typer.Option("--output", help="Output directory for the pulled dataset")] = Path("data/raw/"),
8493
+ split: Annotated[str, typer.Option("--split", help="Dataset split to download (default: train)")] = "train",
8494
+ ):
8495
+ """Pull a tabular or time-series dataset from Hugging Face and prepare it for MDMP."""
8496
+ console = Console()
8497
+ try:
8498
+ from datasets import load_dataset
8499
+ except ImportError:
8500
+ console.print("[bold red]Please install 'datasets' package first: pip install datasets[/bold red]")
8501
+ raise typer.Exit(code=1)
8502
+
8503
+ console.print(f"Downloading dataset {repo} ({split} split)...")
8504
+ try:
8505
+ dataset = load_dataset(repo, split=split)
8506
+ df = dataset.to_pandas()
8507
+ output.mkdir(parents=True, exist_ok=True)
8508
+ repo_name = repo.split("/")[-1]
8509
+ out_file = output / f"{repo_name}_{split}.csv"
8510
+ df.to_csv(out_file, index=False)
8511
+ console.print(f"[green]Successfully downloaded dataset to {out_file}[/green]")
8512
+ console.print(f"Rows: {len(df)}, Columns: {len(df.columns)}")
8513
+ console.print("[yellow]Next step:[/yellow] Use 'iints data certify' or 'iints data mdmp-visualizer' to map columns to the SDK's schema.")
8514
+ except Exception as e:
8515
+ console.print(f"[bold red]Failed to pull dataset:[/bold red] {e}")
8516
+ raise typer.Exit(code=1)
8517
+
8370
8518
 
8371
8519
  @mdmp_app.command(name="template")
8372
8520
  def mdmp_template(
@@ -9365,6 +9513,63 @@ def manage_results(
9365
9513
  _print_results_index_bundle(bundle, console)
9366
9514
 
9367
9515
 
9516
+ @research_app.command(name="xai-report")
9517
+ def research_xai_report(
9518
+ results_csv: Annotated[Path, typer.Argument(help="Path to the simulation results.csv")],
9519
+ hf_model: Annotated[Optional[str], typer.Option("--hf-model", help="Hugging Face medical LLM to use for generation (e.g. devanshamin/PubMedDiabetes-LLM-Predictions)")] = None,
9520
+ output_json: Annotated[Path, typer.Option("--output", help="Path to write the xai_events.json")] = Path("xai_events.json"),
9521
+ ):
9522
+ """Generate an XAI (Explainable AI) clinical report from a simulation run, optionally using a Hugging Face medical LLM."""
9523
+ console = Console()
9524
+ if not results_csv.is_file():
9525
+ console.print(f"[bold red]Results file not found:[/bold red] {results_csv}")
9526
+ raise typer.Exit(code=1)
9527
+
9528
+ try:
9529
+ df = pd.read_csv(results_csv)
9530
+ hypos = len(df[df.get("glucose_actual_mgdl", pd.Series([100])) < 70])
9531
+ hypers = len(df[df.get("glucose_actual_mgdl", pd.Series([100])) > 180])
9532
+ summary_text = f"Simulation complete. {hypos} hypoglycemic intervals and {hypers} hyperglycemic intervals detected."
9533
+
9534
+ if hf_model:
9535
+ console.print(f"Loading medical LLM from Hugging Face: {hf_model}...")
9536
+ try:
9537
+ from transformers import pipeline
9538
+ generator = pipeline("text-generation", model=hf_model, device="cpu")
9539
+ prompt = f"Patient simulation summary: {summary_text}. Provide a clinical interpretation:"
9540
+ output = generator(prompt, max_new_tokens=50, num_return_sequences=1)
9541
+ clinical_interpretation = output[0]["generated_text"]
9542
+ except ImportError:
9543
+ console.print("[bold red]Please install transformers: pip install transformers torch[/bold red]")
9544
+ raise typer.Exit(code=1)
9545
+ except Exception as e:
9546
+ console.print(f"[bold yellow]Failed to generate with {hf_model}, falling back to basic summary. Error: {e}[/bold yellow]")
9547
+ clinical_interpretation = "Clinical interpretation requires a successful LLM generation."
9548
+ else:
9549
+ clinical_interpretation = "Provide a --hf-model to generate clinical insights."
9550
+
9551
+ xai_events = {
9552
+ "source_file": str(results_csv),
9553
+ "summary": summary_text,
9554
+ "clinical_interpretation": clinical_interpretation,
9555
+ "metrics": {
9556
+ "hypoglycemia_minutes": hypos * 5,
9557
+ "hyperglycemia_minutes": hypers * 5
9558
+ }
9559
+ }
9560
+
9561
+ output_json.parent.mkdir(parents=True, exist_ok=True)
9562
+ output_json.write_text(json.dumps(xai_events, indent=2))
9563
+ console.print(f"[green]XAI Report generated and saved to {output_json}[/green]")
9564
+ console.print(f"Summary: {summary_text}")
9565
+ if hf_model:
9566
+ console.print(f"LLM Interpretation: {clinical_interpretation}")
9567
+
9568
+ except Exception as e:
9569
+ console.print(f"[bold red]Failed to generate XAI report:[/bold red] {e}")
9570
+ raise typer.Exit(code=1)
9571
+
9572
+
9368
9573
  @research_app.command(name="results-index")
9369
9574
  def research_results_index(
9370
9575
  root: Annotated[
@@ -10013,9 +10218,13 @@ def research_glucose_model_jetson_train_hf(
10013
10218
  Path,
10014
10219
  typer.Option(help="Normalized glucose training dataset CSV/Parquet built by glucose-model build-dataset."),
10015
10220
  ] = Path("models/iints-glucose-forecast-v0/dataset/glucose_training_dataset.csv"),
10016
- repo_id: Annotated[
10221
+ base_hf_repo: Annotated[
10222
+ Optional[str],
10223
+ typer.Option("--base-hf-repo", "--repo-id", help="External Hugging Face model to pull from (e.g. username/GlucoFM). If empty, pulls from target_hf_repo. --repo-id is kept as a compatibility alias."),
10224
+ ] = None,
10225
+ target_hf_repo: Annotated[
10017
10226
  Optional[str],
10018
- typer.Option("--repo-id", help="Hugging Face model repo id, e.g. username/iints-glucose-forecast-v0."),
10227
+ typer.Option("--target-hf-repo", help="Your Hugging Face model repo id to push to, e.g. username/iints-glucose-forecast-v0."),
10019
10228
  ] = "IINTS/iints-glucose-forecast-v0",
10020
10229
  local_base_dir: Annotated[
10021
10230
  Optional[Path],
@@ -10076,7 +10285,8 @@ def research_glucose_model_jetson_train_hf(
10076
10285
  from iints.research.jetson_hf_trainer import run_jetson_hf_training
10077
10286
 
10078
10287
  result = run_jetson_hf_training(
10079
- repo_id=repo_id,
10288
+ base_repo_id=base_hf_repo or target_hf_repo,
10289
+ target_repo_id=target_hf_repo,
10080
10290
  dataset=dataset,
10081
10291
  work_dir=work_dir,
10082
10292
  local_base_dir=local_base_dir,
@@ -11459,11 +11669,11 @@ def docs_algo(
11459
11669
  except Exception as e:
11460
11670
  console.print(f"[bold red]Error loading algorithm module {algo_path}: {e}[/bold red]")
11461
11671
  raise typer.Exit(code=1)
11462
-
11672
+
11463
11673
  if algorithm_instance is None:
11464
11674
  console.print(f"[bold red]Error: No subclass of InsulinAlgorithm found in {algo_path}[/bold red]")
11465
11675
  raise typer.Exit(code=1)
11466
-
11676
+
11467
11677
  # Extract Metadata
11468
11678
  metadata = algorithm_instance.get_algorithm_metadata()
11469
11679
 
@@ -11531,7 +11741,7 @@ def benchmark(
11531
11741
  if not scenarios_dir.is_dir():
11532
11742
  console.print(f"[bold red]Error: Scenarios directory '{scenarios_dir}' not found.[/bold red]")
11533
11743
  raise typer.Exit(code=1)
11534
-
11744
+
11535
11745
  # Ensure output directory exists
11536
11746
  output_dir.mkdir(parents=True, exist_ok=True)
11537
11747
 
@@ -11595,7 +11805,7 @@ def benchmark(
11595
11805
  for scenario_file in scenario_files:
11596
11806
  scenario_name = scenario_file.stem
11597
11807
  console.print(f" [bold]Scenario: {scenario_name}[/bold]")
11598
-
11808
+
11599
11809
  # Load Scenario Data (validated)
11600
11810
  try:
11601
11811
  scenario_model = load_scenario(scenario_file)
@@ -11622,7 +11832,7 @@ def benchmark(
11622
11832
  )
11623
11833
  for event in stress_events:
11624
11834
  simulator_ai.add_stress_event(event)
11625
-
11835
+
11626
11836
  try:
11627
11837
  results_df_ai, safety_report_ai = simulator_ai.run_batch(duration)
11628
11838
  metrics_ai = iints.generate_benchmark_metrics(results_df_ai)
@@ -11637,7 +11847,7 @@ def benchmark(
11637
11847
  console.print(f" Running [yellow]Standard Pump[/yellow]...") # Use name directly
11638
11848
  # The standard pump also needs patient-specific parameters for ISF, ICR, basal rate, etc.
11639
11849
  # We'll pass the patient_params directly to the StandardPumpAlgorithm constructor.
11640
- standard_pump_algo_instance = iints.StandardPumpAlgorithm(settings=patient_params)
11850
+ standard_pump_algo_instance = iints.StandardPumpAlgorithm(settings=patient_params)
11641
11851
  patient_model_std = iints.PatientModel(**patient_params) # New instance for each run
11642
11852
  simulator_std = iints.Simulator(
11643
11853
  patient_model=patient_model_std,
@@ -11647,7 +11857,7 @@ def benchmark(
11647
11857
  )
11648
11858
  for event in stress_events:
11649
11859
  simulator_std.add_stress_event(event)
11650
-
11860
+
11651
11861
  try:
11652
11862
  results_df_std, safety_report_std = simulator_std.run_batch(duration)
11653
11863
  metrics_std = iints.generate_benchmark_metrics(results_df_std)
@@ -11673,19 +11883,19 @@ def benchmark(
11673
11883
  **{f"Std Safety Violations": safety_report_std.get('total_violations', float('nan'))},
11674
11884
  })
11675
11885
  run_index += 1
11676
-
11886
+
11677
11887
  console.print("\n[bold green]Benchmark Suite Completed![/bold green]")
11678
11888
 
11679
11889
  # Print Comparison Table
11680
11890
  if benchmark_results:
11681
11891
  results_df = pd.DataFrame(benchmark_results)
11682
-
11892
+
11683
11893
  table = Table(title="IINTS-AF Benchmark Results", show_header=True, header_style="bold magenta")
11684
-
11894
+
11685
11895
  # Add columns dynamically
11686
11896
  table.add_column("Patient", style="cyan", no_wrap=True)
11687
11897
  table.add_column("Scenario", style="cyan", no_wrap=True)
11688
-
11898
+
11689
11899
  # Assuming AI Algo and Standard Algo names are consistent across results
11690
11900
  ai_algo_name = benchmark_results[0]["AI Algo"]
11691
11901
  std_algo_name = benchmark_results[0]["Standard Algo"]
@@ -11697,7 +11907,7 @@ def benchmark(
11697
11907
  for metric_name_raw in sample_metrics_keys_raw:
11698
11908
  table.add_column(f"{ai_algo_name} {metric_name_raw}", style="green")
11699
11909
  table.add_column(f"{std_algo_name} {metric_name_raw}", style="yellow")
11700
-
11910
+
11701
11911
  table.add_column(f"{ai_algo_name} Safety Violations", style="red")
11702
11912
  table.add_column(f"{std_algo_name} Safety Violations", style="red")
11703
11913
 
@@ -11706,13 +11916,13 @@ def benchmark(
11706
11916
  for metric_name_raw in sample_metrics_keys_raw:
11707
11917
  ai_val = row[f'AI {metric_name_raw}']
11708
11918
  std_val = row[f'Std {metric_name_raw}']
11709
-
11919
+
11710
11920
  ai_formatted = f"{ai_val:.2f}%" if "%" in metric_name_raw and not pd.isna(ai_val) else (f"{ai_val:.2f}" if not pd.isna(ai_val) else "N/A")
11711
11921
  std_formatted = f"{std_val:.2f}%" if "%" in metric_name_raw and not pd.isna(std_val) else (f"{std_val:.2f}" if not pd.isna(std_val) else "N/A")
11712
-
11922
+
11713
11923
  row_data.append(ai_formatted)
11714
11924
  row_data.append(std_formatted)
11715
-
11925
+
11716
11926
  ai_safety_violations = row['AI Safety Violations']
11717
11927
  std_safety_violations = row['Std Safety Violations']
11718
11928
  row_data.append(f"{ai_safety_violations:.0f}" if not pd.isna(ai_safety_violations) else "N/A")
@@ -14034,3 +14244,88 @@ def edge_benchmark_alias(
14034
14244
  api_port=api_port,
14035
14245
  seed=seed,
14036
14246
  )
14247
+
14248
+ @app.command(name="render-molecules")
14249
+ def render_molecules(
14250
+ target: Annotated[str, typer.Option("--target", help="The structural target to render (e.g. insulin-mutation, glucagon, glut4, insulin-receptor, or all)")] = "all",
14251
+ ) -> None:
14252
+ """
14253
+ Render explanatory 3D structural-biology targets using AlphaFold and PyMOL.
14254
+
14255
+ The generated structures are research/education artifacts. They help connect
14256
+ SDK model terms such as insulin, glucagon, GLUT4, and receptor biology to
14257
+ public protein-structure data, but they are not simulator inputs or clinical
14258
+ evidence.
14259
+ """
14260
+ from iints.research.structure import render_target
14261
+ render_target(target)
14262
+
14263
+ @app.command(name="render-pathways")
14264
+ def render_pathways(
14265
+ network: Annotated[str, typer.Option("--network", help="The physiological network to render (e.g. insulin-cascade, glucagon-rescue, or all)")] = "all",
14266
+ ) -> None:
14267
+ """
14268
+ Download explanatory physiological pathway images from the STRING Database.
14269
+
14270
+ The rendered protein-interaction networks help users see how model concepts
14271
+ such as insulin signalling, GLUT4 translocation, and glucagon rescue relate
14272
+ to known biological pathways. They do not calibrate or replace the simulator.
14273
+ """
14274
+ from iints.research.physiology import render_pathways as fetch_networks
14275
+ fetch_networks(network)
14276
+
14277
+ @app.command(name="render-pae")
14278
+ def render_pae(
14279
+ target: Annotated[str, typer.Option("--target", help="The structural target to render the PAE matrix for (e.g. insulin-mutation, glucagon, glut4, insulin-receptor, or all)")] = "all",
14280
+ ) -> None:
14281
+ """
14282
+ Render interactive Predicted Aligned Error (PAE) matrices from AlphaFold.
14283
+
14284
+ The Plotly heatmaps show AlphaFold's predicted relative-position uncertainty
14285
+ between residues. PAE is useful for structural context only; it is not a
14286
+ glucose, dosing, safety-supervisor, or treatment metric.
14287
+ """
14288
+ from iints.research.structure import render_pae as fetch_pae
14289
+ fetch_pae(target)
14290
+
14291
+ @app.command(name="simulate-mutation")
14292
+ def simulate_mutation(
14293
+ gene: Annotated[str, typer.Option("--gene", help="The gene to simulate a mutation for (e.g. INSR or INS)")] = "INSR",
14294
+ ) -> None:
14295
+ """
14296
+ Fetch ClinVar variant summaries and show deterministic stress-test mappings.
14297
+
14298
+ The SDK currently includes curated educational mappings for genes such as
14299
+ INSR and INS. These mappings create virtual-patient edge cases for research;
14300
+ they are not diagnostic genetics or patient-specific interpretation.
14301
+ """
14302
+ from iints.research.genetics import simulate_mutation as run_sim
14303
+ run_sim(gene)
14304
+
14305
+ @app.command(name="analyze-insulin")
14306
+ def analyze_insulin(
14307
+ drug: Annotated[str, typer.Option("--drug", help="The insulin analog to analyze (e.g. lispro, glargine, regular)")] = "lispro",
14308
+ ) -> None:
14309
+ """
14310
+ Fetch ChEMBL molecule context and show deterministic insulin PK mappings.
14311
+
14312
+ ChEMBL provides public pharmacology context. The simulator absorption values
14313
+ are fixed SDK defaults for common insulin classes, not patient-specific
14314
+ dosing guidance and not AI-generated at runtime.
14315
+ """
14316
+ from iints.research.pharmacology import analyze_insulin as run_analysis
14317
+ run_analysis(drug)
14318
+
14319
+ @app.command(name="render-expression")
14320
+ def render_expression(
14321
+ gene: Annotated[str, typer.Option("--gene", help="The gene to map anatomically (e.g. GLUT4 or GCGR)")] = "GLUT4",
14322
+ ) -> None:
14323
+ """
14324
+ Fetch GTEx tissue expression data and render an interactive anatomy chart.
14325
+
14326
+ The chart helps explain why genes such as SLC2A4/GLUT4 matter for muscle
14327
+ glucose uptake and exercise modelling. It is supporting biology context, not
14328
+ automatic patient calibration.
14329
+ """
14330
+ from iints.research.anatomy import render_expression as fetch_expression
14331
+ fetch_expression(gene)
@@ -98,12 +98,18 @@ class HovorkaPatientModel:
98
98
  dawn_phenomenon_strength: float = 0.0,
99
99
  dawn_start_hour: float = 4.0,
100
100
  dawn_end_hour: float = 8.0,
101
+ molecular_affinity_scalar: float = 1.0,
102
+ muscle_sensitivity_scalar: float = 1.0,
103
+ liver_sensitivity_scalar: float = 1.0,
101
104
  carb_absorption_duration_minutes: float = 240.0,
102
105
  max_glucose_rate_mgdl_per_min: float = 3.0,
103
106
  hovorka_params: Optional[HovorkaParameters] = None,
104
107
  ) -> None:
105
108
  self.basal_insulin_rate = basal_insulin_rate
106
- self.insulin_sensitivity = insulin_sensitivity
109
+ self.molecular_affinity_scalar = molecular_affinity_scalar
110
+ self.muscle_sensitivity_scalar = muscle_sensitivity_scalar
111
+ self.liver_sensitivity_scalar = liver_sensitivity_scalar
112
+ self.insulin_sensitivity = insulin_sensitivity * molecular_affinity_scalar
107
113
  self.carb_factor = carb_factor
108
114
  self.initial_glucose = initial_glucose
109
115
  self.basal_glucose_target = basal_glucose_target
@@ -152,8 +158,8 @@ class HovorkaPatientModel:
152
158
 
153
159
  I_basal = 10.0 # mU/L approximation.
154
160
  x1_init = p.S_IT * I_basal
155
- x2_init = p.S_ID * I_basal
156
- x3_init = p.S_IE * I_basal
161
+ x2_init = p.S_ID * self.muscle_sensitivity_scalar * I_basal
162
+ x3_init = p.S_IE * self.liver_sensitivity_scalar * I_basal
157
163
 
158
164
  # State vector: [Q1, Q2, S1, S2, I, x1, x2, x3, D1, D2, D3, H_stress, H_exercise, Y1, Y2, Gamma, x_gluc, HAAF, GLUT4_active]
159
165
  return np.array(
@@ -491,8 +497,8 @@ class HovorkaPatientModel:
491
497
  overall_sens = stress_sens_multiplier * ex_sens_multiplier
492
498
 
493
499
  k_b1 = p.S_IT * p.k_a1 * overall_sens
494
- k_b2 = p.S_ID * p.k_a2 * overall_sens
495
- k_b3 = p.S_IE * p.k_a3 * overall_sens
500
+ k_b2 = p.S_ID * p.k_a2 * overall_sens * self.muscle_sensitivity_scalar
501
+ k_b3 = p.S_IE * p.k_a3 * overall_sens * self.liver_sensitivity_scalar
496
502
 
497
503
  # Insulin action
498
504
  dx1_dt = -p.k_a1 * x1 + k_b1 * I
@@ -24,7 +24,8 @@ class CustomPatientModel:
24
24
  dawn_start_hour: float = 4.0,
25
25
  dawn_end_hour: float = 8.0,
26
26
  carb_absorption_duration_minutes: float = 240.0,
27
- max_glucose_rate_mgdl_per_min: float = 3.0):
27
+ max_glucose_rate_mgdl_per_min: float = 3.0,
28
+ molecular_affinity_scalar: float = 1.0):
28
29
  """
29
30
  Initializes the patient model with simplified parameters.
30
31
 
@@ -39,9 +40,11 @@ class CustomPatientModel:
39
40
  insulin_peak_time (float): Time to peak insulin activity in minutes.
40
41
  meal_mismatch_epsilon (float): The multiplier for carb intake to simulate meal size errors.
41
42
  `true_carbs = announced_carbs * meal_mismatch_epsilon`. Defaults to 1.0.
43
+ molecular_affinity_scalar (float): Multiplier representing molecular binding affinity (1.0 = normal, 0.2 = 80% loss of function).
42
44
  """
43
45
  self.basal_insulin_rate = basal_insulin_rate
44
- self.insulin_sensitivity = insulin_sensitivity
46
+ self.molecular_affinity_scalar = molecular_affinity_scalar
47
+ self.insulin_sensitivity = insulin_sensitivity * molecular_affinity_scalar
45
48
  self.carb_factor = carb_factor
46
49
  self.glucose_decay_rate = glucose_decay_rate
47
50
  self.glucose_absorption_rate = glucose_absorption_rate
iints/core/simulator.py CHANGED
@@ -724,12 +724,13 @@ class Simulator:
724
724
  """Alias for run_batch to ensure backward compatibility."""
725
725
  return self.run_batch(duration_minutes)
726
726
 
727
- def run_batch(self, duration_minutes: int) -> Tuple[pd.DataFrame, Dict[str, Any]]:
727
+ def run_batch(self, duration_minutes: int, step_callback: Optional[Callable[[int, int, float], None]] = None) -> Tuple[pd.DataFrame, Dict[str, Any]]:
728
728
  """
729
729
  Runs the entire simulation and returns the results as a single DataFrame.
730
730
 
731
731
  Args:
732
732
  duration_minutes (int): Total simulation duration in minutes.
733
+ step_callback (Callable): Optional callback for live telemetry (time, duration, glucose).
733
734
 
734
735
  Returns:
735
736
  pd.DataFrame: A DataFrame containing the complete simulation results.
@@ -740,6 +741,8 @@ class Simulator:
740
741
  try:
741
742
  for record in self.run_live(duration_minutes):
742
743
  all_records.append(record)
744
+ if step_callback:
745
+ step_callback(record["time"], duration_minutes, record["glucose"])
743
746
  except SimulationLimitError as err:
744
747
  logger.error("Simulation terminated early: %s", err)
745
748
  self._termination_info = {