Guides
Extending dataset()
Two ways to grow the composed frame without touching the parity-critical core: station panels (dataset(stations=[…]) stacks per-station calendars into one long-format frame) and the experimental contributor registry (register your own labels-only feature block, composed in via features=[…]).
Station panels
Section titled “Station panels”Pass stations=[...] (instead of a single station=) to compose a long-format panel — one call fans out to per-station single-station dataset() calls and concatenates them. The station column comes first, caller order is preserved, and each station gets its own settlement calendar and its own label-recipe resolution (mixed-venue panels are fine).
import mostlyright
df = mostlyright.dataset( stations=["KNYC", "KLAX", "KORD"], from_date="2025-01-06", to_date="2025-01-12", label="cli",)# Long-format: the `station` column is first, then the usual daily columns.# Rows/columns are exactly the concat of the three single-station calls.print(df.head())
# Nested per-station provenance hangs off attrs["stations"].print(df.attrs["stations"]["NYC"]) # that station's flat single-call attrsimport { datasetPanel } from "mostlyright/panels";
const { rows, provenance } = await datasetPanel("2025-01-06", "2025-01-12", { stations: ["KNYC", "KLAX", "KORD"], label: "cli",});
// rows is the concatenated long-format array (station first).// TS has no `.attrs`, so per-station provenance is a sibling field.console.log(provenance.stations.NYC);Rules worth knowing:
stations=is mutually exclusive withstation=/city=/contract=/contracts=. The station list must be non-empty and unique (a duplicate would double-count rows).- Panels require the DataFrame return path in Python (
return_type="list"raises). - Mixed-venue is OK — one station on
label="cli", another sliced from a BYO frame, etc. — as long as each resolves to a valid recipe. - A BYO label frame for a panel must carry a
stationcolumn so each label maps to the right station calendar (the aligner raisesLabelAlignmentError(reason="missing_station_panel")otherwise). - In Python, the nested provenance lives at
df.attrs["stations"]keyed by the resolved (normalized) station code. In TypeScript,datasetPanel()returns{ rows, provenance }— the same map hangs offprovenance.stations(TS has no.attrsseam).
The experimental contributor registry
Section titled “The experimental contributor registry”A contributor is one function — contribute(entity, window, grain) -> {key: {prefixed_col: value}} — plus declared metadata (a column prefix, an archive_class, an injection_point, and the columns it emits). Registered contributors compose into dataset() through the features=[...] selector with no core change. The built-in "forecasts", "satellite", "cwop", and "trades" features are themselves built on this same registry.
Register a contributor
Section titled “Register a contributor”from mostlyright.experimental import contributor
@contributor( "era5_ssrd", # unique registry name + features=[...] token "ssrd_", # column prefix (must end with "_") "refetchable", # archive_class: "refetchable" | "ledger" injection_point="post_join", # "pre_aggregation" | "post_join" columns=("ssrd_mean",), # REQUIRED for third-party: the prefixed columns)def era5_ssrd(entity, window, grain, *, tz_override=None): from_date, to_date = window # ... fetch + aggregate to one row per LST settlement day ... # keyed by the documented (station, settlement_date) tuple # (a plain settlement-date string is also accepted): return {(entity, "2025-01-06"): {"ssrd_mean": 12.3}, ...}import { registerContributor } from "mostlyright/experimental";
registerContributor({ key: "era5_ssrd", // unique registry key + features token prefix: "ssrd_", // column prefix archiveClass: "refetchable", injectionPoint: "post_join", columns: ["ssrd_mean"], // REQUIRED for third-party contributors contribute: ({ entity, window, grain }) => { // return a Map keyed by settlement day (or the flattened key): return new Map([["2025-01-06", { ssrd_mean: 12.3 }]]); },});Compose it in
Section titled “Compose it in”Once registered, add the contributor’s name to features=[...]:
df = mostlyright.dataset( "KNYC", "2025-01-06", "2025-01-08", label="cli", features=["era5_ssrd"],)# df now carries an additive ssrd_mean column; label columns untouched.The labels-only firewall (unconditional)
Section titled “The labels-only firewall (unconditional)”A contributor can never emit or override a label column. The firewall forbids the reserved obs_ / cli_ / wu_ / noaa_wrh_ prefixes and the exact label / date / station names — categorically, with no flag that could ever disable it. A contributor that declares or emits a firewalled column raises a loud typed error and refuses to compose. This is the inverse of the BYO-label collision guard: labels are produced in exactly one place, and features can never shadow them.
Failure semantics
Section titled “Failure semantics”The registry distinguishes three failure modes deliberately:
| What happens | Result |
|---|---|
contribute() raises at compose time (or covers zero days) | its declared columns join as an all-NaN block, a RuntimeWarning fires, the record lands in df.attrs["contributor_errors"], and the dataset() call still succeeds |
a missing optional extra (SourceUnavailableError) | re-raised loudly — a missing [satellite] / [cwop] install is a hard, actionable error, not a silent NaN block |
| a firewall / prefix violation | raises loudly at registration/compose — it is a programming error in the contributor |
Because a raising contribute() still materializes its declared columns as all-NaN, columns never silently vanish — the horizontal concat stays a pure LEFT join (label columns and row order untouched). Inspect df.attrs["contributor_errors"] after a run to see which contributors degraded.
See also
Section titled “See also”- Choosing the label — the label axis the firewall protects.
- Research API — the
dataset()surface in full, includingfeatures=[...]. - Markets guide — the thin delegators built on
label="cli"/"daily_extremes". - → v1.15 migration — where panels, the registry, and
features=[...]all landed.