← Docs

Engineering the pipeline

This project publishes fresh forecast profiles for every catalogued site, every model run, around the clock — and its total infrastructure is a public GitHub repository. No server, no database, no API keys, no bill. This page records the patterns that make that work, because each one is reusable well beyond windgrams.

GitHub Actions as the compute plane

The entire pipeline is one workflow on a public repository, where Actions minutes are free. A schedule fires every 15 minutes; each builder asks its upstream "is there a run newer than the one in data/?" and exits if not. A no-new-run check costs a single request, so polling aggressively is polite. When a run has advanced, the builder fetches, derives, writes to data/, and the workflow commits.

A concurrency group serialises builds so two scheduled fires never race, and experimental legs are wired to warn instead of fail — a dark HRDPS 1 km feed must never block the 2.5 km publish. As more model builders land, each is its own step with the same contract: exit quietly on nothing-new, write to your own directory on success, fail loudly on anything malformed.

Git-scraping: the repository is the database

Every build that finds a new model run ends in exactly one commit, made by the workflow itself. That single decision buys a lot:

raw.githubusercontent.com as a zero-config CDN

Consumers read the published JSON straight from raw.githubusercontent.com/azohra/windgrams/main/... — no key, no server of ours, backed by GitHub's CDN. Two properties matter in practice:

Fetching kilobytes from gigabyte files

The single most important cost trick in the pipeline: never download a model field you don't need.

One manner worth copying regardless of transport: clients identify themselves with a real User-Agent pointing at this repository, retry 429/5xx with jittered backoff, honour Retry-After, and publish their request counts in the manifest. Free data stays free when consumers are visible and polite.

Probe for completeness before building

Model runs appear on servers file-by-file over many minutes. Fetch eagerly and you build from a half-published run. Two guards, used by every builder:

Either way the failure mode is a clean early exit; the 15-minute schedule retries soon enough.

Append-only history as concatenated gzip members

Every published run is also appended to data/history/<slug>/<year>.jsonl.gz. The trick is how: each run is compressed as an independent gzip member and appended to the file (windgrams/publish.py). The gzip format specifies that readers process concatenated members as one stream, so any standard reader — zcat, Python's gzip — sees one JSON line per model run, in order. Existing bytes are never rewritten, which keeps git happy (appends, not churn) and makes the archive safe against interrupted writes. About 12 KB per site per run buys a permanent record of every forecast ever published — the raw material for the verification studies in where this goes next.

The ULP-parity rewrite

The derivation began life as TypeScript inside a club website. Moving it into this repository meant a Python rewrite — and a rule: the rewrite must reproduce the committed output of the original to within one double ULP (unit in the last place), verified against the previously published run as a golden file before the switch. Consumers comparing this windgram against canadarasp, or against last week's published values, must never see the implementation language.

Two details made byte-identical JSON possible:

The test suite (tests/) pins the derivation with exact-value assertions, so the parity survives refactoring.

What this adds up to

A forecast product with 100% managed infrastructure: ECCC and NOAA run the models, GitHub runs the compute and the CDN, git provides the database, history, audit log, and rollback. The pipeline's only irreplaceable asset is the code and the catalogue — both of which are in front of you.