Skip to content

Guides

Build a point-in-time research table

A point-in-time research table answers one question: what could have been known at each decision? Start with a target and cutoff, then align sources using their own availability timestamps.

Each row needs:

  • an entity, such as a station or contract
  • a decision cutoff
  • the outcome you want to predict

For a Kalshi daily-temperature market, the NWS CLI label provides the target:

from mostlyright import weather
target = weather.label.cli(
"KNYC",
"2025-01-06",
"2025-01-12",
)

The target acts as the table’s spine. Keep its decision_time_utc visible so every feature can be evaluated against the same cutoff.

Do not treat every timestamp as equally strong. Exact availability timestamps can support a point-in-time audit; derived or reconstructed timestamps carry a weaker guarantee. See Temporal safety for the timing model and its limits.

Use mostlyright.experimental.align() when you want to choose which sources are joined to the target:

from mostlyright.experimental import align
reports = weather.observations("KNYC")
table = align(target, reports)

Calling a supported source without dates returns a DeferredSource. align() reads the target window, fetches the source, and applies its SourceContract against the decision cutoff.

Add more sources explicitly:

forecasts = weather.forecasts("KNYC")
table = align(target, reports, forecasts)

align() can audit exact source timestamps. Reconstructed or unknown timing does not become exact just because it passes through the helper. The composition API is documented and usable, but semver-exempt while it remains experimental.

Use date-bound domain reads when you want the source rows before composition:

reports = weather.observations("KNYC", "2025-01-06", "2025-01-12")
forecasts = weather.forecasts("KNYC", "2025-01-06", "2025-01-12")
target = weather.label.cli("KNYC", "2025-01-06", "2025-01-12")

These stable reads are eager and require both from_date and to_date. observations() returns per-report METAR/SPECI rows; it does not hide a daily aggregation behind a switch.

Compose these rows yourself with pandas or Polars when you need a custom table. Keep each source’s availability timestamp and the target cutoff visible through the join.

04 · Use the built-in training-table example

Section titled “04 · Use the built-in training-table example”

weather.training_table() is a small supervised-learning example. It places a built-in settlement target beside daily observation features:

from datetime import date
from mostlyright import weather
example = weather.training_table(
"KNYC",
date(2025, 1, 6),
date(2025, 1, 12),
)

The result includes:

  • identity and timing: station, local_standard_date, decision_time_utc, label_available_time_utc, knowable_at_utc
  • target columns: daily_summary_temp_high_f, daily_summary_temp_low_f, daily_summary_report_type
  • observation features: observed_temp_high_f, observed_temp_low_f, observed_temp_mean_f, and the other observed_* aggregates

The helper is deliberately narrow: station, two inclusive dates, and label. Use it when this recipe matches your research; use explicit sources and composition when it does not.

label=TargetTypical use
"cli" (default)NWS CLI daily high/low in °FKalshi NHIGH/NLOW research
"daily_extremes"WU / NOAA-WRH daily extremes in °CPolymarket weather research
kalshi = weather.training_table(
"KNYC", "2025-01-06", "2025-01-12", label="cli"
)
polymarket = weather.training_table(
"EGLL", "2025-01-06", "2025-01-12", label="daily_extremes"
)

For a station panel, pass a list or tuple. The result stays long-form, with one row per station and local-standard day:

panel = weather.training_table(
["KNYC", "KLAX", "KMDW"],
"2024-11-01",
"2025-04-30",
)

The 1.x verbs pairs(), research(), and dataset() were removed in 2.0. See the upgrade guide for the migration map.

TypeScript mirrors the stable domain reads and the small training-table example:

import { trainingTable, weather } from "mostlyright";
const { rows: reports } = await weather.observations(
"KNYC",
"2025-01-06",
"2025-01-12",
);
const { rows: example } = await trainingTable(
"KNYC",
"2025-01-06",
"2025-01-12",
);

Both calls return a DataResult; read the data from rows. When the built-in example does not fit, compose the explicit domain rows in your application.

Split research data on time, not randomly, and recompute rolling features inside each walk-forward fold.

Common failures are typed:

  • ContractError for an invalid station, date window, source, or label
  • HttpError for upstream transport and HTTP failures
  • LeakageError when an experimental composition violates a point-in-time cutoff
  • NoDataError when a valid request has no usable rows