Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,19 @@

All notable changes to `mostlyright`. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); the project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.7.0] — 2026-06-12 — GEFS/CFS ensemble member selection

Minor release: `forecast_nwp()` gains a `member=` ensemble-member selector for GEFS and CFS.

### Added
- **`member=` ensemble selector on `forecast_nwp()` for GEFS + CFS** ([#75](https://github.com/mostlyrightmd/mostlyright-sdk/pull/75), closes [#74](https://github.com/mostlyrightmd/mostlyright-sdk/issues/74)). `docs/forecasts.md` documented `member=` for GEFS but the public function never exposed it — the path builders (`ge{member}` / CFS `6hrly_grib_{member}`) supported member selection all along. `forecast_nwp(station, "gefs", member="p05")` now fetches a specific ensemble member; valid members are validated against the closed enums (GEFS: `c00` control + `p01`..`p30` perturbations + `avg`/`spr` statistical products; CFS: `01`..`04`) with a loud `ValueError` for any other model or out-of-enum value, raised before the `[nwp]` extra imports. `member=None` (default) stays byte-identical to v1.6.0 behavior. Works on both the single-cycle path and `cycle_range_start`/`cycle_range_end` backfills. The output schema is unchanged (no `member` column — selector only; tracked as future work). TS twin: `ForecastNwpOptions` gains `readonly member?: string` (signature-forward; TS NWP execution remains v2.0+).

### Changed
- **`mostlyrightmd[research]` extra now pins `mostlyrightmd-weather>=1.7.0`** (was `>=1.6.0`): the core `forecast_nwp()` wrapper threads `member=` to the weather impl, a kwarg introduced in weather 1.7.0. Default (`member=None`) calls remain call-compatible with older weather installs — the wrapper only passes the kwarg when explicitly set.

### Notes
- Dual version bump: PyPI `1.7.0` (`mostlyrightmd`, `mostlyrightmd-weather`, `mostlyrightmd-markets`) and npm `vts-1.7.0` (`@mostlyrightmd/core`, `@mostlyrightmd/weather`, `@mostlyrightmd/markets`, `mostlyright`).

## [1.6.0] — 2026-06-06 — Open-Meteo forecast-join correctness, NWP fields, OM rate-limiting + research() docs

Minor release bundling two correctness fixes and two feature PRs.
Expand Down
34 changes: 32 additions & 2 deletions docs/forecasts.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,14 @@ rows roll into the correct calendar settlement.
| `hrrr` | ✓ wired | CONUS 3km | hourly | 2014-07-30 | High-resolution rapid refresh |
| `hrrrak` | ✓ wired | Alaska 3km | 3-hourly | 2018-01-01 | HRRR for Alaska |
| `gfs` | ✓ wired | Global 0.25° | 6-hourly | 2021-01-01 | Standard global model |
| `gefs` | ✓ wired | Global 0.5° ensemble (32 members) | 6-hourly | 2017-01-01 | Default member `c00`; opt in via `member=` |
| `gefs` | ✓ wired | Global 0.5° 31-member ensemble (`c00` + `p01`..`p30`) plus `avg`/`spr` statistical products | 6-hourly | 2017-01-01 | Default member `c00`; opt in via `member=` (e.g. `member="p05"`) |
| `gdas` | ✓ wired | Global 0.25° (short-range) | 6-hourly | 2021-01-01 | GFS analysis system |
| `nbm` | ✓ wired | Regional blend | hourly | 2020-01-01 | National Blend; `fxx=0` auto-bumps to `1` |
| `rap` | ✓ wired | CONUS 13km | hourly | 2020-01-01 | Rapid refresh |
| `rrfs` | ✓ wired | CONUS 3km | hourly | 2024-01-01 | HRRR successor (pre-operational) |
| `rtma` | ✓ wired | CONUS 2.5km analysis | hourly | 2024-01-01 | Real-time mesoscale analysis (`fxx=0` only) |
| `urma` | ✓ wired | CONUS 2.5km analysis | hourly | 2024-01-01 | Un-Restricted MA (`fxx=0` only) |
| `cfs` | ✓ wired | Global 1° (4-member) | 6-hourly | 2011-01-01 | Climate Forecast System |
| `cfs` | ✓ wired | Global 1° 4-member ensemble (`01`..`04`) | 6-hourly | 2011-01-01 | Climate Forecast System; default member `01`, opt in via `member=` (e.g. `member="03"`) |

All 11 NCEP-family models are end-to-end wired in v1.0.

Expand Down Expand Up @@ -207,6 +207,36 @@ df = pd.concat(frames, ignore_index=True)
BDP depths are documented above; older cycles raise
`HistoricalDepthError`.

### Ensemble members (`member=`)

The ensemble models **GEFS** and **CFS** accept a `member=` selector
(issue #74). It threads to the path builder so you fetch a specific
ensemble member instead of the default control run:

```python
from mostlyright.forecasts import forecast_nwp

# GEFS perturbation member p05 (default is the c00 control run)
df = forecast_nwp(station="KNYC", model="gefs", member="p05")

# CFS member 03 (default is 01)
df = forecast_nwp(station="KNYC", model="cfs", member="03")
```

- **GEFS** members: `c00` (control, default), `p01`..`p30`
(perturbations), plus `avg` / `spr` statistical products — 33 values.
- **CFS** members: `01`..`04` (default `01`).
- `member=` is **only** valid for `gefs` / `cfs`. Passing it for any
other model raises `ValueError`. An out-of-enum member value also
raises `ValueError` listing the valid members. Both errors fire before
the `[nwp]` extra is imported.
- `member=None` (the default) is byte-identical to pre-#74 behavior — no
`member` is threaded and the path-builder default is used.

> **Note:** `member=` does not (yet) add a `member` column to the output
> DataFrame — it selects which member's grid is fetched. A per-row
> `member` column is tracked as future work.

### Settlement-day envelope (Mode 2)

`research(include_forecast=True, forecast_models=[...])` fetches a
Expand Down
2 changes: 1 addition & 1 deletion packages-ts/core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@mostlyrightmd/core",
"version": "1.6.0",
"version": "1.7.0",
"description": "TypeScript SDK core for quants, ML pipelines, and AI agents: types, schemas, validators, temporal-safety primitives, and the research() join over weather data + prediction-market settlements. Local-first, no hosted backend.",
"keywords": [
"weather",
Expand Down
2 changes: 1 addition & 1 deletion packages-ts/markets/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@mostlyrightmd/markets",
"version": "1.6.0",
"version": "1.7.0",
"description": "Prediction-market data for TypeScript / Node — Kalshi NHIGH/NLOW weather-contract resolvers, Polymarket discovery + settlement, and Kalshi + Polymarket trade history. For quants, backtesting, and ML training pipelines.",
"keywords": [
"kalshi",
Expand Down
2 changes: 1 addition & 1 deletion packages-ts/meta/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "mostlyright",
"version": "1.6.0",
"version": "1.7.0",
"description": "Public-data SDK for TypeScript — one import for quants, ML pipelines, and AI agents. Adapters ship weather (METAR, ASOS, GHCNh, NWS CLI) and prediction-market settlements (Kalshi NHIGH/NLOW, Polymarket) today; SEC filings, Federal Reserve series, court filings, FDA approvals, and equities are next. Local-first, no hosted backend.",
"keywords": [
"weather",
Expand Down
2 changes: 1 addition & 1 deletion packages-ts/weather/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@mostlyrightmd/weather",
"version": "1.6.0",
"version": "1.7.0",
"description": "Weather data for TypeScript / Node — live METAR (AWC), ASOS archive (IEM), historical observations (GHCNh), and NWS climate text products (CLI). For quants, ML training pipelines, and weather-bot agents. Direct public-API access, no hosted backend.",
"keywords": [
"weather",
Expand Down
6 changes: 6 additions & 0 deletions packages-ts/weather/src/forecasts/nwp-stub.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@ export interface ForecastNwpOptions {
readonly fxx?: number;
/** Force a mirror (e.g. `"aws_bdp"`). */
readonly mirror?: string;
/**
* Ensemble member id (e.g. GEFS `"p05"`, CFS `"03"`). Mirrors the
* Python `member=` kwarg (issue #74); only meaningful for GEFS / CFS.
* Signature-forward only — TS NWP execution lands in v2.0+.
*/
readonly member?: string;
}

/**
Expand Down
13 changes: 12 additions & 1 deletion packages-ts/weather/tests/forecasts/nwp-stub.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { describe, expect, it } from "vitest";

import { DataAvailabilityError, NwpNotAvailableError } from "@mostlyrightmd/core";

import { forecastNwp } from "../../src/forecasts/index.js";
import { type ForecastNwpOptions, forecastNwp } from "../../src/forecasts/index.js";

describe("forecastNwp (Phase 21 21-07 messaging)", () => {
it("raises NwpNotAvailableError (subclass of DataAvailabilityError)", async () => {
Expand Down Expand Up @@ -115,4 +115,15 @@ describe("forecastNwp (Phase 21 21-07 messaging)", () => {
// Exercise one call to lock the runtime behavior.
await expect(forecastNwp("KNYC", models[0])).rejects.toThrow();
});

it("ForecastNwpOptions accepts an optional member (issue #74 parity)", async () => {
// Compile-level check: `member?` must exist on ForecastNwpOptions, or
// tsc fails. Runtime still throws the v1.x stub error.
const opts: ForecastNwpOptions = { member: "p05" };
await expect(forecastNwp("KNYC", "gefs", opts)).rejects.toThrow(NwpNotAvailableError);
// Inline-literal form mirrors the Python call shape.
await expect(forecastNwp("KNYC", "cfs", { member: "03" })).rejects.toThrow(
NwpNotAvailableError,
);
});
});
12 changes: 7 additions & 5 deletions packages/core/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "mostlyrightmd"
version = "1.6.0"
version = "1.7.0"
description = "Python SDK for quants, ML engineers, and AI agents — one interface to public data. Adapters ship weather + prediction-market settlements (Kalshi NHIGH/NLOW, Polymarket) today; SEC filings, Federal Reserve series, court filings, FDA approvals, and equities are next. Schema-versioned, leakage-free, local-first. Imports as `mostlyright`."
readme = "README.md"
license = "MIT"
Expand Down Expand Up @@ -72,10 +72,12 @@ parquet = [
# pandas upper bound aligned with `parquet` extra at <4.0; both backends
# are exercised by the dual-pandas CI matrix.
research = [
# >=1.6.0: research() now calls fetch_open_meteo(variables=...), a kwarg
# introduced in mostlyrightmd-weather 1.6.0 (#64). An older weather pin would
# raise TypeError on research(..., forecast_source="open_meteo") (codex P1).
"mostlyrightmd-weather>=1.6.0,<2.0",
# >=1.7.0: forecast_nwp() threads member= to the weather impl, a kwarg
# introduced in mostlyrightmd-weather 1.7.0 (#74) — an older weather would
# TypeError on forecast_nwp(..., member=...). (Supersedes the >=1.6.0 floor
# for fetch_open_meteo(variables=...), #64 codex P1; default calls stay
# skew-tolerant via the core wrapper's conditional member threading.)
"mostlyrightmd-weather>=1.7.0,<2.0",
"pyarrow>=17.0,<24.0",
"pandas>=2.2,<4.0",
]
Expand Down
20 changes: 19 additions & 1 deletion packages/core/src/mostlyright/forecasts.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ def forecast_nwp(
cycle_range_end: datetime | None = None,
fxx: int | None = None,
mirror: str | None = None,
member: str | None = None,
client: httpx.Client | None = None,
) -> pd.DataFrame:
"""Fetch an NWP forecast from NOAA Big Data Program direct-fetch.
Expand All @@ -149,6 +150,12 @@ def forecast_nwp(
fxx: Forecast hour ahead of ``cycle``. Default ``1``.
mirror: Force a specific NOAA BDP mirror (``"aws_bdp"`` or
``"nomads"``). Default: try AWS then NOMADS.
member: Ensemble member id — only valid for the member-capable
models GEFS (``"c00"``/``"p01"``..``"p30"``/``"avg"``/``"spr"``,
default ``"c00"``) and CFS (``"01"``..``"04"``, default
``"01"``). ``None`` (default) is byte-identical to today.
Validation + the valid-member enums live in the weather impl;
this wrapper passes ``member`` straight through (issue #74).
client: Reuse an ``httpx.Client`` for connection pooling.

Returns:
Expand All @@ -157,7 +164,9 @@ def forecast_nwp(
Raises:
ValueError: ``model`` not in :data:`SUPPORTED_NWP_MODELS` and
not a reserved ECMWF id; ``fxx`` is negative; ``cycle`` is
naive; ``mirror`` outside the supported set.
naive; ``mirror`` outside the supported set; ``member`` set
for a non-member model or not a valid member of the model's
ensemble (validated in the weather impl).
NwpModelNotAvailableError: ``model`` is a reserved ECMWF id.
SourceUnavailableError: ``[nwp]`` optional extra not installed.
NoLiveForNwpError: every wired NOAA BDP mirror failed.
Expand Down Expand Up @@ -261,6 +270,14 @@ def forecast_nwp(
source=f"nwp.{model}",
) from None

# Issue #74 cross-version skew guard: thread ``member`` only when the
# caller actually set it. Passing ``member=member`` unconditionally would
# make EVERY core-wrapper call TypeError against an older
# mostlyrightmd-weather whose impl predates the kwarg (core 1.7.0 +
# weather <=1.6.0 outside the [research] extra's floor). With the guard,
# default calls stay call-compatible across the skew; only explicit
# ``member=`` callers on an old weather see the loud TypeError.
member_kwargs: dict[str, str] = {} if member is None else {"member": member}
return _impl(
station,
model,
Expand All @@ -270,4 +287,5 @@ def forecast_nwp(
fxx=fxx,
mirror=mirror,
client=client,
**member_kwargs,
)
41 changes: 41 additions & 0 deletions packages/core/tests/test_forecast_nwp_schema_phase17.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,3 +126,44 @@ def test_rtma_default_call_does_not_raise_analysis_guard() -> None:
)
except Exception:
pass


# ---------------------------------------------------------------------------
# Issue #74 — core wrapper forwards member= to the weather impl
# ---------------------------------------------------------------------------
def test_core_wrapper_forwards_member_to_impl() -> None:
"""The public ``mostlyright.forecasts.forecast_nwp`` wrapper must accept
``member=`` and forward it verbatim to the weather impl. Patch the
delegation target (``mostlyright.weather.forecast_nwp.forecast_nwp``)
with a Mock so no network / [nwp] extra is required."""
from unittest.mock import Mock, patch

from mostlyright.forecasts import forecast_nwp

fake_impl = Mock(return_value="sentinel-df")
with patch("mostlyright.weather.forecast_nwp.forecast_nwp", fake_impl):
result = forecast_nwp("KNYC", "gefs", member="p05")

assert result == "sentinel-df"
fake_impl.assert_called_once()
_, kwargs = fake_impl.call_args
assert kwargs.get("member") == "p05"


def test_core_wrapper_omits_member_kwarg_by_default() -> None:
"""Cross-version skew guard (#74): a default call (no ``member=``) must
NOT pass a ``member`` kwarg to the weather impl, so core stays
call-compatible with a pre-1.7.0 mostlyrightmd-weather whose impl lacks
the parameter. Mirrors the weather-side Test D at the delegation layer."""
from unittest.mock import Mock, patch

from mostlyright.forecasts import forecast_nwp

fake_impl = Mock(return_value="sentinel-df")
with patch("mostlyright.weather.forecast_nwp.forecast_nwp", fake_impl):
result = forecast_nwp("KNYC", "gefs")

assert result == "sentinel-df"
fake_impl.assert_called_once()
_, kwargs = fake_impl.call_args
assert "member" not in kwargs
2 changes: 1 addition & 1 deletion packages/markets/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "mostlyrightmd-markets"
version = "1.6.0"
version = "1.7.0"
description = "Prediction-market data for Python — Kalshi NHIGH/NLOW weather-contract resolvers, Polymarket discovery + settlement, and Kalshi + Polymarket trade history. For quants, backtesting, and ML training pipelines. Imports as `mostlyright.markets`."
readme = "README.md"
license = "MIT"
Expand Down
2 changes: 1 addition & 1 deletion packages/weather/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "mostlyrightmd-weather"
version = "1.6.0"
version = "1.7.0"
description = "Weather data for Python — live METAR (AWC), ASOS archive (IEM), historical observations (GHCNh), and NWS climate text products (CLI). For quants, ML training pipelines, and weather-bot agents. Direct public-API access, no hosted backend. Imports as `mostlyright.weather`."
readme = "README.md"
license = "MIT"
Expand Down
Loading
Loading