Skip to content

Builder contract

A builder translates one verified provider feed into the shared source shape, then delegates profile derivation and publication. It does not redefine the public JSON shape or renderer behaviour.

  1. Catalogue agreement. The slug, kind, levels, cadence, horizon, capability presence, and field semantics match models.json.
  2. Complete-run selection. A builder selects a provider run only after its declared final product is available, and skips an already-published referenceTime without rewriting output.
  3. Domain honesty. A sample too far from a site signals out-of-domain clamping; the build fails rather than publishing a boundary value.
  4. Unit and direction normalization. Source values reach the profile contract’s units and meteorological FROM-direction before publication.
  5. Absence stays absent. Missing records, masked sentinels, and unsupported optional fields are omitted, never converted to zero.
  6. Semantics are supplied, not inferred. The verified builder passes gust, precipitation, and (where the model carries smoke) smoke semantics into deriveSiteForecast.
  7. Derived values have one authority. Builders supply source fields; forecast/src/derive.ts supplies derived.*.
  8. Publication is deterministic at the edge. Shared rounding and JSON writers own precision and serialized shape.

A builder implements only what is genuinely model-specific — field tables, URLs, and provider quirks — and composes builders/common.ts for the walk every build shares: forecast-slot timestamps, the bounded fetch pool, and the source-hour and level skeletons whose NaN seeds a never-ran fetch task leaves behind for the serializer to refuse loudly. Publication authority lives in derive.ts and publish.ts; a builder never open-codes the profile, history, or manifest writes.

builder source published profile
temperatureC ─┐ surface.temperatureC
dewPointDepressionC ├─→ surface.dewPointC
heat fluxes + sampled levels ├─→ derived.*
optional provider fields └─→ optional surface/level fields

The source hour is internal and may carry provider-facing intermediate names such as dew-point depression. The published contract is the stable boundary. Do not expose a builder intermediate merely to avoid an appropriate derivation.

Ensemble builders derive every member independently, then aggregate matching numeric positions. run.members is total membership; EnsembleValue.members counts contributors at that position and can be lower when null or censored member values are excluded. Wind direction uses circular aggregation. Height censoring uses ceiledMembers only on positions where the forecast engine records a column ceiling.

Focused builder tests should use committed fixtures or injected transport responses. They must not require live provider access; verification establishes facts and tests make behaviour repeatable.