Source code for jupedsim_scenarios.cli

"""Command-line entry point for jupedsim-scenarios.

    jps-scenarios run scenario.json --seed 42 --out trajectory.sqlite
    jps-scenarios run scenario.zip --out trajectory.sqlite
    jps-scenarios run scenario_dir/ --out trajectory.sqlite

Accepts the same inputs as ``load_scenario`` — single self-contained
JSON, ZIP archive, or a directory holding ``*.json`` + ``*.wkt``.
Designed for CI smoke tests and scripted pipelines. Notebook use should
go through the Python API (``run_scenario`` / ``run_sweep``).
"""

from __future__ import annotations

import argparse
import json
import pathlib
import sys

try:
    from importlib.metadata import version as _pkg_version

    _VERSION = _pkg_version("jupedsim-scenarios")
except Exception:  # pragma: no cover - importlib.metadata failure is benign for --version
    _VERSION = "0.0.0"

from .runner import load_scenario, run_scenario


def _cmd_run(args: argparse.Namespace) -> int:
    scenario_path = pathlib.Path(args.scenario).resolve()
    if not scenario_path.exists():
        print(f"error: scenario path not found: {scenario_path}", file=sys.stderr)
        return 2

    try:
        scenario = load_scenario(str(scenario_path))
    except (ValueError, OSError) as exc:
        print(f"error: {exc}", file=sys.stderr)
        return 2

    try:
        result = run_scenario(
            scenario,
            seed=args.seed,
            dt=args.dt,
            every_nth_frame=args.every_nth_frame,
            output_path=args.out,
        )
    # Either invalid args (ValueError) or filesystem trouble writing the
    # trajectory (OSError / PermissionError on parent.mkdir / sqlite open)
    # surface as a friendly exit-2 instead of a traceback.
    except (ValueError, OSError) as exc:
        print(f"error: {exc}", file=sys.stderr)
        return 2

    # Keep the sqlite only when --out was given AND the run succeeded.
    # On the failure path with --out, clean up so we don't leave a
    # partial / misleading trajectory at a known location.
    keep_sqlite = args.out is not None and result.success
    try:
        if not result.success:
            print(
                f"error: simulation failed: {result.metrics.get('message', 'unknown')}",
                file=sys.stderr,
            )
            return 1

        summary = {
            "scenario": str(scenario_path),
            "seed": result.seed,
            "model_type": scenario.model_type,
            "evacuation_time": result.evacuation_time,
            "total_agents": result.total_agents,
            "agents_evacuated": result.agents_evacuated,
            "agents_remaining": result.agents_remaining,
            # Only report the sqlite path when we're actually keeping the file
            # (i.e. --out was given). Otherwise it's about to be unlinked.
            "sqlite_file": result.sqlite_file if keep_sqlite else None,
        }
        # Single-line JSON so callers (CI, scripts) can grep the last line of
        # stdout without colliding with the simulation engine's DEBUG prints.
        print(json.dumps(summary))
        return 0
    finally:
        if not keep_sqlite:
            result.cleanup()


[docs] def build_parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser( prog="jps-scenarios", description="Run JuPedSim scenarios authored in the web app.", ) parser.add_argument("--version", action="version", version=f"%(prog)s {_VERSION}") sub = parser.add_subparsers(dest="command", required=True) run = sub.add_parser("run", help="Run a single scenario and emit a trajectory sqlite.") run.add_argument( "scenario", help="Scenario source: a self-contained JSON file, a ZIP archive, " "or a directory holding one JSON + one WKT.", ) run.add_argument( "--seed", type=int, default=None, help="Override the scenario's seed (default: use the value in the JSON).", ) run.add_argument( "--out", default=None, help="Where to write the trajectory sqlite. If omitted, the file is " "created in a tempdir and deleted on exit (metrics are still printed).", ) run.add_argument( "--dt", type=float, default=None, help="Iteration step in seconds (default: jupedsim's built-in, " "currently 0.01).", ) run.add_argument( "--every-nth-frame", type=int, default=10, help="Trajectory writer stride. Default 10 (≈ 10 fps at dt=0.01); " "set to 1 to capture every iteration.", ) run.set_defaults(func=_cmd_run) return parser
[docs] def main(argv: list[str] | None = None) -> int: parser = build_parser() args = parser.parse_args(argv) return args.func(args)
if __name__ == "__main__": # pragma: no cover - argparse entrypoint raise SystemExit(main())