Skip to content

feat(studio): ERA5 climatology — committed product, derived ambient state, stage-1 panel (task 1.1) - #96

Merged
aliakherati merged 3 commits into
studio/devfrom
feat/era5-climatology
Aug 21, 2026
Merged

feat(studio): ERA5 climatology — committed product, derived ambient state, stage-1 panel (task 1.1)#96
aliakherati merged 3 commits into
studio/devfrom
feat/era5-climatology

Conversation

@aliakherati

Copy link
Copy Markdown
Collaborator

"I do not see ERA5 data being utilized" — fixed, end to end

The decisions existed (SCIENCE-1, BLOCKING-5); nothing implemented them. This PR is task 1.1
complete: fetch → committed product → derivation → wizard.

The committed product

data/pipelines/era5_zonal_monthly.py fetches ERA5 monthly means — 1991–2020 (WMO normal),
15 levels, 300–5 hPa, 2.5° grid (ASSUMPTION-9) — and reduces 343 MB of raw fields to a 0.1 MB
(month × level × lat) npz, committed at studio/science/data/ with a sha256 manifest. Raw ERA5 is
never redistributed; the derived product is, with attribution. The loader checksum-verifies once per
process and refuses to extrapolate outside 300–5 hPa (ADR-005).

Schema 0.4.0 — the ambient state gets a source

Same structure as the emission system:

dataset = USER (default) dataset = ERA5
entered given_temperature_k, given_h2o_ppmv (kept, inert)
derived temperature_k = given, h2o_ppmv = given interpolated at (latitude, month, pressure)

Pressure stays entered — it places the box, so it is the lookup's coordinate, not its result.
Default USER: every existing config resolves byte-identically. Provenance records the dataset
id + checksum automatically
for ERA5 runs (ProvenanceRecord.datasets, carried empty since
Phase 0, now used).

The physics tests paid for themselves before the first commit

The acceptance suite asserts the stratosphere looks like the stratosphere: tropical cold point
185–205 K, summer pole warmer than winter pole, H₂O single-digit ppmv. The first cut of the pipeline
shipped CDS's descending pressure levels into an interpolation that assumed ascending — every
lookup silently read the 300 hPa row, and T(55 hPa) came back 240 K with 414 ppmv of water.
Plausible numbers at a glance; impossible as a stratosphere.

Fixed in the pipeline (both axes sorted ascending) and refused in the loader — a non-monotonic
axis raises rather than being reordered, because the checksum pins what was read.

The headline

ERA5 zonal-mean, 30°N, June, 55 hPa:   T = 209.42 K     (the ensemble typed 210 K)
                                       H₂O = 4.15 ppmv  (typed 6.91; reanalysis dry bias → #94/MLS)
Tropical cold point (Feb, 100 hPa):    191.4 K
December at 30°N:                      207.12 K         (real seasonal cycle, derived live)

The paper's site temperature is confirmed to half a kelvin by 30 years of reanalysis.

In the wizard

Stage 1 gains the ERA5 temperature-profile panel (pressure on a log axis, the box marked on it) —
under USER it is context; under ERA5 the marker is the run's temperature. Flip "Ambient state
from"
to era5 and T/H₂O derive from latitude, month and pressure, go stale correctly when you
move them, and carry the dataset checksum into provenance.

357 Python Tier-A (+11), 55 vitest. The climatology tests run in CI — the product is
committed, so this is the first substantive science whose tests CI actually executes.

Stacked on #95. Same merge-then-rebase dance as before if GitHub flags it.

Raised in use: "I do not see ERA5 data being utilized" -- correct. SCIENCE-1 and
BLOCKING-5 decided ERA5 zonal-mean monthly; nothing implemented it. Now:

The committed product. data/pipelines/era5_zonal_monthly.py fetches ERA5 monthly means
(1991-2020 WMO normal, 15 levels 300-5 hPa, 2.5 deg -- ASSUMPTION-9) and reduces 343 MB
of raw fields to a 0.1 MB (month x level x lat) npz, committed with a sha256 manifest.
Raw ERA5 is never redistributed; the derived product is, with attribution. The loader
verifies the checksum once per process and refuses to extrapolate outside 300-5 hPa.

Schema 0.4.0. site.dataset (USER | ERA5) with the emission-system structure: entered
given_temperature_k / given_h2o_ppmv kept-but-inert, temperature_k / h2o_ppmv DERIVED.
Pressure stays entered -- it places the box, so it is the lookup's coordinate rather than
its result. Default USER: every existing config resolves byte-identically. Provenance
records the dataset id + checksum automatically for ERA5 runs (ProvenanceRecord.datasets,
carried empty since Phase 0, now used); the seam refuses an unresolved temperature.

The wizard: stage 1 gains the ERA5 temperature-profile panel, box marked on it.

The physics tests paid for themselves before the first commit. The acceptance suite
asserts the stratosphere looks like the stratosphere (tropical cold point 185-205 K,
summer pole warmer than winter, H2O single-digit ppmv). The first cut shipped CDS's
DESCENDING pressure levels into an interpolation that assumed ascending: every lookup
silently read the 300 hPa row, and T(55 hPa) came back 240 K with 414 ppmv of water --
plausible at a glance, impossible as a stratosphere. Fixed in the pipeline (both axes
sorted ascending) and REFUSED in the loader (a non-monotonic axis raises rather than
being reordered, because the checksum pins what was read).

The headline: ERA5 says 209.42 K at the ensemble's site (30N, June, 55 hPa) where the
ensemble typed 210 K -- the paper's number confirmed to half a kelvin by 30 years of
reanalysis. H2O is 4.15 ppmv against the typed 6.91 (RH = 3% assumption), consistent
with the known reanalysis dry bias (MLS: issue #94). Tropical cold point 191.4 K.

357 Python Tier-A (+11 -- and the climatology tests run in CI, since the product is
committed rather than gitignored or a submodule), 55 vitest. Hash 0.3.0 -> 0.4.0
(...7813ae -> ...d35ba6): same default run, three more fields describing it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EaDHnPdiacsybr8Tp5WnqR
@aliakherati
aliakherati force-pushed the feat/era5-climatology branch from 7d84796 to 040ce9a Compare August 19, 2026 23:52
aliakherati and others added 2 commits August 19, 2026 18:28
Reported from use: "in the atmosphere the pressure goes down as you go up." The profile
panel drew 300 hPa at the TOP of the frame -- an upside-down atmosphere -- while its own
caption claimed the opposite. The chart's default y mapping (domain minimum at the bottom)
is right for every quantity that grows upward and exactly wrong for pressure as a vertical
coordinate.

Chart gains yReverse, which swaps the PIXEL range rather than the domain, so tick
generation stays on an ascending domain. The profile panel uses it, and its caption now
describes what is actually drawn: up on the chart is up in the atmosphere.

Chart.test.tsx pins the orientation in both modes by reading marker pixel positions out of
the rendered SVG -- the caption asserting one thing while the axis did another is exactly
the drift a text-free render test catches.

58 vitest (+3).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EaDHnPdiacsybr8Tp5WnqR
Requested in review: translate the profile into height. Implemented as a second LABELING
of the same axis rather than a second scale -- altitude and pressure are the same
coordinate related by the product's own geopotential at this latitude and month, so
round-kilometre ticks are placed at the pressures where those altitudes actually sit
(20 km at 56.8 hPa in June at 30N), and the box marker names its own altitude.

The number that makes this worth having: z(30N, June, 55 hPa) = 20.20 km, and the
ensemble's site label is literally "30N_20km" -- the product confirms the site name.

Deliberately NOT a dual-axis chart: an independent second y-scale is the one chart crime
the dataviz rules forbid outright. rightTicks name positions in the PRIMARY domain, so
the two labelings cannot disagree; a round-trip test pins each tick to its claimed
altitude within the interpolation's own error.

357 -> 360 Python Tier-A, 58 vitest.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EaDHnPdiacsybr8Tp5WnqR
@aliakherati
aliakherati merged commit 2933df2 into studio/dev Aug 21, 2026
2 checks passed
@aliakherati
aliakherati deleted the feat/era5-climatology branch August 21, 2026 04:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant