The forecast engine

Point @azohra/meteo.forecast at your launches and it publishes the forecasts: it fetches each model's newest run, derives the soaring numbers for every site in your catalogue, and writes plain JSON files wherever you host them — on a schedule you own, with one CLI.

Below is one of its runs — an HRDPS forecast for three sites. Browse any file in it.

data-sample/runs.json · 170 B · the whole file

{
  "schemaVersion": 1,
  "runs": {
    "hrdps-continental": {
      "referenceTime": "2026-08-12T12:00:00Z",
      "generatedAt": "2026-08-12T18:22:29.601Z"
    }
  }
}
the smallest published document: the newest run per model, written by meteo forecast runs-index

The published dataset

Point the engine at a site catalogue and an output directory, and this tree is what lands there. This one is an HRDPS continental run over three test sites, served at meteo.azohra.com/data-sample/ — curl any path in it.

meteo.azohra.com/data-sample/

  • data-sample/
  • models.json10.1 kBthe model catalogue: grid, cadence, horizon, capabilities, sunset dates
  • runs.json170 Bthe newest run per model — printed whole above
  • sites.json530 Bthe site catalogue this run was built from
  • site-context.json5.5 kBmeasured terrain per site: elevation, slope, aspect, relief, land cover — sources named
  • hrdps-continental/
  • manifest.json556 Bthis run: reference time, hours, sites, and what the build cost
  • sites/
  • test-hill.json10.7 kBone site document — excerpted at right
  • test-ridge.json7.7 kB
  • test-valley.json12.6 kB
  • history/
  • test-hill/2026-08.jsonl.gz2.0 kBappend-only month archive: one gzip member per published run
  • test-hill/2026-08.index.json273 Bsidecar index: byte offset and length per member
  • test-ridge/2026-08.jsonl.gz1.6 kB
  • test-ridge/2026-08.index.json273 B
  • test-valley/2026-08.jsonl.gz2.3 kB
  • test-valley/2026-08.index.json273 B

hrdps-continental/sites/test-hill.json · excerpt, pretty-printed

{
  "schemaVersion": 2,
  "model": "hrdps-continental",
  "run": {
    "referenceTime": "2026-08-12T12:00:00Z",
    "generatedAt": "2026-08-12T18:22:29Z"
  },
  "site": {
    "id": "test-hill",
    "name": "Test Hill",
    "latitude": 49.4581,
    "longitude": -117.3956,
    "modelElevationM": 1180.8,
    "timeZone": "America/Vancouver"
  },
  "semantics": {
    "gust": "hourMax",
    "precipitation": "windowMeanRate"
  },
  "hours": [
    … 7 earlier hours …
    {
      "validAt": "2026-08-12T20:00:00Z",
      "surface": { … 12 surface quantities … },
      "levels": [ … 7 pressure levels, 875–600 hPa … ],
      "derived": {
        "boundaryLayerTopM": 2136.9,
        "thermalVelocityMps": 1.8,
        "cloudBaseM": 2362.8,
        "usableLiftTopM": 2362.8
      }
    }
  ]
}
Between the run's first and last published hour, thermalVelocityMps goes 0 →1.8 and usableLiftTopM goesnull → 2362.8 — derived by the engine from the sampled fields. The equations behind every derived quantity are in Meteogram derivations. The full file: test-hill.json.

The documents above total 54.6 kB. manifest.stats records what building them cost the engine: 717 provider downloads, 1.44 GB fetched,106 s, 28 retries.

From model cycle to browser

Publication flow from model cycle to browser A five-stage sequence from upstream forecast publication through completeness probing, model-specific building, one static publication, and a consumer consistency check.
The upstream model publishes a cycle; the engine probes it for completeness, builds, and publishes once — the consumer's consistency check closes the loop. The walk-through is Forecast architecture.
Two transports, one job A comparison of NOAA indexed byte ranges and ECCC whole-domain streaming. Four HRRR records are located at byte offsets read from the repository fixture for 2026-08-06 12Z; ECCC builders stream one file at a time, sample configured sites, and discard the file.
How the provider bytes arrive: NOAA models are read as .idx-indexed byte ranges; ECCC models stream one whole-domain file at a time, sampled in memory and discarded. The reference is Provider transports.

The CLI

Installing the package puts the meteo binary on your path. Six commands: meteo forecast <build|migrate|terrain|runs-index|freshness|catalogue>.

a first build

$ pnpm add @azohra/meteo.forecast

# plan it first: prints what it would build, fetches and writes nothing
$ pnpm exec meteo forecast build --model hrdps-continental \
    --sites ./sites.json --output ./public/data --dry-run

the build behind the dataset above

$ pnpm --dir forecast build
$ node forecast/dist/cli.js forecast build --model hrdps-continental \
    --sites scenarios/catalog/sites.json \
    --output site/public/data-sample --max-steps 8

Drop --dry-run and the engine fetches the newest complete run and writes the tree shown above. --max-steps 8 is what holds the dataset above to eight of the model's 48 hourly steps.

Engine, not instance

The engine is a package, not a hosted service. A running instance — the cron schedule that triggers builds, the site catalogue it builds from, the bucket the documents land in and its credentials — is yours to own; none of it ships in the package. acrophobia.ca runs the reference instance. To stand up your own, Run one model walks the first build end to end; the engine's reference is the forecast documentation.