docs/cli.md

# CLI

Entry point: `iohmm-evac` (declared in `pyproject.toml` under
`[project.scripts]`). All commands assume you're running through `uv` so the
project's locked environment is in use.

## Subcommands

```bash
iohmm-evac simulate [OPTIONS]              # run a simulation, write Parquet+TOML
iohmm-evac scenarios list                  # list registered scenarios
iohmm-evac config dump --scenario NAME     # print the resolved config as TOML
iohmm-evac fit ACTION [OPTIONS]            # fit an IO-HMM (Build 2)
iohmm-evac diagnose recovery ...           # state and parameter recovery vs truth
iohmm-evac report ACTION [OPTIONS]         # diagnostic plots (DGP, fit, sweep, bootstrap)
iohmm-evac sweep run|summary [OPTIONS]     # multi-scenario sweep + network metrics (Build 3)
iohmm-evac bootstrap fit|shift-sweep|summary [OPTIONS]  # parametric bootstrap + Fig. 6 (Build 4)
```

## Run the baseline

```bash
uv run iohmm-evac simulate \
    --scenario baseline \
    --seed 0 \
    --output ./output/baseline.parquet
```

Writes four files alongside one another:

* `./output/baseline.parquet` — long-format observations, one row per (household, t).
* `./output/baseline.population.parquet` — static covariates per household.
* `./output/baseline.timeline.parquet` — exogenous timeline.
* `./output/baseline.config.toml` — exact configuration that produced the run.

## Run all four scenarios

```bash
for s in baseline early-warning targeted-messaging contraflow; do
    uv run iohmm-evac simulate \
        --scenario "$s" \
        --seed 42 \
        --output "./output/${s}.parquet"
done
```

## Override a parameter

`--set` accepts `dotted.path=value`, repeated as needed. Coercion uses the
target field's annotated type; tuple fields parse comma-separated strings.

```bash
uv run iohmm-evac simulate \
    --scenario baseline \
    --set transitions.ua_to_aw.beta_mand=3.0 \
    --set feedback.n_cap=2000 \
    --output ./output/tuned.parquet
```

## Load a TOML config

```bash
uv run iohmm-evac config dump --scenario baseline > tuned.toml
# edit tuned.toml in your editor of choice
uv run iohmm-evac simulate \
    --config tuned.toml \
    --output ./output/tuned.parquet
```

`--config` overrides land *on top of* the scenario, and `--set` lands on top
of `--config`. Scenarios → TOML → `--set` is the precedence chain.

## Quiet / verbose

* `--quiet`: suppresses the per-output-path progress lines on stderr.
* `--verbose`: adds a one-line summary (final SH share) on stderr.

Both flags are mutually exclusive.

## Reproducibility

All randomness flows through `numpy.random.default_rng(seed)`. Two runs with
the same seed and the same resolved config produce bit-identical states,
emissions, and timelines.

## Reporting

The `report` subcommand renders diagnostic plots from a saved bundle. It
reads the four files written by `simulate` (the main observations Parquet
plus its `.population.parquet`, `.timeline.parquet`, and `.config.toml`
sidecars).

```bash
iohmm-evac report occupancy    --input PATH [--output PATH] [--show]
iohmm-evac report departures   --input PATH [--output PATH] [--show]
iohmm-evac report trajectories --input PATH [--household-ids 0,42,1337] \
                               [--output PATH] [--show]
iohmm-evac report emissions    --input PATH [--output PATH] [--show]
iohmm-evac report summary      --input PATH
iohmm-evac report all          --input PATH --output-dir DIR \
                               [--household-ids ...]
```

Render every plot at once:

```bash
uv run iohmm-evac report all \
    --input ./output/baseline.parquet \
    --output-dir ./output/figures/
```

Writes `occupancy.png`, `departures.png`, `trajectories.png`, and
`emissions.png` into the chosen directory. `--show` opens an interactive
window instead of writing a file (skip on a headless box). When neither
`--show` nor `--output` is given, single-plot subcommands write a PNG next
to the input parquet, named `<stem>.<plot>.png`.

See [`reporting.md`](reporting.md) for what each plot shows and how to read
a healthy baseline.

## Fitting an IO-HMM (Build 2)

```bash
uv run iohmm-evac fit \
    --input ./output/baseline.parquet \
    --output ./output/fit/ \
    --restarts 5 \
    --init kmeans \
    --max-iter 200 \
    --tol 1e-5 \
    --seed 0
```

Writes a four-file fit bundle into `./output/fit/`:

- `theta.toml` — best-restart parameters (initial, transitions, emissions).
- `log_likelihood_trace.parquet` — per-iteration LL for every restart.
- `posterior_states.parquet` — Viterbi MAP path under the best fit.
- `metadata.toml` — best-restart index, iteration counts, convergence flags.

`--init` accepts `random`, `kmeans`, and `truth`. The `truth` strategy
reads the DGP-side parameter table from the input bundle's `config.toml`
and seeds EM at the projected truth — used by recovery tests and by
debugging passes; not for production fits.

See [`inference.md`](inference.md) for the math.

## Recovery diagnostics

```bash
uv run iohmm-evac diagnose recovery \
    --fit ./output/fit/ \
    --truth ./output/baseline.parquet
```

Reads the fit bundle and the original simulation bundle, aligns the fit's
states to truth via the Hungarian algorithm, and writes
`./output/fit/recovery.toml` with state-recovery accuracy, the
row-normalized confusion matrix, and per-group parameter RMSEs.

Visual versions:

```bash
uv run iohmm-evac report recovery-confusion --fit ./output/fit/ \
    --truth ./output/baseline.parquet --output ./output/figures/recovery.png
uv run iohmm-evac report parameter-recovery --fit ./output/fit/ \
    --truth ./output/baseline.parquet --output ./output/figures/params.png
uv run iohmm-evac report ll-trace --fit ./output/fit/ \
    --output ./output/figures/ll_trace.png
uv run iohmm-evac report fit-summary --fit ./output/fit/
```

See [`diagnostics.md`](diagnostics.md) for what each metric and plot mean.

## Scenario sweep (Build 3)

```bash
uv run iohmm-evac sweep run \
    --output-dir ./output/sweep/ \
    --seed 0 \
    --n-households 10000 \
    --n-hours 120
```

Runs every predefined scenario under a common seed and writes one
sub-directory per scenario plus a top-level `sweep.toml` marker. Each
sub-directory mirrors the layout of `simulate` (`observations.parquet` plus
its three sidecars) and adds a `network_metrics.toml` carrying the
post-hoc network/shelter metrics.

```bash
uv run iohmm-evac sweep summary --input-dir ./output/sweep/
```

Prints a `scenario × {delay_hr, peak_er_share, peak_er_hour, overflow,
failed_evac}` table to stdout. Use `--scenarios baseline,contraflow` on
`sweep run` to run a subset.

The sweep ships with three reporting helpers:

```bash
iohmm-evac report sweep-departures --input-dir DIR [--output PATH]
iohmm-evac report sweep-network    --input-dir DIR [--output PATH]
iohmm-evac report sweep-all        --input-dir DIR --output-dir DIR
```

`sweep-departures` renders Fig. 4 (one cumulative-departure curve per
scenario, colorblind-friendly palette, landfall as the only common
vertical reference). `sweep-network` renders Fig. 5 (2x2 horizontal-bar
panel: total delay, peak EnRoute share, shelter overflow, failed
evacuations). `sweep-all` writes both to `--output-dir`.

See [`sweep.md`](sweep.md) for the directory layout and
[`network.md`](network.md) for the metric definitions.

## Bootstrap and shift sweep (Build 4)

Three subcommands run the parametric bootstrap pipeline that produces
Fig. 6:

```bash
iohmm-evac bootstrap fit \
    --input ./output/baseline.parquet \
    --warm-start ./output/fit/ \
    --output-dir ./output/bootstrap/fits/ \
    --n-replicates 50 [--jobs J] [--seed INT]

iohmm-evac bootstrap shift-sweep \
    --bootstrap-dir ./output/bootstrap/fits/ \
    --output ./output/bootstrap/shift_sweep.parquet \
    [--shifts -24,-16,-8,0,8,16,24] [--seed INT] [--scenario baseline]

iohmm-evac bootstrap summary \
    --input ./output/bootstrap/shift_sweep.parquet

iohmm-evac report bootstrap-bands \
    --input ./output/bootstrap/shift_sweep.parquet \
    --output ./output/figures/bootstrap_bands.png \
    [--metric METRIC]
```

`bootstrap fit` defaults to `--n-replicates 50` and `--jobs -1` (all
cores). It uses joblib's loky backend with BLAS thread pinning to keep
worker processes from oversubscribing the box (see
[`development.md`](development.md)). Each replicate is persisted to
`replicate_NNN/` immediately so a partial run is recoverable.

`--warm-start` is optional. When supplied, every replicate's EM is seeded
from the directory's `theta.toml`, halving per-replicate iteration counts.
Without it each replicate fits cold from a random init.

`bootstrap shift-sweep` ingests the `replicate_NNN/` directories and runs
one IO-HMM-faithful simulation per `(replicate, shift)` cell. The default
shift grid is `-24,-16,-8,0,8,16,24`; pass `--shifts=...` to override (note
the equals sign — argparse otherwise interprets the leading `-` as a flag).
The cell-level seed is deterministic in `(b, shift_index)`.

`bootstrap summary` prints a per-shift table of median ± [P25, P75] and
[P5, P95] for each of the four network metrics.

`report bootstrap-bands` renders Fig. 6 (failed-evacuation count vs.
warning-shift, with quantile bands). Use `--metric` to plot any of the
other three network metrics from the same sweep — useful for the chapter's
discussion paragraphs.

See [`bootstrap.md`](bootstrap.md) for the procedure, why it captures the
right uncertainty, and how to read the bands.