Skip to content
ClearFeature

Guide

Point-in-time correctness: what did the model actually know?

The single most expensive subtlety in production ML training data — explained from first principles, with the clocks named precisely.

Definition

A training dataset is point-in-time (PIT) correct when every row contains only information that was available to the system at that row’s decision moment. Not information that existed somewhere in the world. Not information that describes an earlier time. Information the system could actually have read, then.

The definition sounds pedantic until you see what violating it does: a model trained on late-arriving information learns patterns its production counterpart can never use. Offline metrics look excellent; live performance disappoints; and the gap is invisible in code review, because every individual query looks reasonable.

Why historical joins leak the future

The naive way to build training data is a join on time: for each historical decision, pull the most recent source records by their timestamps. The flaw is that a record’s timestamp usually says what the record describes — not when your system received it. A bureau report describing Monday may have arrived Thursday. The naive join hands your Tuesday training row a report your Tuesday production system had not seen. The model trains with a few days of clairvoyance, consistently, across millions of rows.

This failure is systematic, not random — which is what makes it dangerous. Random noise hurts offline metrics; systematic leakage improves them, so the better your leaked backtest looks, the more confidently you ship a model that cannot repeat it.

The three clocks

Untangling this requires naming the clocks separately. ClearFeature’s data model uses these terms; the concepts apply to any system.

Event time

event_ts (or report_ts / data_ts): the time the data describes. A bureau report’s event time is the moment the credit picture it captures was true. A market tick’s event time is when the trade happened.

Availability time

available_at: when your system could first know the data — when the feed delivered, the file landed, the API responded. Availability is a property of your ingestion reality, not of the world. Two systems consuming the same vendor can have different availability for the same event.

Observation time

observation_ts (decision time): the moment the historical decision was made — the moment each training row is anchored to, and the moment that defines what “the past” means for that row.

A useful fourth clock, for auditability rather than eligibility, is computation time (calc_ts): when the platform actually computed a feature value.

A concrete timeline

event_ts · 10:00

what the data describes

observation_ts · 10:05

the decision is made

available_at · 10:15

the system could first know it

The report describes 10:00 — but it arrived at 10:15. The 10:05 decision must not use it.

The eligibility rule that falls out of the three clocks is one sentence: a value may enter a training row only if it belongs to the past and was available by the observation time. In the timeline above, the report fails the second condition — it describes the past, but the system learned it too late.

Why event time alone is insufficient

Filtering on event_ts < observation_ts feels safe and is the most common bug in training-data construction. It enforces “the data describes the past” while ignoring “the system knew it.” Everything with delivery lag — bureau files, vendor feeds, operational reports, revised forecasts — slips through, describing the past while arriving from the future. Only available_at <= observation_ts closes the gap.

Multiple sources, multiple lags

Real decisions read several sources, each with its own delivery behavior: the application data was instant, the bureau report took minutes, the income verification took days. PIT correctness must be enforced per source, per row. A dataset can be perfectly correct with respect to one source and quietly leaky through another — this is why the discipline belongs in the platform that assembles training data, not in each team’s query hygiene.

Constructing PIT training datasets

The construction that follows from the model: take the set of historical observations (entity, observation_ts, label). For each observation, select from each source the latest record with available_at <= observation_ts; execute the feature logic over exactly that information set; emit the vector. In ClearFeature this is a platform operation — fsctl build-training-dataset consumes observations and produces availability-correct rows by construction, using the same feature implementation that runs online.

Credit example

An underwriting model is trained on two years of decisions. Bureau reports in that history were delivered with hours-to-days of lag. Built on event time, the training data gives every historical decision a slightly newer credit picture than underwriting actually had — the model learns to lean on freshness that production never sees, and validation later struggles to explain a persistent offline/online gap. Built on availability, the training rows match decision reality. (ClearFeature for credit risk →)

Trading example

A forecasting model backtests beautifully: it “reacts” to market events within a minute. The research dataset was joined on event time; the vendor feed delivers with two minutes of lag. The strategy’s edge is exactly the leak. In markets, minutes of future information do not inflate a backtest — they are the backtest. (ClearFeature for energy & trading →)

Reproducibility is the same problem, reversed

PIT correctness looks forward (build honest training data); reproducibility looks backward (reconstruct what a past decision knew). Both need the same substrate: sources stored with availability, decisions anchored to observation times, and versioned feature logic. If your platform can build a PIT-correct dataset for March 3rd, it can also answer what the model knew on March 3rd — same query, different purpose. (Model reproducibility →)

How ClearFeature models it

In ClearFeature the temporal model is structural, not conventional: sources carry report_ts and available_at; training construction is anchored to observation_ts; eligibility — past and available — is enforced by the platform when datasets are built; and because the same versioned Feature Project executes historically and online, temporal correctness and implementation consistency come from one mechanism rather than two disciplines. The platform page shows where this sits in the wider execution model.

Further reading

Is your training data availability-correct?

If nothing in your stack enforces it, the honest answer is 'probably not'.