Switch ERA5 source to earthmover-public/era5 (full archive)#6
Open
aaronspring wants to merge 10 commits into
Open
Switch ERA5 source to earthmover-public/era5 (full archive)#6aaronspring wants to merge 10 commits into
aaronspring wants to merge 10 commits into
Conversation
Swaps the Arraylake data source to earthmover-public/era5, which extends
coverage from 1975-2024 to 1940-2025 with quarterly updates and adds 16
new surface variables (38 total vs 22). Adapts retrieval.py for the new
repo's structure: group path changed from "{query_type}" to
"single/{query_type}", the time dimension is now "valid_time" (renamed
to "time" on open to keep the rest of the pipeline unchanged), and three
variables were renamed in the store (t2->t2m, d2->d2m, mslp->msl) — a
new zarr_name field on ERA5Variable maps catalog short names to the
store's actual array names so external behavior (filenames, output var
names) stays stable. Also drops the old compute_tp = cp + lsp workaround
since tp is now available directly in the store.
Pressure-level variables (13 levels, 8 vars) exist in the new archive
but are not wired up to retrieve_era5_data yet — noted as follow-up
work in the system prompt.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The new archive compresses several variables with the pcodec codec. Without the pcodec extra, retrieval fails with "codec not available: ''pcodec''". Confirmed via a live end-to-end retrieval smoke test (temporal + spatial queries, renamed vars t2/d2/mslp, native tp, prime-meridian crossing) against earthmover-public/era5. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
aaronspring
commented
Jul 10, 2026
| units="K", | ||
| description="Temperature of the second soil layer (7-28 cm depth)", | ||
| category="land_surface", | ||
| typical_range=(220, 330), |
Author
There was a problem hiding this comment.
not sure if these make sense. how did you come up with typical_range so far? gut feeling or programmatically?
The t2->t2m, d2->d2m, mslp->msl renames were the most breakage-prone part of the era5 source switch and had no test at all: a typo in zarr_name only surfaced at runtime against the network. Pins the store's 38 array names as STORE_ARRAY_NAMES and asserts the catalog maps onto them bijectively, plus direct coverage of get_zarr_name (renames, identity, aliases, unknown passthrough). Verified both mutations — retyping zarr_name and dropping it entirely — now fail four tests each. Also refreshes this file for the new dataset: drops the dead ALL_CATALOG_VARS list and its now-false "tp is derived" comment (tp is native in the new store), and fixes test_agent_prompt_lists_all_variables to match the prompt's table row rather than a bare substring — short names like "sd", "sp" and "ie" occur inside ordinary prompt prose and made it pass vacuously. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`uv lock` fails outright under requires-python = ">=3.10": zarr>=3.0.8
requires Python >=3.11, and the numcodecs[pcodec] pin added for the new era5
store does too. The declared floor and the 3.10 classifier were advertising
an environment in which the project cannot be installed at all.
Verified >=3.11 resolves, and that the pcodec codec — the reason numcodecs is
pinned — actually registers on 3.11, where zarr pins back to 3.1.6
(get_codec_class('numcodecs.pcodec') returns zarr.codecs.numcodecs._codecs.PCodec).
So 3.11 is genuinely supported, not just resolvable; no need to jump to 3.12.
Also syncs the mypy and black target versions, which still said 3.10.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
generate_filename() produced era5_VAR_QTYPE_START_END_REGION.zarr with no source discriminator, and retrieve_era5_data() returns CACHE HIT on a bare os.path.exists. Any deployment with a warm data/ directory would keep serving files fetched from era5-surface-aws after the switch to era5, never re-downloading. That is not merely stale: old spatial cp/lsp/ssr/ssrd files have dims (latitude, longitude, time) where new ones are (time, latitude, longitude), and old tp files hold the computed cp + lsp rather than native tp. Names are now prefixed with the source repo (earthmover-public-era5_...), so pre-existing era5_* files can never be mistaken for the new store's output. Verified end to end: first call downloads, second call hits the cache. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The new store declares an `lsm` land-sea-mask coordinate on every single-level variable, so xarray attached it to every download and wrote it into the output zarr — a coordinate that was never there before this source switch. It is not a usable mask. Reading lsm straight from the store, in both single/temporal and single/spatial, returns 721x1440 of pure NaN: the array is declared but never written upstream. Propagating it hands callers an empty array that looks like a land-sea mask and silently masks everything. Drops it, so output coords are exactly (time, latitude, longitude) on both the normal and prime-meridian-concat paths. Also documents the output shape in the system prompt, since the agent writes python_repl code against these files, and points at `sst.notnull()` as the mask that actually works. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The "single/" prefix was hardcoded in two places in retrieval.py while data_source sat in AgentConfig. The two describe the same store and are only valid together, so pointing data_source at another repo silently produced wrong group paths with no error. Adds AgentConfig.data_group, used by both call sites, with a comment saying the two fields travel together. Also makes the pressure-level group reachable by configuration when someone wires it up. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Wind gust speed was given RdBu_r, copied from u10/v10 where a diverging map is right because those components are signed. Gusts are >= 0 (its own typical_range says (0, 60)), so RdBu_r centres white in the middle of the data and reads as if half the field were negative. Switches it to viridis, matching zust, the other non-negative wind diagnostic. Adds a test pinning the invariant: any variable using RdBu_r must have a typical_range that straddles zero. The six remaining users (u10, v10, u100, v100, slhf, ie) all do; reverting fg10 fails the test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…wnload The snippet says "Reproduce this download yourself" but selected on valid_time and never renamed, so pasting it gave you a file whose time dim was valid_time, not time — and, after the previous commit, one that still carried the all-NaN lsm coordinate the tool now drops. Mirrors the two normalisation steps the pipeline performs, with comments saying why each is needed. Verified by extracting the generated python block and executing it verbatim, then diffing against the tool's own output: same dims, same coords, and np.allclose(equal_nan=True) on the values. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…entials retrieve_era5_data rejected any start_date newer than now-5d, citing an "ERA5T ~5-day processing lag". That lag describes the near-real-time ERA5T stream, not this archive, which is a reanalysis of the past extended quarterly. The guard was simultaneously too strict (refusing dates it had no reason to refuse) and useless (the real limit, currently 2025-12-31, is months earlier). Replaces it with the only check that cannot go stale — reject dates in the future — and lets the existing empty-time-range branch report actual coverage, which it reads from the store and so tracks each quarterly update. That branch now names both ends of the range rather than only the last timestep. Moves the check ahead of the API-key lookup: it is pure input validation and needs no credentials. This also fixes test_future_date_returns_error, which had been failing for anyone without ARRAYLAKE_API_KEY in their environment — it asserted on "future" but the function returned the missing-key error first, never reaching the date logic its docstring claimed it tested. Also drops the now-unused timedelta import and the same stale ERA5T note from the all-NaN error message. Full suite now passes both without a key (206 passed) and with one, including the live e2e tests (217 passed). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Swaps the Arraylake data source from
earthmover-public/era5-surface-awstoearthmover-public/era5.Why:
tp(total precipitation) is now available directly in the store instead of being derived ascp + lsp.Adaptation required (the new repo restructures the store):
"{query_type}"to"single/{query_type}"for surface-level variables.timetovalid_time— normalized back totimeright after opening the dataset so the rest of the retrieval pipeline (and cached filenames/output variable names) is unaffected.t2→t2m,d2→d2m,mslp→msl. Added azarr_namefield onERA5Variableso our catalog's stable short names (t2,d2,mslp) keep mapping correctly to the new store array names.compute_tp = cp + lspfallback sincetpis a native variable in the new store.pcodeccodec — addednumcodecs[pcodec]as a core dependency, otherwise retrieval fails withcodec not available: ''pcodec''.earthmover-public/era5uses pcodec filterlsmland-sea-mask coordinate on every variable but ships it unwritten — reads return 721×1440 of pure NaN. It is dropped from downloads rather than propagated as an empty array that looks like a usable mask.Why the Python floor moves to 3.11
requires-pythonsaid>=3.10, but the project could not be installed on 3.10 at all —uv lockfails outright with an unsatisfiable-requirements error:Two dependencies force this:
zarr>=3.0.0>=3.11(3.1.x),>=3.12(3.2.x)numcodecs[pcodec]>=0.16.0>=3.11earthmover-public/era5compresses with thepcodeccodecSo
>=3.10was already wrong onmain(zarr alone made it unsatisfiable); this PR'snumcodecspin makes it wrong for a second, independent reason. Since this PR is what introduces that pin, it's the right place to fix the declared floor rather than leavepyproject.tomladvertising an environment nobody can install into. TheProgramming Language :: Python :: 3.10classifier, the[tool.mypy] python_version, and black'starget-versionwere all saying 3.10 too, and are synced.Why 3.11 and not 3.12?
>=3.12also resolves, but 3.11 is the lowest floor that actually works, so it's the least restrictive honest choice. Resolution succeeding isn't sufficient evidence on its own — on 3.11, zarr pins back to 3.1.6, so thepcodeccodec (the entire reasonnumcodecsis pinned) has to register against that older zarr. Verified directly rather than assumed:3.11 is genuinely supported, not merely resolvable. The Dockerfile (3.12) and CI are unaffected.
Not yet in scope: the new archive also has pressure-level variables (temperature, wind, humidity, geopotential, etc. at 13 levels from 1000-50 hPa, under a separate
pressure/group).retrieve_era5_dataonly supports thesingle/(surface) group for now — wiring up pressure levels is flagged as follow-up work in the system prompt.AgentConfig.data_groupnow makes that group reachable by configuration.Review fixes
Subsequent commits address findings from a review of the initial diff:
zarr_namehad no test coverage — the three store renames were the most breakage-prone part of this change and only failed at runtime, against the network. Now pinned against the store's 38 array names; mutation-verified (retypingt2m, or droppingd2's mapping, each fails four tests).data/directory would keep serving files fetched fromera5-surface-awsforever. Names are now prefixed with the source repo. This matters beyond staleness: old spatialcp/lsp/ssr/ssrdfiles have dims(latitude, longitude, time)where new ones are(time, latitude, longitude).lsmcoordinate dropped from downloads (see above).test_future_date_returns_error, red for anyone withoutARRAYLAKE_API_KEY.fg10(wind gust, ≥ 0) no longer uses a diverging colormap; invariant pinned by a test.valid_timeand never renamed)."single/"group prefix moved out ofretrieval.pyintoAgentConfig.data_group, next to thedata_sourceit must travel with.Test plan
ARRAYLAKE_API_KEY— 206 passed, 5 skipped. (Previouslytest_future_date_returns_errorfailed in this configuration, onmaintoo.)single/spatial,single/temporal,pressure/spatial; 38 vars,valid_timedim,t2m/d2m/mslrenames).earthmover-public/era5, covering:t2) and spatial query (sst, different group path)d2→d2mandmslp→msl, output correctly namedd2/mslpmslpcombined with prime-meridian-crossing longitude bounds (lon range −6° → 10°)tp(no longer computed fromcp + lsp)pcodeccodec dependency and the all-NaNlsmarray in the processnp.allclose(..., equal_nan=True)on values.pcodeccodec registration confirmed on Python 3.11 (see above).🤖 Generated with Claude Code