Skip to content

Profile document

A profile is the portable boundary between generation and rendering. It identifies its contract version, model, publication, site, optional provider semantics, and every chronological forecast hour. Day windows and local time remain consumer choices, with the site’s timezone echoed for new documents.

profile
├── schemaVersion + model
├── run { referenceTime, generatedAt, members? }
├── site { identity, coordinates, launch altitude, model elevation, timeZone? }
├── semantics? { gust?, precipitation? }
└── hours[]
├── validAt
├── surface
├── levels[]
└── derived
Block Representative values Contract rule
surface pressure, temperature, moisture, winds, heat fluxes, precipitation, and declared optional science fields Optional capability fields are absent, never filled with zero
levels height, temperature, dew point, wind, and optional omega/cloud fraction Entries are ascending by height; pressure is the isobaric coordinate
derived boundary-layer top, thermal velocity, cloud base, usable-lift top Computed by the pipeline from the full required inputs

run.referenceTime is model initialization; run.generatedAt identifies the publication. Ensemble documents also declare total membership once at run.members.

site.altitudeM is surveyed launch elevation (nullable in stored profiles); site.modelElevationM is grid terrain. Keep both because derived column values and display launch lines answer different questions.

The v0.4 publication wave added optional site.timeZone, echoed from the site catalogue’s required IANA zone. New documents therefore carry their own local-time context without a catalogue join. The field remains optional so older schema-1 profiles stay valid; absence means the document predates the echo — never that the launch uses UTC — and requires a caller-owned timezone choice. This paragraph is the semantics’ one home; the contract guide lists each API’s documented fallback behaviour.

The optional semantics block lets a stored document retain gust and precipitation meaning without joining the catalogue. Its absence creates no default.

Every numeric position can contain a number or an ensemble percentile object. Consumers switch on the value shape, not a model name.

read-wind.ts
import { isEnsembleDropout, isEnsembleValue, type WindgramProfile } from "windgram/contract";
export function firstWindSpeed(profile: WindgramProfile): number | null | undefined {
const speed = profile.hours[0]?.surface.windSpeedMs;
return speed === undefined
? undefined
: isEnsembleValue(speed)
? isEnsembleDropout(speed) ? null : speed.p50
: speed;
}

Per-position contributor counts and censoring are explained in Ensemble values.

Use parseWindgramProfileJson for raw stored text or parseWindgramProfile for an already-parsed value. Both return null on a rejected boundary. The package schemas and generated JSON Schema define every field constraint.

models.json is the machine-readable catalogue. It declares which models exist, their cadence and levels, and the semantics of optional capabilities. A consumer should render those declarations instead of assuming every model supplies the same fields.

See Schemas and units for the integration crosswalk. The full field-by-field contract and generated JSON Schema live with the windgram package.