Build a point-in-time research table
A point-in-time table contains only values that were available before each simulated decision. Read each source directly, keep its availability timestamp, and make the cutoff condition visible in your dataframe code.
01 · Define the decision
Section titled “01 · Define the decision”Each row needs an entity, a decision time, and the outcome you will evaluate later. Store the cutoff as a timezone-aware UTC timestamp.
import pandas as pd
decision_time = pd.Timestamp("2025-04-01T00:00:00Z")Do not use the period a value describes as its publication time. March CPI can describe March, be published in April, and be revised in May.
02 · Read every published vintage
Section titled “02 · Read every published vintage”from mostlyright import economy
history = economy.series( "cpi", "2024-01-01", "2025-06-01", vintages="all",)
available = history.loc[ pd.to_datetime(history["knowledge_time"], utc=True) <= decision_time]Keep revisions as separate rows until after the cutoff filter. Selecting the latest value first would let a later revision enter an earlier decision.
When you only need one indicator at one cutoff, use the SDK’s stable shortcut:
then = economy.snapshot("cpi", as_of=decision_time.to_pydatetime())snapshot() raises IndicatorNotYetReleasedError when no qualifying value
existed. It does not substitute a later release.
03 · Join weather rows explicitly
Section titled “03 · Join weather rows explicitly”Weather sources use different timestamps for different facts. Filter forecasts by their issuance time, then join them to the local-standard day they predict.
from mostlyright import weather
forecasts = weather.forecasts( "KNYC", "2025-01-01", "2025-01-31",)labels = weather.daily_summaries( "KNYC", "2025-01-01", "2025-01-31",)
forecasts["issued_at_utc"] = pd.to_datetime( forecasts["issued_at_utc"], utc=True)features = forecasts.loc[forecasts["issued_at_utc"] <= decision_time]
table = features.merge( labels, on=["station", "local_standard_date"], how="inner", validate="many_to_one",)Choose the last eligible forecast run per station and target day before the
merge when your model expects one row per decision. Keep issued_at_utc,
source, and the label’s source fields in the result so the join can be
audited.
TypeScript
Section titled “TypeScript”TypeScript returns the same descriptive row fields in DataResult.rows:
import { series, snapshot } from "@mostlyrightmd/economy";
const history = await series( "cpi", new Date("2024-01-01T00:00:00Z"), new Date("2025-06-01T00:00:00Z"), { vintages: "all" },);
const cutoff = new Date("2025-04-01T00:00:00Z");const available = history.rows.filter( (row) => new Date(row.knowledge_time) <= cutoff,);
const then = await snapshot({ indicator: "cpi", asOf: cutoff });Use your dataframe or query engine for multi-source joins. Keep the cutoff and source timestamps as columns rather than hiding them in an application-wide default.
Verify the result
Section titled “Verify the result”Before training or evaluating a model:
- Assert every input availability timestamp is at or before its decision time.
- Split rows chronologically, not randomly.
- Recompute rolling features inside each training fold.
- Fit preprocessing only on the training rows in that fold.
- Compare historical and live schemas and source tags before deployment.
The SDK can check timestamps that a source supplies. It cannot prove an upstream timestamp is correct or detect leakage added later in your feature engineering.
See also
Section titled “See also”- Temporal safety: event time, knowledge time, and decision time
- Economy: revisions and cutoff snapshots
- Forecasts: issuance and target times
- Sources & provenance: source identity on returned rows