Metadata-Version: 2.4
Name: rustwx
Version: 0.5.11
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Rust
Classifier: Topic :: Scientific/Engineering :: Atmospheric Science
Requires-Dist: numpy>=1.26,<2.3
Summary: Rust-first weather model metadata and projected map rendering bindings
Keywords: weather,meteorology,hrrr,grib,rendering
License-Expression: MIT
Requires-Python: >=3.10
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
Project-URL: Repository, https://github.com/FahrenheitResearch/rustwx

# rustwx Python bindings

`rustwx` is the optional Python binding package for the Rust-first `rustwx`
weather workspace.

## Design goal

Keep Python convenient, keep the hot path in Rust, and expose generic render/model metadata surfaces that are usable outside WRF-specific callers.

## What is implemented

With the `python` feature enabled, the module exposes:

- agent-facing discovery and map rendering via `agent_capabilities_json`,
  `list_domains_json`, `render_maps_json`, `render_glm_lightning_json`, and
  `sample_point_timeseries_json`
- model listing and source/model helpers
- projected-grid rendering via `render_projected_map` and `render_projected_map_json`
- compatibility aliases `render_wrf_map` and `render_wrf_map_json`
- standalone projected projection metadata via `describe_projected_projection`
- standalone projected grid/layout metadata via `describe_projected_geometry`
- standalone projected CONUS basemap overlay extraction via `build_projected_basemap_overlays`
- future-facing cross-section request validation/normalization via `normalize_cross_section_request`
- native sounding-column rendering via `render_sounding_column` and `render_sounding_column_json`

The wheel also installs a stable `rustwx` console command for agent and MCP
adapters, plus a standalone local web UI command:

```powershell
rustwx capabilities
rustwx list-domains --kind country --limit 5
rustwx prepare-data --date 20260424 --model hrrr --forecast-hours 0-2 --products 2m_temperature_10m_winds,2m_dewpoint_10m_winds
rustwx render-maps --date 20260424 --model hrrr --domain california --product 2m_temperature_10m_winds --out-dir out
rustwx render-lightning --domain california --data-dir C:\Users\drew\lightning-test\data\glm --out-dir out
rustwx sample-point-timeseries --date 20260427 --cycle 0 --lat 40.802 --lon -124.164 --forecast-hour-end 6
rustwx-studio
```

`rustwx-studio` runs a no-AI browser UI directly from the `rustwx` wheel. The
first surface includes every model/product advertised by
`agent_capabilities_json`, GOES satellite rendering through
`render_goes_satellite_json`, click-to-sounding, pressure VolumeStore
cross-sections, WxStore export/import/plotting, point time-series sampling
through `sample_point_timeseries_json`, and NEXRAD rendering when the optional
`radar_export` binary is available on `PATH` or via `--bin-dir`.
Studio launches generation through a local background job queue, so larger
domain/product batches can be monitored from the browser while outputs stream
into the configured artifact directory.
The **Prepare Data** action and `prepare_model_data_json` API warm the shared
GRIB cache for selected products, forecast hours, model runs, and sources before
plotting, so subsequent map renders reuse cached subsets. The same workflow is
available from the console as `rustwx prepare-data`.
Interactive soundings and cross sections use a pressure VolumeStore when the
optional store builder/renderers are available: the first request warms the
model/domain/hour cache, and subsequent clicks render from the store rather than
re-decoding pressure GRIBs.

`render-maps` accepts mixed product slugs and routes them to the appropriate
direct, light derived, heavy ECAPE-derived, or HRRR windowed product path. Heavy
ECAPE slugs such as `sbecape`, `mlecape`, `muecape`, ECAPE/CAPE ratios, NCAPE,
ECIN, and ECAPE EHI/SCP/STP use the canonical `derived_batch` ECAPE path; they
do not require callers to discover or run separate binaries.

When `cache_dir` / `--cache-dir` is omitted, the agent API uses a shared
`rustwx_outputs/cache` fetch/decode cache, or `RUSTWX_CACHE_DIR` when that
environment variable is set. The cache is intentionally independent of
`out_dir` and map bounds, so city or bbox sweeps can reuse the same upstream
GRIB fetches while writing PNGs into different output folders.

MCP servers should call these stable Python/CLI entry points instead of invoking
internal proof binaries.

`render_glm_lightning_json` / `rustwx render-lightning` reads GOES GLM
`OR_GLM-L2-LCFA_*.nc` files, renders native Rust projected flash maps, and
writes a JSON flash artifact with lat/lon, time, energy, and area fields for
agent consumption.

`sample_point_timeseries_json` samples native model fields at a lat/lon over a
forecast-hour range for meteograms and point-and-click agent tools. The default
variable set is HRRR-meteogram ready: 2 m T/Td/Tw/RH, 10 m wind/gust, hourly
and accumulated QPF, low/mid/high clouds, MSLP, VPD, HDW, and the fire-weather
composite. It uses rustwx's GRIB/idx fetch path and shared cache rather than
cfgrib/xarray.

Every new projected helper has both a Python-object entry point and a `_json` variant:

- Python-object entry points accept either a JSON string or a JSON-serializable Python `dict`
- `_json` entry points keep returning pretty JSON strings for low-friction interop

## Projected map API

The projected map surface is generic and public-facing. The caller supplies:

- `lat`, `lon`, `field` as `numpy.ndarray` 2-D arrays
- a render spec with product metadata, color scale, layout, and projection metadata
- optional contour, overlay, and wind layers

`render_projected_map(...)` writes the PNG and returns a Python `dict` with:

- typed `projection`, `extents`, `layout`, and `layers` sections
- legacy `pixel_bounds`, `data_extent`, `valid_data_extent`, and `projection_info` keys for compatibility

## Minimal example

```python
import rustwx

print(rustwx.list_models_json())
```

## Point time-series example

```python
import json
import rustwx

report = rustwx.sample_point_timeseries_json(json.dumps({
    "model": "hrrr",
    "date_yyyymmdd": "20260427",
    "cycle_utc": 0,
    "source": "nomads",
    "lat": 40.802,
    "lon": -124.164,
    "forecast_hour_start": 0,
    "forecast_hour_end": 6,
    "variables": ["temperature_2m_c", "relative_humidity_2m_pct", "wind_speed_10m_ms", "hdw"],
}))
print(report)
```

## Projected render example

```python
import rustwx

spec = {
    "output_path": "example.png",
    "product_key": "Example",
    "field_units": "dBZ",
    "scale": {
        "kind": "palette",
        "palette": "reflectivity",
        "levels": [5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70],
        "extend": "Both",
    },
    "projection": {
        "map_proj": 1,
        "truelat1": 30.0,
        "truelat2": 60.0,
        "stand_lon": -97.0,
        "cen_lat": 38.0,
        "cen_lon": -97.0,
    },
    "width": 1100,
    "height": 850,
    "basemap_style": "none",
}

metadata = rustwx.render_projected_map(spec, lat, lon, field)
print(metadata["projection"]["kind"])
print(metadata["pixel_bounds"])
```

## Geometry and overlay metadata example

```python
surface = {
    "projection": spec["projection"],
    "width": 1100,
    "height": 850,
    "visual_mode": "filled_meteorology",
    "basemap_style": "filled",
}

geometry = rustwx.describe_projected_geometry(
    surface,
    lat,
    lon,
    include_projected_domain=False,
)
overlays = rustwx.build_projected_basemap_overlays(
    surface,
    lat,
    lon,
    include_geometry=False,
)

print(geometry["extents"]["padded"])
print(overlays["counts"])
```

## Cross-section request normalization example

`normalize_cross_section_request(...)` does not render a cross-section yet. It validates and fills defaults for a future shared cross-section API surface.

```python
xsect = rustwx.normalize_cross_section_request(
    {
        "path": {
            "start": {"lat": 39.74, "lon": -104.99, "label": "Denver"},
            "end": {"lat": 41.88, "lon": -87.63, "label": "Chicago"},
        },
        "field": {"product_key": "temperature", "field_units": "degC"},
    }
)

print(xsect["path_metrics"])
print(xsect["request"]["axis"])
```

## Current limits

- projected rendering still expects caller-owned arrays
- cross-section support is validation/normalization only in this crate
- `render_maps_json` covers model fetch/download/render orchestration for
  direct, derived, heavy ECAPE-derived, and HRRR windowed map products
- `sample_point_timeseries_json` is a point data primitive; consumer-specific
  meteogram styling stays outside the Python wheel
- sounding rendering expects a caller-supplied validated column; model fetch and
  lat/lon extraction live in the Rust CLI for now

