iints-sdk-python35 1.5.30__py3-none-any.whl → 1.5.31__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 (27) hide show
  1. iints/__init__.py +1 -1
  2. iints/cli/cli.py +534 -0
  3. iints/core/formula_registry.py +115 -0
  4. iints/presets/evidence_sources.yaml +73 -1
  5. iints/research/__init__.py +57 -0
  6. iints/research/academic_bundle.py +525 -0
  7. iints/research/binding_evidence.py +256 -0
  8. iints/research/cellml_models.py +338 -0
  9. iints/research/clinvar_engine.py +226 -0
  10. iints/research/copasi_models.py +403 -0
  11. iints/research/external_models_common.py +171 -0
  12. iints/research/fmi_models.py +535 -0
  13. iints/research/genomics_engine.py +57 -25
  14. iints/research/mechanistic_models.py +740 -0
  15. iints_desktop/evidence_connectors.py +215 -4
  16. iints_desktop/launcher.py +1 -1
  17. iints_desktop/qt_app.py +272 -6
  18. iints_desktop/tauri_bridge.py +242 -1
  19. iints_desktop/update.py +1 -1
  20. {iints_sdk_python35-1.5.30.dist-info → iints_sdk_python35-1.5.31.dist-info}/METADATA +31 -3
  21. {iints_sdk_python35-1.5.30.dist-info → iints_sdk_python35-1.5.31.dist-info}/RECORD +27 -19
  22. {iints_sdk_python35-1.5.30.dist-info → iints_sdk_python35-1.5.31.dist-info}/WHEEL +0 -0
  23. {iints_sdk_python35-1.5.30.dist-info → iints_sdk_python35-1.5.31.dist-info}/entry_points.txt +0 -0
  24. {iints_sdk_python35-1.5.30.dist-info → iints_sdk_python35-1.5.31.dist-info}/licenses/LICENSE +0 -0
  25. {iints_sdk_python35-1.5.30.dist-info → iints_sdk_python35-1.5.31.dist-info}/licenses/LICENSE-MIT-IINTS-LEGACY +0 -0
  26. {iints_sdk_python35-1.5.30.dist-info → iints_sdk_python35-1.5.31.dist-info}/licenses/NOTICE +0 -0
  27. {iints_sdk_python35-1.5.30.dist-info → iints_sdk_python35-1.5.31.dist-info}/top_level.txt +0 -0
iints/__init__.py CHANGED
@@ -11,7 +11,7 @@ except ImportError: # pragma: no cover - Python < 3.8 fallback
11
11
  try:
12
12
  __version__ = version("iints-sdk-python35")
13
13
  except PackageNotFoundError: # pragma: no cover - source tree fallback
14
- __version__ = "1.5.30"
14
+ __version__ = "1.5.31"
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
@@ -8670,6 +8670,26 @@ glucose_model_app = typer.Typer(
8670
8670
  help="Dedicated glucose forecasting model workflow for training and Hugging Face export."
8671
8671
  )
8672
8672
  research_app.add_typer(glucose_model_app, name="glucose-model")
8673
+ mechanistic_model_app = typer.Typer(
8674
+ help="Inspect and independently execute local SBML reference models."
8675
+ )
8676
+ research_app.add_typer(mechanistic_model_app, name="mechanistic")
8677
+ copasi_model_app = typer.Typer(
8678
+ help="Inspect COPASI tasks and explicitly run configured sensitivity/parameter analyses."
8679
+ )
8680
+ research_app.add_typer(copasi_model_app, name="copasi")
8681
+ cellml_model_app = typer.Typer(
8682
+ help="Inspect local CellML models and validate them independently with OpenCOR."
8683
+ )
8684
+ research_app.add_typer(cellml_model_app, name="cellml")
8685
+ fmi_model_app = typer.Typer(
8686
+ help="Inspect FMUs safely and explicitly execute trusted device-physics models with FMPy."
8687
+ )
8688
+ research_app.add_typer(fmi_model_app, name="fmi")
8689
+ binding_evidence_app = typer.Typer(
8690
+ help="Retrieve measured BindingDB affinity evidence without changing patient parameters."
8691
+ )
8692
+ research_app.add_typer(binding_evidence_app, name="binding")
8673
8693
 
8674
8694
 
8675
8695
  @research_app.command(name="prepare-azt1d")
@@ -9621,6 +9641,520 @@ def research_results_index(
9621
9641
  _print_results_index_bundle(bundle, console)
9622
9642
 
9623
9643
 
9644
+ @research_app.command(name="academic-bundle")
9645
+ def research_academic_bundle(
9646
+ run_dir: Annotated[
9647
+ Path,
9648
+ typer.Argument(help="Completed IINTS run directory to describe as an RO-Crate."),
9649
+ ],
9650
+ title: Annotated[Optional[str], typer.Option(help="Human-readable experiment title.")] = None,
9651
+ description: Annotated[Optional[str], typer.Option(help="Short research question or experiment description.")] = None,
9652
+ creator: Annotated[Optional[str], typer.Option(help="Researcher name recorded in the crate.")] = None,
9653
+ orcid: Annotated[
9654
+ Optional[str],
9655
+ typer.Option(help="Canonical ORCID URL, for example https://orcid.org/0000-0002-1825-0097."),
9656
+ ] = None,
9657
+ license_id: Annotated[
9658
+ str,
9659
+ typer.Option(
9660
+ "--license",
9661
+ help="SPDX license for run artifacts; defaults to NOASSERTION until the researcher chooses one.",
9662
+ ),
9663
+ ] = "NOASSERTION",
9664
+ source_id: Annotated[
9665
+ List[str],
9666
+ typer.Option("--source-id", help="Repeatable evidence source ID from `iints sources`."),
9667
+ ] = [],
9668
+ ) -> None:
9669
+ """Add RO-Crate metadata, checksums, sources, and an academic audit to a run."""
9670
+
9671
+ console = Console()
9672
+ from iints.research.academic_bundle import build_academic_bundle
9673
+
9674
+ try:
9675
+ bundle = build_academic_bundle(
9676
+ run_dir,
9677
+ title=title,
9678
+ description=description,
9679
+ creator_name=creator,
9680
+ creator_orcid=orcid,
9681
+ license_id=license_id,
9682
+ source_ids=source_id,
9683
+ )
9684
+ except Exception as exc:
9685
+ console.print(f"[bold red]Academic bundle export failed:[/bold red] {exc}")
9686
+ raise typer.Exit(code=1)
9687
+
9688
+ status_style = "green" if bundle.readiness_status == "ready" else "yellow"
9689
+ table = Table(title="IINTS Academic Research Bundle")
9690
+ table.add_column("Field", style="cyan")
9691
+ table.add_column("Value", overflow="fold")
9692
+ table.add_row("Readiness", f"[{status_style}]{bundle.readiness_status}[/{status_style}]")
9693
+ table.add_row("Audit score", f"{bundle.readiness_score_pct:.2f}%")
9694
+ table.add_row("Artifacts hashed", str(bundle.artifact_count))
9695
+ table.add_row("Sources associated", str(bundle.source_count))
9696
+ table.add_row("RO-Crate", str(bundle.ro_crate_metadata))
9697
+ table.add_row("Academic audit", str(bundle.audit_json))
9698
+ table.add_row("Source snapshot", str(bundle.sources_json))
9699
+ table.add_row("Reviewer guide", str(bundle.readme_md))
9700
+ console.print(table)
9701
+ console.print(
9702
+ "[yellow]Scope:[/yellow] FAIR-oriented metadata and reproducibility checks do not constitute "
9703
+ "peer review, clinical validation, or medical-device certification."
9704
+ )
9705
+
9706
+
9707
+ @mechanistic_model_app.command(name="inspect")
9708
+ def research_mechanistic_inspect(
9709
+ model: Annotated[Path, typer.Argument(help="Local SBML .xml or .sbml model file.")],
9710
+ output_json: Annotated[
9711
+ Optional[Path],
9712
+ typer.Option(help="Optional path for the machine-readable structural inspection."),
9713
+ ] = None,
9714
+ ) -> None:
9715
+ """Inspect SBML structure and units without executing model equations."""
9716
+
9717
+ from iints.research.mechanistic_models import inspect_sbml_model, sbml_summary_payload
9718
+
9719
+ console = Console()
9720
+ try:
9721
+ summary = inspect_sbml_model(model)
9722
+ except Exception as exc:
9723
+ console.print(f"[bold red]SBML inspection failed:[/bold red] {exc}")
9724
+ raise typer.Exit(code=1)
9725
+
9726
+ table = Table(title="IINTS Mechanistic Reference Model")
9727
+ table.add_column("Field", style="cyan")
9728
+ table.add_column("Value", overflow="fold")
9729
+ table.add_row("Model", summary.model_name or summary.model_id or summary.model_path.name)
9730
+ table.add_row("SBML", f"Level {summary.sbml_level}, version {summary.sbml_version}")
9731
+ table.add_row("Readiness", summary.readiness_status)
9732
+ table.add_row("SHA-256", summary.sha256)
9733
+ for key, value in summary.counts.items():
9734
+ table.add_row(key.replace("_", " ").title(), str(value))
9735
+ table.add_row("Declared model units", json.dumps(summary.model_units, sort_keys=True))
9736
+ table.add_row("Warnings", "\n".join(summary.warnings) if summary.warnings else "None")
9737
+ console.print(table)
9738
+
9739
+ if output_json is not None:
9740
+ output_json = output_json.expanduser().resolve()
9741
+ output_json.parent.mkdir(parents=True, exist_ok=True)
9742
+ output_json.write_text(
9743
+ json.dumps(sbml_summary_payload(summary), indent=2, sort_keys=True) + "\n",
9744
+ encoding="utf-8",
9745
+ )
9746
+ console.print(f"[green]Inspection written to:[/green] {output_json}")
9747
+ console.print(
9748
+ "[yellow]Scope:[/yellow] Structural inspection is not SBML schema validation, biological validation, "
9749
+ "or permission to map model quantities to IINTS units automatically."
9750
+ )
9751
+
9752
+
9753
+ @mechanistic_model_app.command(name="run")
9754
+ def research_mechanistic_run(
9755
+ model: Annotated[Path, typer.Argument(help="Local SBML .xml or .sbml model file.")],
9756
+ output_dir: Annotated[
9757
+ Path,
9758
+ typer.Option(help="Root directory for the isolated reference-model run."),
9759
+ ] = Path("results/mechanistic_reference"),
9760
+ start: Annotated[float, typer.Option(help="Start in the model's declared time units.")] = 0.0,
9761
+ end: Annotated[float, typer.Option(help="End in the model's declared time units.")] = 1440.0,
9762
+ points: Annotated[int, typer.Option(help="Number of sampled points including endpoints.")] = 289,
9763
+ variable: Annotated[
9764
+ List[str],
9765
+ typer.Option(
9766
+ "--variable",
9767
+ help=(
9768
+ "Repeatable species/global parameter ID. Use [X] or concentration:X for concentration and "
9769
+ "amount:X for amount; defaults follow hasOnlySubstanceUnits."
9770
+ ),
9771
+ ),
9772
+ ] = [],
9773
+ source_url: Annotated[
9774
+ Optional[str],
9775
+ typer.Option(help="Optional HTTPS provenance URL for the exact model source."),
9776
+ ] = None,
9777
+ model_license: Annotated[
9778
+ str,
9779
+ typer.Option(help="Model-artifact license; use NOASSERTION when it has not been verified."),
9780
+ ] = "NOASSERTION",
9781
+ ) -> None:
9782
+ """Execute a local SBML model independently through libRoadRunner."""
9783
+
9784
+ from iints.research.mechanistic_models import run_sbml_model
9785
+
9786
+ console = Console()
9787
+ try:
9788
+ result = run_sbml_model(
9789
+ model,
9790
+ output_dir,
9791
+ start=start,
9792
+ end=end,
9793
+ points=points,
9794
+ variables=variable,
9795
+ source_url=source_url,
9796
+ model_license=model_license,
9797
+ )
9798
+ except Exception as exc:
9799
+ console.print(f"[bold red]Mechanistic reference run failed:[/bold red] {exc}")
9800
+ raise typer.Exit(code=1)
9801
+
9802
+ table = Table(title="IINTS Mechanistic Reference Run")
9803
+ table.add_column("Field", style="cyan")
9804
+ table.add_column("Value", overflow="fold")
9805
+ table.add_row("Engine", f"{result.engine} {result.engine_version}")
9806
+ table.add_row("Rows", str(result.row_count))
9807
+ table.add_row("Selections", ", ".join(result.selections))
9808
+ table.add_row("Run directory", str(result.run_dir))
9809
+ table.add_row("Results", str(result.results_csv))
9810
+ table.add_row("Manifest", str(result.manifest_json))
9811
+ table.add_row("Review report", str(result.report_md))
9812
+ console.print(table)
9813
+ console.print(
9814
+ "[yellow]Research only:[/yellow] execution success is not biological validation and outputs are not "
9815
+ "converted to glucose or insulin units automatically."
9816
+ )
9817
+
9818
+
9819
+ @copasi_model_app.command(name="status")
9820
+ def research_copasi_status() -> None:
9821
+ """Show whether the CopasiSE batch engine is available."""
9822
+
9823
+ from iints.research.copasi_models import copasi_status
9824
+
9825
+ console = Console()
9826
+ payload = copasi_status()
9827
+ style = "green" if payload["available"] else "yellow"
9828
+ table = Table(title="IINTS COPASI Engine")
9829
+ table.add_column("Field", style="cyan")
9830
+ table.add_column("Value", overflow="fold")
9831
+ table.add_row("Available", f"[{style}]{payload['available']}[/{style}]")
9832
+ table.add_row("Path", str(payload["path"] or "not found"))
9833
+ table.add_row("Version", str(payload["version_hint"] or "unknown"))
9834
+ table.add_row("Message", str(payload["message"]))
9835
+ console.print(table)
9836
+
9837
+
9838
+ @copasi_model_app.command(name="inspect")
9839
+ def research_copasi_inspect(
9840
+ model: Annotated[Path, typer.Argument(help="Local COPASI .cps model file.")],
9841
+ output_json: Annotated[Optional[Path], typer.Option(help="Optional structural inspection JSON.")] = None,
9842
+ ) -> None:
9843
+ """Inspect configured COPASI tasks without executing them."""
9844
+
9845
+ from iints.research.copasi_models import copasi_summary_payload, inspect_copasi_model
9846
+
9847
+ console = Console()
9848
+ try:
9849
+ summary = inspect_copasi_model(model)
9850
+ except Exception as exc:
9851
+ console.print(f"[bold red]COPASI inspection failed:[/bold red] {exc}")
9852
+ raise typer.Exit(code=1)
9853
+ table = Table(title="IINTS COPASI Model Inspection")
9854
+ table.add_column("Field", style="cyan")
9855
+ table.add_column("Value", overflow="fold")
9856
+ table.add_row("Model", summary.model_name)
9857
+ table.add_row("Readiness", summary.readiness_status)
9858
+ table.add_row("SHA-256", summary.sha256)
9859
+ table.add_row("Tasks", str(len(summary.tasks)))
9860
+ table.add_row("Scheduled", str(summary.scheduled_task_count))
9861
+ table.add_row("Sensitivity tasks", str(summary.sensitivity_task_count))
9862
+ table.add_row("Parameter estimation", str(summary.parameter_estimation_task_count))
9863
+ table.add_row("Warnings", "\n".join(summary.warnings) if summary.warnings else "None")
9864
+ console.print(table)
9865
+ if output_json is not None:
9866
+ target = output_json.expanduser().resolve()
9867
+ target.parent.mkdir(parents=True, exist_ok=True)
9868
+ target.write_text(
9869
+ json.dumps(copasi_summary_payload(summary), indent=2, sort_keys=True) + "\n",
9870
+ encoding="utf-8",
9871
+ )
9872
+ console.print(f"[green]Inspection written to:[/green] {target}")
9873
+ console.print(
9874
+ "[yellow]Scope:[/yellow] task presence is not parameter identifiability, convergence, or validation."
9875
+ )
9876
+
9877
+
9878
+ @copasi_model_app.command(name="run")
9879
+ def research_copasi_run(
9880
+ model: Annotated[Path, typer.Argument(help="Reviewed local COPASI .cps model file.")],
9881
+ output_dir: Annotated[Path, typer.Option(help="Evidence output root.")] = Path("results/copasi"),
9882
+ task: Annotated[Optional[str], typer.Option(help="Optional exact COPASI task-name override.")] = None,
9883
+ timeout_seconds: Annotated[int, typer.Option(help="CopasiSE wall-time limit in seconds.")] = 900,
9884
+ allow_external_execution: Annotated[
9885
+ bool,
9886
+ typer.Option(
9887
+ "--allow-external-execution",
9888
+ help="Confirm that the configured COPASI tasks and external file references were reviewed.",
9889
+ ),
9890
+ ] = False,
9891
+ ) -> None:
9892
+ """Run an already configured sensitivity or parameter-analysis task."""
9893
+
9894
+ from iints.research.copasi_models import run_copasi_model
9895
+
9896
+ console = Console()
9897
+ try:
9898
+ result = run_copasi_model(
9899
+ model,
9900
+ output_dir,
9901
+ scheduled_task=task,
9902
+ timeout_seconds=timeout_seconds,
9903
+ allow_external_execution=allow_external_execution,
9904
+ )
9905
+ except Exception as exc:
9906
+ console.print(f"[bold red]COPASI run failed:[/bold red] {exc}")
9907
+ raise typer.Exit(code=1)
9908
+ table = Table(title="IINTS COPASI Analysis")
9909
+ table.add_column("Field", style="cyan")
9910
+ table.add_column("Value", overflow="fold")
9911
+ table.add_row("Task", result.selected_task or "task scheduled in model")
9912
+ table.add_row("Run directory", str(result.run_dir))
9913
+ table.add_row("Report", str(result.report_txt))
9914
+ table.add_row("Manifest", str(result.manifest_json))
9915
+ table.add_row("Review", str(result.review_md))
9916
+ console.print(table)
9917
+ console.print(
9918
+ "[yellow]Research only:[/yellow] a fitted value is not identifiable or transferable until independently reviewed."
9919
+ )
9920
+
9921
+
9922
+ @cellml_model_app.command(name="status")
9923
+ def research_cellml_status() -> None:
9924
+ """Show whether OpenCOR is available for CellML validation."""
9925
+
9926
+ from iints.research.cellml_models import opencor_status
9927
+
9928
+ console = Console()
9929
+ payload = opencor_status()
9930
+ style = "green" if payload["available"] else "yellow"
9931
+ table = Table(title="IINTS OpenCOR Engine")
9932
+ table.add_column("Field", style="cyan")
9933
+ table.add_column("Value", overflow="fold")
9934
+ table.add_row("Available", f"[{style}]{payload['available']}[/{style}]")
9935
+ table.add_row("Path", str(payload["path"] or "not found"))
9936
+ table.add_row("Version", str(payload["version"] or "unknown"))
9937
+ table.add_row("Message", str(payload["message"]))
9938
+ console.print(table)
9939
+
9940
+
9941
+ @cellml_model_app.command(name="inspect")
9942
+ def research_cellml_inspect(
9943
+ model: Annotated[Path, typer.Argument(help="Local .cellml or CellML .xml file.")],
9944
+ output_json: Annotated[Optional[Path], typer.Option(help="Optional structural inspection JSON.")] = None,
9945
+ ) -> None:
9946
+ """Inspect CellML metadata without resolving imports or executing equations."""
9947
+
9948
+ from iints.research.cellml_models import cellml_summary_payload, inspect_cellml_model
9949
+
9950
+ console = Console()
9951
+ try:
9952
+ summary = inspect_cellml_model(model)
9953
+ except Exception as exc:
9954
+ console.print(f"[bold red]CellML inspection failed:[/bold red] {exc}")
9955
+ raise typer.Exit(code=1)
9956
+ table = Table(title="IINTS CellML Model Inspection")
9957
+ table.add_column("Field", style="cyan")
9958
+ table.add_column("Value", overflow="fold")
9959
+ table.add_row("Model", summary.model_name)
9960
+ table.add_row("CellML", summary.cellml_version)
9961
+ table.add_row("Readiness", summary.readiness_status)
9962
+ table.add_row("SHA-256", summary.sha256)
9963
+ table.add_row("Components", str(summary.component_count))
9964
+ table.add_row("Variables", str(summary.variable_count))
9965
+ table.add_row("Imports", str(summary.import_count))
9966
+ table.add_row("Warnings", "\n".join(summary.warnings) if summary.warnings else "None")
9967
+ console.print(table)
9968
+ if output_json is not None:
9969
+ target = output_json.expanduser().resolve()
9970
+ target.parent.mkdir(parents=True, exist_ok=True)
9971
+ target.write_text(
9972
+ json.dumps(cellml_summary_payload(summary), indent=2, sort_keys=True) + "\n",
9973
+ encoding="utf-8",
9974
+ )
9975
+ console.print(f"[green]Inspection written to:[/green] {target}")
9976
+
9977
+
9978
+ @cellml_model_app.command(name="validate")
9979
+ def research_cellml_validate(
9980
+ model: Annotated[Path, typer.Argument(help="Local CellML model to validate with OpenCOR.")],
9981
+ output_dir: Annotated[Path, typer.Option(help="Evidence output root.")] = Path("results/cellml"),
9982
+ timeout_seconds: Annotated[int, typer.Option(help="OpenCOR validation timeout.")] = 120,
9983
+ ) -> None:
9984
+ """Validate CellML syntax/semantics with OpenCOR's official CLI plugin."""
9985
+
9986
+ from iints.research.cellml_models import validate_cellml_model
9987
+
9988
+ console = Console()
9989
+ try:
9990
+ result = validate_cellml_model(model, output_dir, timeout_seconds=timeout_seconds)
9991
+ except Exception as exc:
9992
+ console.print(f"[bold red]OpenCOR validation failed:[/bold red] {exc}")
9993
+ raise typer.Exit(code=1)
9994
+ style = "green" if result.valid else "red"
9995
+ table = Table(title="IINTS OpenCOR Validation")
9996
+ table.add_column("Field", style="cyan")
9997
+ table.add_column("Value", overflow="fold")
9998
+ table.add_row("Valid", f"[{style}]{result.valid}[/{style}]")
9999
+ table.add_row("Run directory", str(result.run_dir))
10000
+ table.add_row("Validation log", str(result.validation_log))
10001
+ table.add_row("Manifest", str(result.manifest_json))
10002
+ console.print(table)
10003
+ if not result.valid:
10004
+ raise typer.Exit(code=2)
10005
+
10006
+
10007
+ @fmi_model_app.command(name="status")
10008
+ def research_fmi_status() -> None:
10009
+ """Show FMPy availability; static FMU inspection is always available."""
10010
+
10011
+ from iints.research.fmi_models import fmpy_status
10012
+
10013
+ console = Console()
10014
+ payload = fmpy_status()
10015
+ style = "green" if payload["available"] else "yellow"
10016
+ table = Table(title="IINTS FMI / FMPy Engine")
10017
+ table.add_column("Field", style="cyan")
10018
+ table.add_column("Value", overflow="fold")
10019
+ table.add_row("Static inspection", "[green]available[/green]")
10020
+ table.add_row("FMPy execution", f"[{style}]{payload['available']}[/{style}]")
10021
+ table.add_row("Version", str(payload["version"] or "not installed"))
10022
+ table.add_row("Message", str(payload["message"]))
10023
+ console.print(table)
10024
+
10025
+
10026
+ @fmi_model_app.command(name="inspect")
10027
+ def research_fmi_inspect(
10028
+ model: Annotated[Path, typer.Argument(help="Local .fmu archive.")],
10029
+ output_json: Annotated[Optional[Path], typer.Option(help="Optional structural inspection JSON.")] = None,
10030
+ ) -> None:
10031
+ """Inspect FMU metadata without extracting or loading native binaries."""
10032
+
10033
+ from iints.research.fmi_models import fmu_summary_payload, inspect_fmu_model
10034
+
10035
+ console = Console()
10036
+ try:
10037
+ summary = inspect_fmu_model(model)
10038
+ except Exception as exc:
10039
+ console.print(f"[bold red]FMU inspection failed:[/bold red] {exc}")
10040
+ raise typer.Exit(code=1)
10041
+ table = Table(title="IINTS FMU Static Inspection")
10042
+ table.add_column("Field", style="cyan")
10043
+ table.add_column("Value", overflow="fold")
10044
+ table.add_row("Model", summary.model_name)
10045
+ table.add_row("FMI", summary.fmi_version)
10046
+ table.add_row("Readiness", summary.readiness_status)
10047
+ table.add_row("SHA-256", summary.sha256)
10048
+ table.add_row("Interfaces", ", ".join(row["type"] for row in summary.interfaces) or "none")
10049
+ table.add_row("Variables", str(summary.variable_count))
10050
+ table.add_row("Platforms", ", ".join(summary.platforms) or "none")
10051
+ table.add_row("Native binaries", str(summary.has_native_binaries))
10052
+ table.add_row("Warnings", "\n".join(summary.warnings) if summary.warnings else "None")
10053
+ console.print(table)
10054
+ if output_json is not None:
10055
+ target = output_json.expanduser().resolve()
10056
+ target.parent.mkdir(parents=True, exist_ok=True)
10057
+ target.write_text(
10058
+ json.dumps(fmu_summary_payload(summary), indent=2, sort_keys=True) + "\n",
10059
+ encoding="utf-8",
10060
+ )
10061
+ console.print(f"[green]Inspection written to:[/green] {target}")
10062
+ console.print(
10063
+ "[bold yellow]Security:[/bold yellow] static inspection did not load FMU native code."
10064
+ )
10065
+
10066
+
10067
+ @fmi_model_app.command(name="run")
10068
+ def research_fmi_run(
10069
+ model: Annotated[Path, typer.Argument(help="Reviewed local .fmu archive.")],
10070
+ output_dir: Annotated[Path, typer.Option(help="Evidence output root.")] = Path("results/fmi"),
10071
+ start: Annotated[float, typer.Option(help="Start in FMU model-time units.")] = 0.0,
10072
+ end: Annotated[float, typer.Option(help="End in FMU model-time units.")] = 60.0,
10073
+ output_interval: Annotated[float, typer.Option(help="Output sampling interval.")] = 0.1,
10074
+ variable: Annotated[List[str], typer.Option("--variable", help="Repeatable declared FMU variable.")] = [],
10075
+ timeout_seconds: Annotated[int, typer.Option(help="Execution timeout in seconds.")] = 300,
10076
+ trust_native_code: Annotated[
10077
+ bool,
10078
+ typer.Option(
10079
+ "--trust-native-code",
10080
+ help="Confirm that the FMU publisher, hash, binaries, and license were reviewed.",
10081
+ ),
10082
+ ] = False,
10083
+ ) -> None:
10084
+ """Execute a trusted device-physics FMU through FMPy."""
10085
+
10086
+ from iints.research.fmi_models import run_fmu_model
10087
+
10088
+ console = Console()
10089
+ try:
10090
+ result = run_fmu_model(
10091
+ model,
10092
+ output_dir,
10093
+ start=start,
10094
+ end=end,
10095
+ output_interval=output_interval,
10096
+ variables=variable,
10097
+ timeout_seconds=timeout_seconds,
10098
+ allow_native_execution=trust_native_code,
10099
+ )
10100
+ except Exception as exc:
10101
+ console.print(f"[bold red]FMI execution failed:[/bold red] {exc}")
10102
+ raise typer.Exit(code=1)
10103
+ table = Table(title="IINTS FMI Device-Physics Run")
10104
+ table.add_column("Field", style="cyan")
10105
+ table.add_column("Value", overflow="fold")
10106
+ table.add_row("Engine", f"{result.engine} {result.engine_version}")
10107
+ table.add_row("Rows", str(result.row_count))
10108
+ table.add_row("Columns", ", ".join(result.columns))
10109
+ table.add_row("Run directory", str(result.run_dir))
10110
+ table.add_row("Results", str(result.results_csv))
10111
+ table.add_row("Manifest", str(result.manifest_json))
10112
+ console.print(table)
10113
+ console.print(
10114
+ "[yellow]Research only:[/yellow] FMU execution is not bench validation and must not control a real device."
10115
+ )
10116
+
10117
+
10118
+ @binding_evidence_app.command(name="query")
10119
+ def research_binding_query(
10120
+ uniprot: Annotated[str, typer.Option(help="One reviewed UniProt accession.")] = "P06213",
10121
+ output_dir: Annotated[Path, typer.Option(help="Evidence output root.")] = Path("results/bindingdb"),
10122
+ cutoff_nm: Annotated[int, typer.Option(help="BindingDB affinity cutoff in nM.")] = 10_000,
10123
+ max_records: Annotated[int, typer.Option(help="Maximum records exported locally.")] = 5_000,
10124
+ timeout_seconds: Annotated[int, typer.Option(help="Verified-TLS request timeout.")] = 30,
10125
+ ) -> None:
10126
+ """Fetch measured affinity records for one UniProt target from BindingDB."""
10127
+
10128
+ from iints.research.binding_evidence import query_bindingdb_uniprot
10129
+
10130
+ console = Console()
10131
+ try:
10132
+ result = query_bindingdb_uniprot(
10133
+ uniprot,
10134
+ output_dir,
10135
+ cutoff_nm=cutoff_nm,
10136
+ max_records=max_records,
10137
+ timeout_seconds=timeout_seconds,
10138
+ )
10139
+ except Exception as exc:
10140
+ console.print(f"[bold red]BindingDB query failed:[/bold red] {exc}")
10141
+ raise typer.Exit(code=1)
10142
+ table = Table(title="IINTS BindingDB Affinity Evidence")
10143
+ table.add_column("Field", style="cyan")
10144
+ table.add_column("Value", overflow="fold")
10145
+ table.add_row("UniProt", result.uniprot_accession)
10146
+ table.add_row("Cutoff", f"{result.cutoff_nm} nM")
10147
+ table.add_row("Records", str(result.record_count))
10148
+ table.add_row("Truncated", str(result.truncated))
10149
+ table.add_row("CSV", str(result.records_csv))
10150
+ table.add_row("Evidence JSON", str(result.evidence_json))
10151
+ table.add_row("Review", str(result.report_md))
10152
+ console.print(table)
10153
+ console.print(
10154
+ "[yellow]Interpretation:[/yellow] Ki, Kd, IC50, and in-vivo effects are not interchangeable."
10155
+ )
10156
+
10157
+
9624
10158
  @research_app.command(name="export-onnx")
9625
10159
  def research_export_onnx(
9626
10160
  model: Annotated[Path, typer.Option(help="Predictor checkpoint (.pt)")] = Path("models/hupa_finetuned_v2/predictor.pt"),