windgram
v0.22.1
Published
Contract, derivations, and reference renderer (scene graph + SVG) for windgram profile documents published by github.com/azohra/windgram.
Readme
windgram
TypeScript companion to the windgram dataset published from github.com/azohra/windgram: the published contract, the derivations that are pure functions of it, the transport that fetches the documents consistently, and the reference renderer (headless scene graph + SVG serializer).
npm install windgramThe package ships ESM with type declarations; the only dependency is zod. Nothing touches the DOM, so these APIs run identically in Node, workers, and browsers.
Sixty seconds to a chart
Discover a model from the published catalogue, load a consistent manifest/profile pair, and serialize the reference chart:
import { parseModelCatalogueJson } from "windgram/contract";
import { buildScene } from "windgram/scene";
import { renderSvg } from "windgram/svg";
import { loadProfile } from "windgram/transport";
const DATA = "https://data.meteo.azohra.com";
// Pick a model by declared capability, never by name.
const response = await fetch(`${DATA}/models.json`);
if (!response.ok) throw new Error(`models.json: HTTP ${response.status}`);
const catalogue = parseModelCatalogueJson(await response.text());
if (!catalogue) throw new Error("models.json failed contract validation");
const model = catalogue.models.find(
(entry) => entry.kind === "deterministic" && entry.capabilities.cape,
);
if (!model) throw new Error("no deterministic model with CAPE is published");
// The manifest + profile pair goes through the reference-time skew guard.
const loaded = await loadProfile({
fetch,
baseUrl: DATA,
modelSlug: model.slug,
siteSlug: "dundee",
});
if ("miss" in loaded) {
throw new Error(`${model.slug}/dundee ${loaded.miss}: ${loaded.url}`);
}
if (loaded.stale) console.warn("run still syncing across the CDN");
const timeZone = loaded.profile.site.timeZone ?? "America/Vancouver";
const svg = renderSvg(buildScene(loaded.profile, { timeZone }));The parse…Json guards return the typed document or null; transport
loaders return the typed pair or a discriminated DocumentMiss. The
first-windgram guide
walks this path step by step and shows the chart it produces.
Launches are render inputs
A profile document is launch-agnostic: its site block records where the
atmosphere was sampled and what the model's own ground there is
(site.modelElevationM), never a launch elevation — one document serves
every launch its grid cell covers. To draw the launch marker, supply the
launch yourself:
import { parseSiteContextJson } from "windgram/contract";
// The pipeline measures every catalogued site's ground truth — the
// elevation pick, terrain, and land cover — into site-context.json,
// published beside the catalogue. Pull it on your own schedule: it
// changes only when the site catalogue does, never per model run.
const contextResponse = await fetch(`${DATA}/site-context.json`);
const context = parseSiteContextJson(await contextResponse.text());
if (!context) throw new Error("site-context.json failed contract validation");
const launch = {
name: loaded.profile.site.name,
elevationM: context.sites["dundee"]!.elevation.elevationM,
};
const svg = renderSvg(buildScene(loaded.profile, { timeZone, launch }));No launch option means no marker and no launch-relative statements —
absence is honest, never an error — and the same seam feeds analysis:
AnalyzeOptions.launch keys the launch-relative findings, and
CompareOptions.launch supplies the one launch a cross-model comparison
measures against.
Entry points
One import subpath per job; each links to its guide.
| Subpath | What it gives you | Guide |
| --- | --- | --- |
| windgram/contract | Zod schemas, inferred types, and nullable parse guards for the five published document kinds | Contract validation |
| windgram/derive | Pure derivations of published state: moisture, wind, lapse, stability, shear, usable lift, day windowing, projection, alignment | Pure derivations |
| windgram/analyze | Typed findings over one profile, each carrying the thresholds and evidence that produced it | Analyze a profile |
| windgram/compare | Cross-run window agreement, height spread, and wind divergence over a member ledger where every non-vote has a stated reason | Compare model profiles |
| windgram/transport | loadProfile and loadRuns with the torn-pair skew guard and discriminated misses | Load published documents |
| windgram/history | Month-archive loaders (member-splitting gzip reader, mandatory run dedupe, the advisory sidecar index) and compareRuns — one model's runs through time. Server-side: Node, Bun, Deno; not browsers | History and run convergence |
| windgram/scene | The headless scene graph: scales, strips, series, barbs, markers, hit-testing, the key spec | Build a scene graph |
| windgram/svg | The deterministic reference serializer for chart and key, styled entirely by CSS custom-property tokens | Render SVG and a key |
Every option and token defaults to the reference look; Defaults and tokens lists the exported default maps.
Ensemble documents
Every numeric position is a Scalar = number | EnsembleValue: deterministic
models publish numbers, ensemble models publish percentile objects in the
same positions. Switch on shape, never on model name — isEnsembleValue and
isEnsembleDropout narrow one position, p50(scalar) reads the median, and
isDeterministicProfile narrows a whole document once so deterministic-only
code never touches p50(). Semantics live in
Ensemble values.
JSON Schemas for non-JS consumers
JSON Schema artifacts generated from the same zod contract ship as plain
files in the npm tarball's schema/ directory
(node_modules/windgram/schema/profile.schema.json and its four siblings)
and in the repository at
toolkit/schema/.
They are not a package export specifier — read them from the filesystem or
the repository, not through import. Their field semantics match the
TypeScript JSDoc.
Authority boundary
The pipeline (Python) owns stored values that need provider inputs or
cross-run authority: W*, boundary-layer top, cloud base, usable-lift
top, and the measured site ground truth in site-context.json (the
elevation pick, terrain analysis, and land cover). This package owns pure
functions of the published JSON: RH, TI, shear,
B/S, lapse, stability, windowing, smoothing, and consumer-parameter
projections such as a different usable-lift sink rate. A projection does not
replace the document's published value. The
project overview
maps the full boundary.
Developing
pnpm --dir toolkit test # vitest
pnpm --dir toolkit build # tsc -> dist/ (types + ESM)
pnpm --dir toolkit schemas # regenerate schema/*.jsonDocumentation and licence
Guides, data references, and teaching material live at the documentation portal. Release history lives in the repository CHANGELOG.
The code is MIT licensed. Published profiles derive from ECCC and NOAA source data; the repository licence section records the attribution requirements.
Made with <3 by Justin Watts.
