← Files JinkōARCHIVED FILE
skills/jinko-model/scripts/create_minimal_model.py
6.98 KB · Oct 5, 2026 · 18:30 UTC
#!/usr/bin/env python3
"""Create a minimal Jinkō model through high-level SDK component methods.
Dry-run by default. Pass --apply to create or edit Jinkō project items.
"""
from __future__ import annotations
import argparse
import sys
from typing import Any
try:
from dotenv import load_dotenv
except ImportError: # pragma: no cover - depends on local environment
load_dotenv = None
PREFERRED_UNIT_CHECK = "UnitCheckAndConvertAllSpeciesToExtentUnits"
def load_env() -> None:
if load_dotenv is not None:
load_dotenv()
def load_sdk():
try:
from jinko import JinkoClient
from jinko.exceptions import JinkoError
except ImportError:
print(
"Cannot import jinko. Install the SDK: pip install jinko-sdk",
file=sys.stderr,
)
return None
return JinkoClient, JinkoError
def print_plan(name: str) -> None:
print(f"Would create empty model: {name}")
print(f"Would set unitCheck: {PREFERRED_UNIT_CHECK}")
print(
"Would batch-create: compartment, parameters, categorical parameter, species, dosing event, ODE, baseline check"
)
print("Run again with --apply to create the model.")
def resolve_folder(client: Any, folder_ref: str | None, *, create: bool) -> Any | None:
if folder_ref is None:
return None
folder = client.get_folder(folder_ref)
if folder is not None:
return folder
folder = client.get_folder_by_name(folder_ref, exact_match_only=True)
if folder is not None:
return folder
if not create:
raise ValueError(
f"Folder {folder_ref!r} was not found. Pass --create-folder to create it."
)
return client.create_folder(folder_ref)
def report_diagnostics(model: Any) -> bool:
diagnostics = model.diagnostics.errors()
if not diagnostics:
return False
print("Model diagnostics contain errors:", file=sys.stderr)
for entry in diagnostics:
diagnostic = entry.diagnostic
print(
f"- {entry.component.id}: {diagnostic.code} ({diagnostic.severity}) {diagnostic.message}",
file=sys.stderr,
)
return True
def report_simple_solve(model: Any, timeseries_id: str) -> bool:
result = model.simple_solve(timeseries_ids=[timeseries_id])
if result.error:
print(f"simple_solve failed: {result.error}", file=sys.stderr)
return True
series = next((item for item in result.results if item.id == timeseries_id), None)
if series is None:
print(
f"simple_solve did not return requested event-affected series: {timeseries_id}",
file=sys.stderr,
)
return True
if len(series.values) < 2 or all(
value == series.values[0] for value in series.values[1:]
):
print(
f"Expected event-driven change was not observed in {timeseries_id}: "
f"{series.values}",
file=sys.stderr,
)
return True
return False
def main() -> int:
parser = argparse.ArgumentParser(
description="Create a minimal SDK-authored Jinkō model."
)
parser.add_argument(
"--name", default="sdk-minimal-model", help="Name for the new model."
)
parser.add_argument(
"--apply", action="store_true", help="Actually create and edit the model."
)
parser.add_argument(
"--skip-validation",
action="store_true",
help="Skip diagnostics and simple_solve after creation.",
)
parser.add_argument(
"--folder",
help="Existing folder id or exact folder name for the new model.",
)
parser.add_argument(
"--create-folder",
action="store_true",
help="Create --folder when missing. Treats --folder as a folder name.",
)
args = parser.parse_args()
if args.create_folder and not args.folder:
print("--create-folder requires --folder", file=sys.stderr)
return 1
if not args.apply:
print_plan(args.name)
if args.folder:
action = "Would create or reuse" if args.create_folder else "Would reuse"
print(f"{action} folder: {args.folder}")
return 0
load_env()
sdk = load_sdk()
if sdk is None:
return 1
JinkoClient, JinkoError = sdk
try:
client = JinkoClient()
folder = resolve_folder(client, args.folder, create=args.create_folder)
model = client.create_empty_model(
args.name,
folder=folder,
description="Minimal SDK-created model with intentional unit checking.",
)
model = model.set_unit_check(
PREFERRED_UNIT_CHECK,
version="set unit policy",
)
model = model.edit_solving_options({"extentUnits": "mol"})
with model.components.batch(version="minimal model components") as batch:
batch.create_compartment(id="central", volume=1.0, unit="L", constant=True)
batch.create_parameter(
id="Dose",
formula=1.0,
unit="mol",
constant=True,
tags=["i::protocol", "s::arbitrary"],
)
batch.create_parameter(
id="k_elim",
formula=0.1,
unit="1/h",
constant=True,
tags=["i::vpop", "s::arbitrary"],
)
batch.create_categorical_parameter(
id="route",
level="iv",
available_levels=["iv", "po"],
tags=["i::protocol"],
)
batch.create_parameter(
id="bioavailability",
formula="case route of { iv -> 1; po -> 0.5; _ -> 1 }",
unit="dimensionless",
constant=False,
)
batch.create_species(
id="Drug",
compartment="central",
initial_condition=0.0,
unit="mol",
tags=["output"],
)
batch.create_event(
id="dose_start",
updates={"Drug": "Dose * bioavailability"},
time_trigger_first_time="0 * u(h)",
record=True,
)
batch.create_ode(
id="drug_elimination",
left_side="Drug",
right_side="-k_elim * Drug",
)
batch.create_baseline_check(id="valid_dose", condition="Dose >= 0")
model = client.get_model(model.sid)
failed = False
if not args.skip_validation:
failed = report_diagnostics(model) or report_simple_solve(model, "Drug")
print(f"Created model {model.sid}")
if folder is not None:
print(f"Folder: {folder.path}")
if getattr(model, "url", None):
print(model.url)
return 1 if failed else 0
except (ValueError, JinkoError) as exc:
print(f"Jinkō SDK request failed: {exc}", file=sys.stderr)
return 2
if __name__ == "__main__":
raise SystemExit(main())
SHA-256: 5487f463d09cac1a7707862bf1491edb8c784ba0d87a3aae9710a99d9011a622