CLI and command registry#
GeoPrior-v3 exposes a staged command-line interface for
pipeline execution, artifact materialization, and figure
rendering. The refactored layout uses
geoprior.scripts as the authoritative home for
reproducibility commands, while the top-level
scripts package remains a backward-compatible launcher.
The practical rule is simple:
geopriorand its family entry points are the modern public interface.geoprior.scriptsowns the real script registry and artifact-path policy.scriptsmirrors that registry for legacy invocations such aspython -m scripts ....
Note
The root dispatcher no longer depends on the legacy
scripts package as its source of truth. Instead,
both the modern CLI and the compatibility launcher read
from geoprior.scripts.registry.
Architecture overview#
The command stack is split into three layers.
Public entry points
geopriorgeoprior-rungeoprior-buildgeoprior-plotgeoprior-init
Internal implementation
geoprior.__main__geoprior.cligeoprior.cli._dispatchgeoprior.scripts.registrygeoprior.scripts.config
Compatibility layer
scripts.__main__scripts.registrypython -m scripts <command>
A compact conceptual map is shown below.
geoprior/
├── __main__.py
├── cli/
│ ├── __init__.py
│ ├── __main__.py
│ ├── _dispatch.py
│ ├── _presets.py
│ ├── config.py
│ ├── init_config.py
│ ├── run_sensitivity.py
│ ├── run_sm3_suite.py
│ ├── stage1.py
│ ├── stage2.py
│ ├── stage3.py
│ ├── stage4.py
│ ├── stage5.py
│ └── build_*.py / sm3_*.py / ...
├── scripts/
│ ├── registry.py
│ ├── config.py
│ └── plot_*.py / build_*.py / ...
└── ...
scripts/
├── __init__.py
├── __main__.py
└── registry.py
Entry points and usage model#
The modern CLI supports two complementary styles.
Root entry point#
The root command uses explicit families:
geoprior run <command> [args]
geoprior build <command> [args]
geoprior plot <command> [args]
Examples:
geoprior run stage1-preprocess
geoprior build exposure --help
geoprior plot physics-fields --help
Family-specific entry points#
Dedicated entry points remove the family prefix:
geoprior-run <command> [args]
geoprior-build <command> [args]
geoprior-plot <command> [args]
geoprior-init [args]
Examples:
geoprior-run stage4-infer --help
geoprior-build model-metrics
geoprior-plot transfer-impact
geoprior-init --yes
Backward compatibility launcher#
The legacy launcher remains available:
python -m scripts <command> [args]
Examples:
python -m scripts plot-physics-fields --help
python -m scripts make-exposure --help
This compatibility surface is intentionally thin. It
re-exports the registry and dispatches to modules stored in
geoprior.scripts.
Command families#
GeoPrior-v3 groups commands into three public families.
Run family#
The run family executes staged workflows and related
experiment drivers.
Typical commands include:
init-configstage1-preprocessstage2-trainstage3-tunestage4-inferstage5-transfersensitivitysm3-identifiabilitysm3-offset-diagnosticssm3-suite
Build family#
The build family materializes derived artifacts, helper
products, and summary tables.
Typical commands include:
full-inputs-npzphysics-payload-npzexternal-validation-fullcitysm3-collect-summariesassign-boreholesadd-zsurf-from-coordsexternal-validation-metricsbrier-exceedancehotspotshotspots-summaryextend-forecastupdate-ablation-recordsmodel-metricsablation-tableboundaryexposuredistrict-gridclusters-with-zones
Plot family#
The plot family renders publication-facing figures,
appendix panels, maps, and transfer-analysis visuals.
Typical commands include:
driver-responsecore-ablationlitho-parityuncertaintyspatial-forecastsphysics-sanityphysics-mapsphysics-fieldsphysics-profilesuncertainty-extrasablations-sensitivityphysics-sensitivitysm3-identifiabilitysm3-bounds-ridge-summarysm3-log-offsetsxfer-transferabilityxfer-impacttransfertransfer-impactgeo-cumulativehotspot-analyticsexternal-validation
Note
The exact command list is defined in the registry. The examples above reflect the current public surface exposed by the dispatcher and the script registry.
Registry ownership#
The central design change in the refactor is the ownership model for reproducibility commands.
Before the refactor, the modern CLI treated scripts as a
registry bridge. After the refactor, the authoritative
registry lives in geoprior.scripts.registry.
That yields a cleaner dependency direction:
geoprior.cli→geoprior.scriptsscripts→geoprior.scripts
and avoids making the main package depend on a legacy shim.
Registry structure#
The registry stores command metadata such as:
module name
callable name
human-readable description
family
aliases
public command name
This information is consumed by the dispatcher in
geoprior.cli.__main__ and mirrored by the compatibility
launcher.
Dispatch model#
The modern CLI resolves commands through the registry and then imports the target module lazily.
This keeps the root entry point compact and makes each script module independently testable.
A simplified execution path is:
user command
↓
geoprior / geoprior-run / geoprior-build / geoprior-plot
↓
geoprior.cli.__main__
↓
registry lookup
↓
import target module
↓
call exported main function
Artifact-root policy#
The CLI also standardizes artifact-path resolution through shared environment-variable and config helpers.
In practice this means that figures, tables, and helper artifacts can be redirected consistently without forcing script-specific path rewrites.
Typical environment variables include:
GEOPRIOR_ARTIFACT_ROOTGEOPRIOR_FIG_DIRGEOPRIOR_OUT_DIR
The conceptual layout is:
<artifact root>/
├── results/
├── fig/
└── out/
This keeps compatibility with the historical folder layout while allowing explicit relocation of generated outputs.
Examples:
export GEOPRIOR_ARTIFACT_ROOT=/tmp/geoprior_artifacts
export GEOPRIOR_FIG_DIR=/tmp/geoprior_figures
export GEOPRIOR_OUT_DIR=/tmp/geoprior_tables
On Windows PowerShell:
$env:GEOPRIOR_ARTIFACT_ROOT = "D:\\geoprior\\artifacts"
$env:GEOPRIOR_FIG_DIR = "D:\\geoprior\\figures"
$env:GEOPRIOR_OUT_DIR = "D:\\geoprior\\tables"
Why geoprior.__main__ matters#
Adding geoprior.__main__ allows direct execution with:
python -m geoprior --help
This complements installed console scripts and gives one more stable execution path for development, testing, and package verification.
API reference strategy#
Top-level entry points#
Run wrappers and workflow drivers#
CLI for initializing GeoPrior configuration files. |
|
Stage-1: Zhongshan/Nansha preprocessing & sequence export for GeoPriorSubsNet |
|
CLI wrapper for Stage-2 training. |
|
CLI wrapper for Stage-3 hyperparameter tuning. |
|
CLI wrapper for Stage-4 inference. |
|
CLI wrapper for Stage-5 cross-city transfer evaluation. |
|
run_sensitivity.py |
|
Preset-driven SM3 suite runner. |
|
Drop-in run_one() for nat.com/sensitivity_lib.py. |
|
Collect SM3 per-regime summaries into one combined table. |
|
CLI wrapper for SM3 log-offset diagnostics. |
|
CLI wrapper for SM3 synthetic identifiability. |
Build wrappers under geoprior.cli#
Build harmonized datasets enriched with surface elevation. |
|
Assign boreholes to the nearest city point cloud. |
|
CLI for building full-city external validation artifacts. |
|
Build external validation metrics from Stage-1 inputs and a physics payload. |
|
CLI for building merged full-input NPZ artifacts. |
|
CLI for building physics payload NPZ artifacts. |
|
Build a combined SM3 summary table from a suite directory. |
Private workflow backends excluded from autosummary#
The modules below are intentionally not imported by the API reference because they depend on workflow state such as manifests or runtime environment setup.
geoprior.cli._sensitivitygeoprior.cli._stage2geoprior.cli._stage3geoprior.cli._stage4geoprior.cli._stage5
Build and compute scripts under geoprior.scripts#
Build tidy ablation/sensitivity tables from ablation_records. |
|
Build a unified "model metrics" table from GeoPrior runs. |
|
Compute Brier scores for subsidence exceedance events. |
|
Script helpers for computing spatial hotspot summaries. |
|
Script helpers for extending forecast outputs in time. |
|
Script helpers for building study-area boundary artifacts. |
|
Script helpers for building district grid artifacts. |
|
Script helpers for building exposure-layer artifacts. |
|
Script helpers for rebuilding confusion tables. |
|
Summarise hotspot point clouds. |
|
Script helpers for tagging clusters with spatial zones. |
|
Update ablation record using post-hoc calibrated metrics. |
|
Update ablation_record using post-hoc calibrated metrics. |
Plot scripts under geoprior.scripts#
Plot extended ablations & sensitivities. |
|
Script helpers for plotting core ablation results. |
|
Script helpers for plotting driver response analyses. |
|
Plot point-support external validation of inferred effective fields. |
|
Plot cumulative subsidence maps on satellite basemap + optional hotspots. |
|
Script helpers for plotting hotspot analytics. |
|
Plot lithology parity across cities. |
|
Plot spatial physics fields and "physics tension". |
|
Plot spatial physics fields. |
|
Plot 1D physics sensitivity profiles. |
|
Script helpers for plotting physics sanity diagnostics. |
|
Plot physics sensitivity (\(epsilon\_prior, \epsilon_cons\)). |
|
Plot bounds vs ridge summary. |
|
Plot synthetic identifiability (SM3). |
|
Script helpers for plotting SM3 log-offset diagnostics. |
|
Script helpers for plotting spatial forecast outputs. |
|
Plot cross-city transferability of GeoPriorSubsNet. |
|
Script helpers for plotting uncertainty diagnostics. |
|
Extra plotting helpers for uncertainty diagnostics. |
|
Plot impact transferability. |
|
Plot cross-city transferability +. |
Registry ownership#
The authoritative command registry lives under
geoprior.scripts.registry. The modern CLI and the
compatibility launcher both read from that registry rather
than maintaining separate command maps.
The dependency direction is therefore:
geoprior.cli→geoprior.scriptsscripts→geoprior.scripts
This keeps the compatibility layer thin and avoids making the main package depend on legacy launch code.
Dispatch model#
The modern CLI resolves a command through the registry, imports the target module lazily, and calls the exported main function.
user command
↓
geoprior / geoprior-run / geoprior-build / geoprior-plot
↓
geoprior.cli.__main__
↓
registry lookup
↓
import target module
↓
call exported main function
Artifact-root policy#
The CLI standardizes artifact-path resolution through shared configuration and environment-variable helpers. In practice, this allows figures, tables, and helper artifacts to be redirected without rewriting each script individually.
Typical environment variables include:
GEOPRIOR_ARTIFACT_ROOTGEOPRIOR_FIG_DIRGEOPRIOR_OUT_DIR
Conceptually:
<artifact root>/
├── results/
├── fig/
└── out/
Examples:
export GEOPRIOR_ARTIFACT_ROOT=/tmp/geoprior_artifacts
export GEOPRIOR_FIG_DIR=/tmp/geoprior_figures
export GEOPRIOR_OUT_DIR=/tmp/geoprior_tables
On Windows PowerShell:
$env:GEOPRIOR_ARTIFACT_ROOT = "D:\\geoprior\\artifacts"
$env:GEOPRIOR_FIG_DIR = "D:\\geoprior\\figures"
$env:GEOPRIOR_OUT_DIR = "D:\\geoprior\\tables"
Source listings#
The most important source files for the CLI stack are listed below. These listings are useful when the API reference is read together with the developer notes.
Package entry point#
geoprior/__main__.py## SPDX-License-Identifier: Apache-2.0
# GeoPrior-v3 — https://github.com/earthai-tech/geoprior-v3
# Copyright (c) 2026-present
# Author: LKouadio <https://lkouadio.com>
r"""Module entry point for running GeoPrior as a package."""
from __future__ import annotations
from .cli import main
if __name__ == "__main__":
main()
Modern CLI package#
geoprior/cli/__init__.py## SPDX-License-Identifier: Apache-2.0
# GeoPrior-v3 — https://github.com/earthai-tech/geoprior-v3
# Copyright (c) 2026-present
# Author: LKouadio <https://lkouadio.com>
"""Command-line interface for GeoPrior-v3.
This subpackage exposes one versatile root entry point and
its public wrapper modules.
Examples
--------
Use the root dispatcher with an explicit family::
geoprior run stage1-preprocess
geoprior build full-inputs-npz --stage1-dir results/foo_stage1
Use the family-specific console scripts directly::
geoprior-run stage4-infer --help
geoprior-build full-inputs
geoprior-plot <command>
geoprior-init --yes
Notes
-----
The package lazily exposes public CLI wrapper modules so
``geoprior.cli.<name>`` works with Sphinx autosummary
without importing private execution backends such as
``geoprior.cli._stage2`` at package import time.
"""
from __future__ import annotations
from importlib import import_module
from types import ModuleType
from typing import Final
_MAIN_EXPORTS: Final = {
"main": (".__main__", "main"),
"run_main": (".__main__", "run_main"),
"build_main": (".__main__", "build_main"),
"plot_main": (".__main__", "plot_main"),
}
_PUBLIC_MODULES: Final = {
"config",
"utils",
"init_config",
"stage1",
"stage2",
"stage3",
"stage4",
"stage5",
"run_sensitivity",
"run_sm3_suite",
"sensitivity_lib",
"sm3_collect_summaries",
"sm3_log_offsets_diagnostics",
"sm3_synthetic_identifiability",
"build_add_zsurf_from_coords",
"build_spatial_sampling",
"build_batch_spatial_sampling",
"build_forecast_ready_sample",
"build_spatial_roi",
"build_spatial_clusters",
"build_extract_zones",
"build_assign_boreholes",
"build_external_validation_fullcity",
"build_external_validation_metrics",
"build_full_inputs_npz",
"build_physics_payload_npz",
"build_sm3_collect_summaries",
}
__all__ = [
"main",
"run_main",
"build_main",
"plot_main",
"config",
"utils",
"init_config",
"stage1",
"stage2",
"stage3",
"stage4",
"stage5",
"run_sensitivity",
"run_sm3_suite",
"sensitivity_lib",
"sm3_collect_summaries",
"sm3_log_offsets_diagnostics",
"sm3_synthetic_identifiability",
"build_add_zsurf_from_coords",
"build_spatial_sampling",
"build_batch_spatial_sampling",
"build_spatial_roi",
"build_spatial_clusters",
"build_extract_zones",
"build_assign_boreholes",
"build_external_validation_fullcity",
"build_external_validation_metrics",
"build_full_inputs_npz",
"build_physics_payload_npz",
"build_sm3_collect_summaries",
]
def _load_module(name: str) -> ModuleType:
module = import_module(f".{name}", __name__)
globals()[name] = module
return module
def _load_export(name: str):
mod_name, attr_name = _MAIN_EXPORTS[name]
module = import_module(mod_name, __name__)
value = getattr(module, attr_name)
globals()[name] = value
return value
def __getattr__(name: str):
if name in _MAIN_EXPORTS:
return _load_export(name)
if name in _PUBLIC_MODULES:
return _load_module(name)
raise AttributeError(
f"module {__name__!r} has no attribute {name!r}"
)
def __dir__() -> list[str]:
return sorted(set(globals()) | set(__all__))
geoprior/cli/__main__.py## SPDX-License-Identifier: Apache-2.0
# GeoPrior-v3 — https://github.com/earthai-tech/geoprior-v3
# Copyright (c) 2026-present
# Author: LKouadio <https://lkouadio.com>
r"""Command-line entry point for GeoPrior."""
from __future__ import annotations
import sys
from ..scripts.registry import SCRIPT_COMMANDS
from ._dispatch import (
CommandSpec,
alias_map,
call_entry,
load_callable,
print_help_table,
run_module,
)
def _scripts_as_cli() -> dict[str, CommandSpec]:
items: dict[str, CommandSpec] = {}
for spec in SCRIPT_COMMANDS.values():
public = spec.public_name
if public is None:
continue
if public in items:
raise ValueError(
f"Duplicate script public name: {public}"
)
items[public] = CommandSpec(
package="geoprior.scripts",
mod=spec.mod,
fn=spec.fn,
desc=spec.desc,
mode=spec.mode,
family=spec.family,
public_name=public,
aliases=spec.aliases,
legacy_names=spec.legacy_names,
)
return items
_CMD: dict[str, CommandSpec] = {
"init-config": CommandSpec(
package="geoprior.cli",
mod="init_config",
fn="main",
desc="Create nat.com/config.py interactively.",
mode="argv",
family="run",
public_name="init-config",
aliases=("init", "bootstrap"),
),
"stage1-preprocess": CommandSpec(
package="geoprior.cli",
mod="stage1",
fn="stage1_main",
desc="Stage-1 preprocessing and export.",
mode="argv",
family="run",
public_name="stage1-preprocess",
aliases=(
"stage1",
"s1",
"preprocess",
"prepare",
),
),
"stage2-train": CommandSpec(
package="geoprior.cli",
mod="stage2",
fn="stage2_main",
desc="Stage-2 training.",
mode="argv",
family="run",
public_name="stage2-train",
aliases=(
"stage2",
"s2",
"train",
"fit",
),
),
"stage3-tune": CommandSpec(
package="geoprior.cli",
mod="stage3",
fn="stage3_main",
desc="Stage-3 hyperparameter tuning.",
mode="argv",
family="run",
public_name="stage3-tune",
aliases=(
"stage3",
"s3",
"tune",
"tuning",
"search",
),
),
"stage4-infer": CommandSpec(
package="geoprior.cli",
mod="stage4",
fn="stage4_main",
desc="Stage-4 inference.",
mode="argv",
family="run",
public_name="stage4-infer",
aliases=(
"stage4",
"s4",
"infer",
"inference",
"predict",
"forecast",
),
),
"stage5-transfer": CommandSpec(
package="geoprior.cli",
mod="stage5",
fn="stage5_main",
desc="Stage-5 transfer evaluation.",
mode="argv",
family="run",
public_name="stage5-transfer",
aliases=(
"stage5",
"s5",
"transfer",
"xfer",
),
),
"sensitivity": CommandSpec(
package="geoprior.cli",
mod="run_sensitivity",
fn="sensitivity_main",
desc="Physics sensitivity grid driver.",
mode="argv",
family="run",
public_name="sensitivity",
aliases=(
"sens",
"lambda-sensitivity",
"run-sensitivity",
),
),
"identifiability": CommandSpec(
package="geoprior.cli",
mod="sm3_synthetic_identifiability",
fn="sm3_identifiability_main",
desc="SM3 synthetic identifiability.",
mode="argv",
family="run",
public_name="identifiability",
aliases=(
"identifiability",
"ident",
"indetifiability",
"sm3-ident",
),
),
"sm3-offset-diagnostics": CommandSpec(
package="geoprior.cli",
mod="sm3_log_offsets_diagnostics",
fn="sm3_offsets_main",
desc="SM3 log-offset diagnostics.",
mode="argv",
family="run",
public_name="sm3-offset-diagnostics",
aliases=(
"offset-diagnostics",
"offsets",
"sm3-offsets",
),
),
"sm3-suite": CommandSpec(
package="geoprior.cli",
mod="run_sm3_suite",
fn="sm3_suite_main",
desc="Preset-driven SM3 multi-regime suite runner.",
mode="argv",
family="run",
public_name="sm3-suite",
aliases=(
"sm3-regimes",
"sm3-preset",
"sm3-batch",
),
),
"full-inputs-npz": CommandSpec(
package="geoprior.cli",
mod="build_full_inputs_npz",
fn="build_full_inputs_main",
desc="Build merged full_inputs.npz from splits.",
mode="argv",
family="build",
public_name="full-inputs-npz",
aliases=(
"build-full-inputs",
"make-full-inputs",
"full-inputs",
"merge-inputs",
),
),
"physics-payload-npz": CommandSpec(
package="geoprior.cli",
mod="build_physics_payload_npz",
fn="build_physics_payload_main",
desc="Build a physics payload NPZ.",
mode="argv",
family="build",
public_name="physics-payload-npz",
aliases=(
"physics-payload",
"payload-npz",
"full-city-payload",
"fullcity-payload",
"export-physics-payload",
),
),
"external-validation-fullcity": CommandSpec(
package="geoprior.cli",
mod="build_external_validation_fullcity",
fn="build_external_validation_fullcity_main",
desc="Build full-city validation artifacts.",
mode="argv",
family="build",
public_name="external-validation-fullcity",
aliases=(
"external-validation",
"fullcity-validation",
"validate-fullcity",
"ext-validation",
),
),
"sm3-collect-summaries": CommandSpec(
package="geoprior.cli",
mod="build_sm3_collect_summaries",
fn="build_sm3_collect_main",
desc="Build one combined SM3 summary table.",
mode="argv",
family="build",
public_name="sm3-collect-summaries",
aliases=(
"sm3-summaries",
"collect-summaries",
"collect-sm3",
"combined-sm3-summary",
),
),
"assign-boreholes": CommandSpec(
package="geoprior.cli",
mod="build_assign_boreholes",
fn="build_assign_boreholes_main",
desc="Build nearest-city borehole tables.",
mode="argv",
family="build",
public_name="assign-boreholes",
aliases=(
"classify-boreholes",
"borehole-city-assignment",
"boreholes-by-city",
"split-boreholes",
),
),
"add-zsurf-from-coords": CommandSpec(
package="geoprior.cli",
mod="build_add_zsurf_from_coords",
fn="build_add_zsurf_main",
desc="Build z_surf-enriched datasets.",
mode="argv",
family="build",
public_name="add-zsurf-from-coords",
aliases=(
"add-zsurf",
"merge-zsurf",
"zsurf-from-coords",
"harmonized-zsurf",
),
),
"batch-spatial-sampling": CommandSpec(
package="geoprior.cli",
mod="build_batch_spatial_sampling",
fn="build_batch_spatial_sampling_main",
desc="Build non-overlapping spatial sample batches.",
mode="argv",
family="build",
public_name="batch-spatial-sampling",
aliases=(
"batch-sampling",
"batch-spatial",
"spatial-batches",
),
),
"spatial-sampling": CommandSpec(
package="geoprior.cli",
mod="build_spatial_sampling",
fn="build_spatial_sampling_main",
desc="Build a stratified spatial sample table.",
mode="argv",
family="build",
public_name="spatial-sampling",
aliases=(
"sample-spatial",
"spatial-sample",
"sampling",
),
),
"spatial-roi": CommandSpec(
package="geoprior.cli",
mod="build_spatial_roi",
fn="build_spatial_roi_main",
desc="Build a spatial region-of-interest table.",
mode="argv",
family="build",
public_name="spatial-roi",
aliases=(
"roi",
"extract-roi",
"roi-table",
),
),
"spatial-clusters": CommandSpec(
package="geoprior.cli",
mod="build_spatial_clusters",
fn="build_spatial_clusters_main",
desc="Build a table with spatial cluster labels.",
mode="argv",
family="build",
public_name="spatial-clusters",
aliases=(
"cluster-spatial",
"cluster-regions",
"clusters",
),
),
"forecast-ready-sample": CommandSpec(
package="geoprior.cli",
mod="build_forecast_ready_sample",
fn="build_forecast_ready_sample_main",
desc="Build a compact forecast-ready panel sample.",
mode="argv",
family="build",
public_name="forecast-ready-sample",
aliases=(
"forecast-sample",
"panel-sample",
"demo-panel",
"ready-sample",
),
),
"extract-zones": CommandSpec(
package="geoprior.cli",
mod="build_extract_zones",
fn="build_extract_zones_main",
desc="Build a threshold-based zone extraction table.",
mode="argv",
family="build",
public_name="extract-zones",
aliases=(
"zones",
"zones-from",
"zone-extract",
),
),
"external-validation-metrics": CommandSpec(
package="geoprior.cli",
mod="build_external_validation_metrics",
fn="build_external_validation_metrics_main",
desc="Build external validation metrics.",
mode="argv",
family="build",
public_name="external-validation-metrics",
aliases=(
"borehole-validation",
"compute-external-validation",
),
),
}
for _name, _spec in _scripts_as_cli().items():
if _name in _CMD:
raise ValueError(f"Duplicate public command: {_name}")
_CMD[_name] = _spec
_FAMILY_ALIASES = {
"run": "run",
"build": "build",
"make": "build",
"plot": "plot",
}
_GROUPS = (
(
"Pipeline",
(
"init-config",
"stage1-preprocess",
"stage2-train",
"stage3-tune",
"stage4-infer",
"stage5-transfer",
"sensitivity",
),
),
(
"Supplementary diagnostics",
(
"sm3-identifiability",
"sm3-offset-diagnostics",
"sm3-suite",
),
),
(
"Build commands",
(
"full-inputs-npz",
"physics-payload-npz",
"external-validation-fullcity",
"sm3-collect-summaries",
"assign-boreholes",
"add-zsurf-from-coords",
"external-validation-metrics",
"forecast-ready-sample",
"batch-spatial-sampling",
"spatial-sampling",
"spatial-roi",
"spatial-clusters",
"extract-zones",
"brier-exceedance",
"hotspots",
"hotspots-summary",
"extend-forecast",
"update-ablation-records",
"model-metrics",
"ablation-table",
"boundary",
"exposure",
"district-grid",
"clusters-with-zones",
),
),
(
"Plot commands",
(
"driver-response",
"core-ablation",
"litho-parity",
"uncertainty",
"spatial-forecasts",
"physics-sanity",
"physics-maps",
"physics-fields",
"physics-profiles",
"uncertainty-extras",
"ablations-sensitivity",
"physics-sensitivity",
"sm3-identifiability",
"sm3-bounds-ridge-summary",
"sm3-log-offsets",
"xfer-transferability",
"xfer-impact",
"transfer",
"transfer-impact",
"geo-cumulative",
"hotspot-analytics",
"external-validation",
),
),
)
def _auto_prog_name() -> str:
argv0 = (sys.argv[0] or "").strip()
if argv0.endswith("__main__.py"):
return "python -m geoprior.cli"
return argv0 or "geoprior"
def _entry_prog(family: str | None) -> str:
if family == "run":
return "geoprior-run"
if family == "build":
return "geoprior-build"
if family == "plot":
return "geoprior-plot"
return "geoprior"
def _display_cmd(
prog: str,
family: str,
cmd: str,
*,
fixed_family: str | None,
) -> str:
if fixed_family is None:
return f"{prog} {family} {cmd}"
return f"{prog} {cmd}"
def _family_items(
family: str,
) -> list[tuple[str, CommandSpec]]:
items: list[tuple[str, CommandSpec]] = []
for name, spec in _CMD.items():
if spec.public_name != name:
continue
if spec.family != family:
continue
items.append((name, spec))
items.sort(key=lambda x: x[0])
return items
def _print_help(
*,
fixed_family: str | None = None,
prog: str | None = None,
) -> None:
prog_name = prog or _auto_prog_name()
if fixed_family is None:
print("Usage:")
print(f" {prog_name} run <command> [args]")
print(f" {prog_name} build <command> [args]")
print(f" {prog_name} plot <command> [args]")
print("")
print("Families:")
print(" run Execute model workflows.")
print(" build Materialize artifacts.")
print(" make Alias of build.")
print(" plot Render figures and maps.")
print("")
for title, names in _GROUPS:
items = [
(name, _CMD[name])
for name in names
if name in _CMD
]
print_help_table(title, items)
amap = alias_map(_CMD)
if amap:
print("Aliases:")
for src in sorted(amap):
print(f" {src} -> {amap[src]}")
print("")
print("Examples:")
print(f" {prog_name} plot physics-fields --help")
print(f" {prog_name} build exposure --help")
print(f" {prog_name} run stage1-preprocess")
return
print("Usage:")
print(f" {prog_name} <command> [args]")
print("")
print(f"{fixed_family.title()} commands:")
print("")
items = _family_items(fixed_family)
print_help_table("Commands", items)
amap = alias_map(_CMD, family=fixed_family)
if amap:
print("Aliases:")
for src in sorted(amap):
print(f" {src} -> {amap[src]}")
print("")
print("Examples:")
if fixed_family == "plot":
print(f" {prog_name} physics-fields --help")
elif fixed_family == "build":
print(f" {prog_name} exposure --help")
else:
print(f" {prog_name} stage1-preprocess")
def _resolve(
args: list[str],
*,
fixed_family: str | None,
prog: str,
) -> tuple[str, list[str], str]:
amap = alias_map(_CMD)
if fixed_family is None:
family = _FAMILY_ALIASES.get(args[0])
if family is None:
_print_help(
fixed_family=None,
prog=prog,
)
raise SystemExit(2)
if len(args) == 1:
_print_help(
fixed_family=family,
prog=f"{prog} {args[0]}",
)
raise SystemExit(0)
if args[1] in {"-h", "--help", "help"}:
_print_help(
fixed_family=family,
prog=f"{prog} {args[0]}",
)
raise SystemExit(0)
cmd = amap.get(args[1], args[1])
return cmd, args[2:], family
repeated = _FAMILY_ALIASES.get(args[0])
if repeated is not None:
print(
f"[ERR] {prog!r} already implies "
f"the {fixed_family!r} family."
)
raise SystemExit(2)
cmd = amap.get(args[0], args[0])
return cmd, args[1:], fixed_family
def _dispatch(
argv: list[str] | None = None,
*,
fixed_family: str | None = None,
prog: str | None = None,
) -> None:
args = list(argv) if argv is not None else sys.argv[1:]
prog_name = prog or _auto_prog_name()
if not args or args[0] in {"-h", "--help", "help"}:
_print_help(
fixed_family=fixed_family,
prog=prog_name,
)
return
cmd, rest, family = _resolve(
args,
fixed_family=fixed_family,
prog=prog_name,
)
spec = _CMD.get(cmd)
if spec is None:
print(f"[ERR] Unknown command: {cmd}")
print("")
_print_help(
fixed_family=family,
prog=prog_name,
)
raise SystemExit(2)
if spec.family != family:
print(
f"[ERR] Command {cmd!r} belongs to "
f"{spec.family!r}, not {family!r}."
)
print(f"[HINT] Use {_entry_prog(spec.family)} {cmd}")
raise SystemExit(2)
display_cmd = _display_cmd(
prog_name,
family,
cmd,
fixed_family=fixed_family,
)
if spec.mode == "module":
run_module(
spec,
display_cmd=display_cmd,
argv=rest,
)
return
fn = load_callable(spec)
call_entry(
fn,
argv=rest,
display_cmd=display_cmd,
)
def main(argv: list[str] | None = None) -> None:
_dispatch(argv, fixed_family=None)
def run_main(argv: list[str] | None = None) -> None:
_dispatch(argv, fixed_family="run")
def build_main(argv: list[str] | None = None) -> None:
_dispatch(argv, fixed_family="build")
def plot_main(argv: list[str] | None = None) -> None:
_dispatch(argv, fixed_family="plot")
main.__doc__ = r"""
Run the root GeoPrior command dispatcher.
This is the top-level CLI entry point behind the generic
``geoprior`` command. It routes user input to one of the three
public command families exposed by the project:
- ``run`` for staged model workflows,
- ``build`` for artifact materialization and table generation,
- ``plot`` for figure and map rendering.
The function itself is intentionally thin. It delegates all parsing,
family resolution, alias expansion, help rendering, and command
execution to the internal dispatcher while keeping a stable public
entry point for console scripts and programmatic invocation.
Conceptually, the root command supports calls of the form
.. code-block:: bash
geoprior run <command> [args]
geoprior build <command> [args]
geoprior plot <command> [args]
which mirrors the stage-wise and artifact-aware workflow adopted by
the GeoPrior project for forecasting, diagnostics, and uncertainty
analysis :cite:p:`Kouadio2025XTFT,Limetal2021`.
Parameters
----------
argv : list of str or None, default=None
Optional command-line tokens excluding the program name.
When ``None``, the function reads arguments from
:data:`sys.argv`.
Returns
-------
None
This function is executed for its side effects. It prints help,
dispatches a command, or raises :class:`SystemExit` on invalid
user input.
Raises
------
SystemExit
Raised when command resolution fails, when help is requested, or
when a delegated subcommand exits.
Notes
-----
This function is the most user-facing CLI entry point in the module.
Use it when you want the full family-aware dispatcher behavior.
For family-specific wrappers that do not require the explicit
``run``, ``build``, or ``plot`` prefix, see :func:`run_main`,
:func:`build_main`, and :func:`plot_main`.
Examples
--------
Call from Python with an explicit token list:
>>> from geoprior.cli.__main__ import main
>>> main(["run", "stage1-preprocess"]) # doctest: +SKIP
Request top-level help:
>>> main(["--help"]) # doctest: +SKIP
Dispatch a plotting command:
>>> main(["plot", "physics-fields", "--help"]) # doctest: +SKIP
See Also
--------
run_main :
Run-family wrapper used by the ``geoprior-run`` entry point.
build_main :
Build-family wrapper used by the ``geoprior-build`` entry point.
plot_main :
Plot-family wrapper used by the ``geoprior-plot`` entry point.
"""
run_main.__doc__ = r"""
Run the GeoPrior dispatcher in fixed ``run`` mode.
This wrapper exposes the workflow-oriented command family behind the
``geoprior-run`` console script. Unlike :func:`main`, it does not
expect a family token as the first positional argument. Instead, it
assumes that every command belongs to the ``run`` family and rejects
attempts to repeat the family prefix.
Typical commands dispatched through this entry point include stage
execution and synthetic or sensitivity workflows, such as
preprocessing, training, tuning, inference, transfer evaluation, and
selected supplementary diagnostics.
Supported usage therefore follows the shorter form
.. code-block:: bash
geoprior-run <command> [args]
rather than
.. code-block:: bash
geoprior run <command> [args]
Parameters
----------
argv : list of str or None, default=None
Optional command-line tokens excluding the program name.
When ``None``, the function reads arguments from
:data:`sys.argv`.
Returns
-------
None
This function dispatches a run-family command for its side
effects.
Raises
------
SystemExit
Raised when the requested command is unknown, belongs to a
different family, or explicitly requests help.
Notes
-----
This wrapper is mainly intended for console-script integration and
family-specific convenience. It is especially useful in automation,
examples, and shell documentation where repeated family prefixes
would add noise.
Examples
--------
Run stage 1 preprocessing:
>>> from geoprior.cli.__main__ import run_main
>>> run_main(["stage1-preprocess"]) # doctest: +SKIP
Inspect the help of a training command:
>>> run_main(["stage2-train", "--help"]) # doctest: +SKIP
Request family-scoped help:
>>> run_main(["--help"]) # doctest: +SKIP
See Also
--------
main :
Root family-aware dispatcher.
build_main :
Build-family wrapper.
plot_main :
Plot-family wrapper.
"""
build_main.__doc__ = r"""
Run the GeoPrior dispatcher in fixed ``build`` mode.
This wrapper exposes the artifact-building command family behind the
``geoprior-build`` console script. It assumes that the requested
subcommand already belongs to the ``build`` family and therefore uses
the compact invocation form
.. code-block:: bash
geoprior-build <command> [args]
Typical commands in this family generate or transform reproducible
artifacts needed by downstream training, validation, interpretation,
or documentation workflows. Examples include merged NPZ payloads,
external-validation tables, spatial sampling products, hotspot
summaries, and other materialized intermediate datasets.
Parameters
----------
argv : list of str or None, default=None
Optional command-line tokens excluding the program name.
When ``None``, the function reads arguments from
:data:`sys.argv`.
Returns
-------
None
This function dispatches a build-family command for its side
effects.
Raises
------
SystemExit
Raised when the command is unknown, belongs to another family, or
when help is requested.
Notes
-----
Use this wrapper when you want a stable programmatic entry point for
artifact generation without exposing the full root dispatcher. This
is the recommended choice for shell examples, gallery preparation,
and reproducible data-materialization scripts built around GeoPrior.
Examples
--------
Build a full input archive:
>>> from geoprior.cli.__main__ import build_main
>>> build_main(["full-inputs-npz", "--help"]) # doctest: +SKIP
Build a compact forecast-ready panel:
>>> build_main(["forecast-ready-sample"]) # doctest: +SKIP
Show the build-family help page:
>>> build_main(["--help"]) # doctest: +SKIP
See Also
--------
main :
Root family-aware dispatcher.
run_main :
Run-family wrapper.
plot_main :
Plot-family wrapper.
"""
plot_main.__doc__ = r"""
Run the GeoPrior dispatcher in fixed ``plot`` mode.
This wrapper exposes the visualization command family behind the
``geoprior-plot`` console script. It dispatches plotting commands
without requiring the explicit ``plot`` family prefix and therefore
supports the compact form
.. code-block:: bash
geoprior-plot <command> [args]
The plot family is used to render publication-style figures,
diagnostic graphics, uncertainty summaries, spatial forecast maps,
transfer panels, and other visual outputs derived from GeoPrior
artifacts. In practice, this family is central to the project's
interpretability and reporting workflow, where forecast accuracy,
uncertainty behavior, and physically informed diagnostics must be
read together :cite:p:`Kouadio2025XTFT`.
Parameters
----------
argv : list of str or None, default=None
Optional command-line tokens excluding the program name.
When ``None``, the function reads arguments from
:data:`sys.argv`.
Returns
-------
None
This function dispatches a plot-family command for its side
effects.
Raises
------
SystemExit
Raised when the command is unknown, belongs to another family, or
when help is requested.
Notes
-----
This wrapper is useful when the calling context is already
visualization-specific, such as gallery scripts, reproducible figure
pipelines, or publication packaging code.
Examples
--------
Inspect help for a physics-field plot:
>>> from geoprior.cli.__main__ import plot_main
>>> plot_main(["physics-fields", "--help"]) # doctest: +SKIP
Render a transferability figure:
>>> plot_main(["xfer-transferability"]) # doctest: +SKIP
Show the plot-family help page:
>>> plot_main(["--help"]) # doctest: +SKIP
See Also
--------
main :
Root family-aware dispatcher.
run_main :
Run-family wrapper.
build_main :
Build-family wrapper.
"""
if __name__ == "__main__":
main()
geoprior/cli/_dispatch.py## SPDX-License-Identifier: Apache-2.0
r"""
Low-level dispatch helpers for GeoPrior command-line entry points.
This module provides the internal building blocks used by the GeoPrior
CLI frontends to resolve commands, import their implementation modules,
and execute the appropriate entry callable with a uniform interface.
It is intentionally small and generic. The higher-level command
registry, family routing, and user-facing help pages live elsewhere,
while this module focuses on the reusable mechanics required by all
CLI entry points.
Overview
--------
The dispatcher infrastructure revolves around a small command
description object, :class:`CommandSpec`, and a set of helper
functions that perform four main tasks:
1. Import a command module from a registry entry.
2. Load the entry callable exposed by that module.
3. Execute the callable or module with the correct delegated
``argv`` and program name.
4. Build compact command/alias listings for help output.
Design goals
------------
This module is designed to keep CLI execution:
- **registry-driven**
so commands can be described declaratively rather than hard-coded,
- **lightweight**
so frontends such as ``geoprior``, ``geoprior-run``,
``geoprior-build``, and ``geoprior-plot`` can share the same
dispatch machinery,
- **backward-compatible**
so legacy command names and aliases can still resolve cleanly,
- **implementation-agnostic**
so a command may expose either a callable entrypoint or a
module-style ``__main__`` execution path.
Execution model
---------------
A command is described by :class:`CommandSpec`, which records the
target package, module, callable name, public command name, family,
and accepted aliases.
Given such a specification, the helpers in this module support two
execution styles:
``call_entry``
Load a Python callable and invoke it using the most compatible
calling convention discovered from its signature.
``run_module``
Execute a module as ``__main__`` after temporarily patching
:data:`sys.argv`, which is useful for module-oriented CLI code.
The module also provides small helpers such as :func:`alias_map`,
:func:`public_items`, and :func:`print_help_table` to support
consistent help rendering across command families.
Notes
-----
This module is primarily internal and is not intended to contain
domain-specific workflow logic. It should remain focused on generic
dispatch behavior only.
See Also
--------
geoprior.cli.__main__
Family-aware public CLI frontends built on top of this module.
geoprior.scripts.registry
Registry definitions consumed by the dispatch layer.
"""
from __future__ import annotations
import importlib
import inspect
import runpy
import sys
from collections.abc import Callable, Iterable
from dataclasses import dataclass
@dataclass(frozen=True)
class CommandSpec:
package: str
mod: str
fn: str
desc: str
mode: str = "argv" # argv | sysargv | module
family: str | None = None
public_name: str | None = None
aliases: tuple[str, ...] = ()
legacy_names: tuple[str, ...] = ()
CommandSpec.__doc__ = r"""
Immutable command description used by the CLI dispatch layer.
A :class:`CommandSpec` stores the minimal metadata needed to resolve
and execute a command from the registry-driven GeoPrior CLI system.
Each instance identifies
- the Python package containing the command,
- the module to import,
- the callable name to execute,
- a short human-readable description,
- the execution mode,
- the command family and public-facing names.
Attributes
----------
package : str
Package path used for relative imports. If empty, ``mod`` is
imported as an absolute module path.
mod : str
Module name containing the command implementation.
fn : str
Name of the callable entrypoint expected inside ``mod`` when the
execution mode is callable-based.
desc : str
Short help text shown in command listings.
mode : str, default="argv"
Execution strategy used by the dispatcher.
Supported values are typically:
- ``"argv"`` for callable entrypoints that accept delegated
argument lists,
- ``"sysargv"`` for callables relying on patched
:data:`sys.argv`,
- ``"module"`` for modules executed through :mod:`runpy`.
family : str or None, default=None
Optional command family such as ``"run"``, ``"build"``, or
``"plot"``.
public_name : str or None, default=None
Canonical public command name exposed to users.
aliases : tuple of str, default=()
Alternative spellings that resolve to ``public_name``.
legacy_names : tuple of str, default=()
Backward-compatible historical command names.
"""
def load_module(spec: CommandSpec):
"""Import a module for a command spec."""
if spec.package:
return importlib.import_module(
f".{spec.mod}",
package=spec.package,
)
return importlib.import_module(spec.mod)
def load_callable(spec: CommandSpec) -> Callable[..., None]:
"""Load the entry callable from a command module."""
mod = load_module(spec)
fn = getattr(mod, spec.fn, None)
if fn is None:
raise AttributeError(
f"Missing {spec.fn!r} in {spec.package}.{spec.mod}"
)
return fn
def run_module(
spec: CommandSpec,
*,
display_cmd: str,
argv: list[str] | None,
) -> None:
"""Execute a module as __main__ with delegated argv."""
old = list(sys.argv)
sys.argv = [display_cmd]
if argv:
sys.argv += list(argv)
mod_name = (
f"{spec.package}.{spec.mod}"
if spec.package
else spec.mod
)
try:
runpy.run_module(mod_name, run_name="__main__")
finally:
sys.argv = old
def call_entry(
fn: Callable[..., None],
*,
argv: list[str] | None,
display_cmd: str,
) -> None:
"""
Call a command entrypoint.
Preference order:
1) fn(argv, prog=...)
2) fn(argv)
3) fn() with patched sys.argv
"""
try:
sig = inspect.signature(fn)
except (TypeError, ValueError):
sig = None
if sig is not None:
params = sig.parameters
if "prog" in params:
fn(argv, prog=display_cmd)
return
positional = [
p
for p in params.values()
if p.kind
in {
inspect.Parameter.POSITIONAL_ONLY,
inspect.Parameter.POSITIONAL_OR_KEYWORD,
}
]
if positional:
fn(argv)
return
old = list(sys.argv)
sys.argv = [display_cmd]
if argv:
sys.argv += list(argv)
try:
fn()
finally:
sys.argv = old
def public_items(
registry: dict[str, CommandSpec],
*,
family: str | None = None,
) -> list[tuple[str, CommandSpec]]:
"""Return public registry items filtered by family."""
items = []
for name, spec in registry.items():
if spec.public_name is None:
continue
if family is not None and spec.family != family:
continue
items.append((spec.public_name, spec))
items.sort(key=lambda x: x[0])
return items
def alias_map(
registry: dict[str, CommandSpec],
*,
family: str | None = None,
) -> dict[str, str]:
"""Build alias -> public-name mapping."""
amap: dict[str, str] = {}
for spec in registry.values():
public_name = spec.public_name
if public_name is None:
continue
if family is not None and spec.family != family:
continue
for alias in spec.aliases:
amap[alias] = public_name
for legacy in spec.legacy_names:
amap[legacy] = public_name
return amap
def print_help_table(
title: str,
items: Iterable[tuple[str, CommandSpec]],
*,
width: int = 30,
) -> None:
"""Print a compact help table."""
shown = list(items)
if not shown:
return
print(f"{title}:")
for name, spec in shown:
left = (" " + name).ljust(width)
print(f"{left}{spec.desc}")
print("")
Compatibility package#
scripts/__init__.py#"""Compatibility launcher for legacy paper scripts.
Run::
python -m scripts <command> [args]
"""
from .__main__ import main
__all__ = ["main"]
scripts/__main__.py## SPDX-License-Identifier: Apache-2.0
# GeoPrior-v3 — https://github.com/earthai-tech/geoprior-v3
# Copyright (c) 2026-present
# Author: LKouadio <https://lkouadio.com>
from __future__ import annotations
import sys
from geoprior.cli._dispatch import (
CommandSpec,
alias_map,
call_entry,
load_callable,
print_help_table,
run_module,
)
from .registry import SCRIPT_COMMANDS, SCRIPT_GROUPS
def _legacy_registry() -> dict[str, CommandSpec]:
items: dict[str, CommandSpec] = {}
for legacy_name, spec in SCRIPT_COMMANDS.items():
items[legacy_name] = CommandSpec(
package="geoprior.scripts",
mod=spec.mod,
fn=spec.fn,
desc=spec.desc,
mode=spec.mode,
family=spec.family,
public_name=legacy_name,
aliases=spec.aliases,
legacy_names=(),
)
return items
_CMD = _legacy_registry()
def _print_help() -> None:
print("Usage:")
print(" python -m scripts <command> [args]")
print("")
for title, public_names in SCRIPT_GROUPS:
items = [
(
next(
legacy
for legacy, spec in SCRIPT_COMMANDS.items()
if spec.public_name == name
),
_CMD[
next(
legacy
for legacy, spec in SCRIPT_COMMANDS.items()
if spec.public_name == name
)
],
)
for name in public_names
if any(
spec.public_name == name
for spec in SCRIPT_COMMANDS.values()
)
]
print_help_table(title, items)
amap = alias_map(_CMD)
if amap:
print("Aliases:")
for src in sorted(amap):
print(f" {src} -> {amap[src]}")
print("")
print("Tip:")
print(" python -m scripts plot-physics-fields -h")
print("")
print("Modern entry points:")
print(" geoprior plot physics-fields -h")
print(" geoprior-build exposure -h")
def main(argv: list[str] | None = None) -> None:
args = list(argv) if argv is not None else sys.argv[1:]
if not args or args[0] in {"-h", "--help", "help"}:
_print_help()
return
amap = alias_map(_CMD)
cmd = amap.get(args[0], args[0])
rest = args[1:]
spec = _CMD.get(cmd)
if spec is None:
print(f"[ERR] Unknown command: {cmd}")
print("")
_print_help()
raise SystemExit(2)
display_cmd = f"python -m scripts {cmd}"
if spec.mode == "module":
run_module(
spec,
display_cmd=display_cmd,
argv=rest,
)
return
fn = load_callable(spec)
call_entry(
fn,
argv=rest,
display_cmd=display_cmd,
)
if __name__ == "__main__":
main()
scripts/registry.py## SPDX-License-Identifier: Apache-2.0
# GeoPrior-v3 — https://github.com/earthai-tech/geoprior-v3
# Copyright (c) 2026-present
# Author: LKouadio <https://lkouadio.com>
"""Compatibility registry for legacy ``python -m scripts`` runs.
The authoritative reproducibility registry now lives under
``geoprior.scripts``. This module re-exports the public objects so
legacy dispatch continues to work without duplicating the registry.
"""
from __future__ import annotations
from geoprior.scripts.registry import (
SCRIPT_COMMANDS,
SCRIPT_GROUPS,
ScriptSpec,
)
__all__ = [
"ScriptSpec",
"SCRIPT_COMMANDS",
"SCRIPT_GROUPS",
]
Design notes#
A few design choices are intentional.
Single source of truth#
The real registry now lives under geoprior.scripts.
This avoids circular dependency pressure and makes the main
package self-contained.
Thin compatibility layer#
The top-level scripts package should stay small. Its role
is to preserve historical commands, not to own the actual
implementation.
Separated artifact policy#
Artifact-path logic belongs in configuration rather than in individual plotting or build scripts. That keeps export behavior consistent and easier to override.