Feature/CLIMATE-22 historical gbd versioning - #41
Conversation
In compile_gcm, filter scenario annual inputs to years not in HISTORY_YEARS so the extended ERA5 historical record (through 2025) takes precedence over forecast data for the overlapping 2024-2025 years. Prevents duplicate/conflicting year coords in combine_by_coords. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Support scenario="historical" (gcm_member "era5"): read the raw historical annual mean_temperature directly and produce a pure-ERA5 10-year rolling zone spanning 1990-2025, instead of the compiled historical+forecast series. Switch the runner/task option from --cmip6-experiment to --scenario and enumerate era5 for historical. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- constants: register gbd_2021/gbd_2025 in HIERARCHY_MAP and add GBD_HIERARCHIES; point AGGREGATE_ROOT at the geospatial working tree (MODEL_ROOT/aggregates). - data: add ClimateAggregateData.person_days_path / compiled_person_days_path; generalize the hierarchy conditionals to GBD_HIERARCHIES; note the gbd_2025 parent-drop check; drop a stray debug print in save_compiled_annual_results. - cli_options: let with_hierarchy take a default. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Add a historical run mode (scenario="historical", era5, years 1990-2025; ERA5 daily used for all years). - compile_person_days: aggregate against gbd_2023 (matches the inputs, which are generated under gbd_2023). - Parameterize --hierarchy (default gbd_2023); version outputs by appending the hierarchy to output_dir, and route reads/writes through the centralized person_days_path / compiled_person_days_path helpers. - _main creates its own per-block output dir so standalone cdtask runs don't depend on the runner pre-creating it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…climate HISTORY_YEARS now runs through 2025 so the draws 'prefer historical ERA5 over scenario' filter actually excludes the overlapping 2024-2025 forecast years. MODEL_ROOT moves to /mnt/share/geospatial/climate/ (AGGREGATE_ROOT already follows MODEL_ROOT). Both were previously entangled in the CLIM-21 dry-run commit; consolidated here so CLIM-21 stays pure infra. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The CLIM-22 rewrite of the compile_person_days runner introduced an un-annotated 'jobs = []' that fails strict mypy (var-annotated). Annotate as list[tuple[str, str]] (gcm_member, scenario). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a standalone `special` step that downloads whole-year ERA5
`reanalysis` and `ensemble_spread` 2m-temperature files for 2024/2025
from the Copernicus CDS, matching Katrin Burkart's
`era5_{product_type}_{variable}_{year}.nc` layout. Fills the 2024/2025
uncertainty gap that GBD temperature PAF work depends on.
- Pulls both products 3-hourly from reanalysis-era5-single-levels (the
only dataset exposing ensemble products); ensemble_spread/EDA is only
3-hourly, reanalysis/HRES is subsampled to match.
- Reads credentials from the caller's ~/.cdsapirc, avoiding the shared
copernicus.yaml keyring.
- Registered in special/__init__.py RUNNERS/TASK_RUNNERS as
`download_era5_uncertainty`; CHANGELOG updated.
ensemble_spread is not yet consumed downstream -- this is an input
gap-fill only.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rtainty Add methodology docs for the CLIMATE-22 features: - New "Temperature person-days exposure" section (special-stage chain: temperature_zone -> temperature_person_days -> compile_person_days -> draw assembly), with subsections for the historical ERA5-only mode (1990-2025 GBD deliverable, distinct from the 1990-2100 forecast) and GBD --hierarchy versioning. - New "ERA5 ensemble uncertainty" subsection (reanalysis/ensemble_spread 2m-temp gap-fill, single-levels only, not yet consumed by the pipeline). - Version-dependent reference-climatology and historical-daily windows: 2019-2023 for FHS-2023, 2021-2025 for FHS-2025; post-2023 extension is currently GBD-only. - Wired docs/methods.md into the mkdocs nav (was orphaned). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`choices` was annotated `Sequence[str]` but defaults to a dict (`cdc.HIERARCHY_MAP`), a pre-existing mypy error. Widen to `Collection[str]` — what a dict is and what `with_choice` accepts. No behavior change; drop the now-unused `Sequence` import. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Review #1 (critical): the branch repointed MODEL_ROOT/AGGREGATE_ROOT to the geospatial run area, which would repoint the whole pipeline on merge. Revert both to the production roots (erf/climate_downscale + climate-aggregates) as the module-level defaults, and add model_root()/aggregate_root() helpers plus a --run-mode CLI option (forecast|historical, default forecast) so the historical GBD run opts into the geospatial roots explicitly rather than via a hardcoded constant. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Review #2/#4/#5: extending HISTORY_YEARS to 2025 created a 2024/2025 overlap with FORECAST_YEARS (double-claimed years), which drove compile_gcm hole risk, a shifted reference-climatology baseline, and wrong-zone person-days binning. Revert HISTORY_YEARS to end at 2023 (production) so the overlap disappears at the source: FORECAST_YEARS starts 2024 with no double-claim, REFERENCE_YEARS is back to 2019-2023, and ALL_YEARS has no duplicates. Add EXPOSURE_START_YEAR=1990 for the person-days/zone analysis window (used by the special stage, which derives its upper bound from the data on disk). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Review #3/#8: drive the person-days loop from the zone's actual `year` coordinate, indexed by matching year (was hardcoded 1990..2025 + positional), skipping years with missing daily/pop inputs instead of raising. Derive FORECAST_START_YEAR and the zone slice from constants. Add --run-mode {forecast,historical} to the special person-days chain to route the storage roots (production vs geospatial); an explicit --climate-data-dir/--output-dir still wins. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Review #6: if the unzip failed part-way, out_path was left truncated and the except only removed download_path, so the next run skipped it as "already downloaded". Write the unzipped payload to a temp and atomically replace onto out_path as the last step; clean up partial out_path/temp on failure. Review #9: comment ERA5_UNCERTAINTY_YEARS as the historical-extension gap-fill (fixed path, run-mode-independent). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Review #10: draws.compile_gcm_main and temperature_zone duplicated the `raw_annual_results glob + open_mfdataset(combine="by_coords").sortby("year") .compute()` idiom, which could diverge. Add ClimateData.combine_raw_annual (the open/sort/compute) and load_raw_annual_mfdataset (glob + combine); use them in both callers. Behavior-preserving; drops the now-unused xarray imports there. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Review #7: the widened most_detailed override applies the UK/India/NZ parent-drop list to gbd_2025. Verified against the hierarchy parquets: 39/41 ids are present in gbd_2025 (4854 J&K/Ladakh reorganized out, harmless no-op), so keep the widening. While verifying, found 4919 is absent in all rounds -- a typo for 4619 ("North West England"), so its UTLA children were never dropped; correct it and update the NOTE. Checked 4919 explicitly: it appears as neither a location_id nor a parent_id in any raking hierarchy parquet, and 4618-4626 are exactly England's nine regions with only 4619 otherwise missing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Review #5 + run-mode docs: note that the reference window is the trailing five years of history, so changing the history end shifts it and the reference climatology and bias correction must be regenerated in lockstep. Document that --run-mode selects the storage roots (forecast=production, historical=geospatial) and that the historical product's span is derived from the ERA5 data on disk (1990-2025 as produced). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
@rmbarber — pushed a round of changes addressing an independent code review of this branch, plus the methodology docs. Ready for your look; summary below. Core design changeThe branch had made two global changes a Review findings → resolution (7 gated commits,
|
| / block_key | ||
| / f"{cmip6_experiment}_{gcm_member}.parquet" | ||
| ) | ||
| path = ca_data.person_days_path(block_key, scenario, gcm_member) |
There was a problem hiding this comment.
Probably preferable to just handle the io in the data module (i.e., write a ca_data.load_person_days rather than generate the path and then call read_parquet here)
| scenario, "mean_temperature", year, gcm_member | ||
| ).sel(**climate_slice) | ||
| pop_arr = pm_data.load_results(f"{year}q1", block_key)._ndarray.flatten() # noqa: SLF001 | ||
| except FileNotFoundError as exc: |
There was a problem hiding this comment.
What is the impetus for this? I would have thought we need squareness across time/scenario/ensemble member, and that missingness would be something that should fail loudly rather than be skipped.
There was a problem hiding this comment.
Yeah, that is a very good point. I may be leaning on claude too heavily here, because I missed that point.
Review (rmbarber): the per-year loop caught FileNotFoundError and skipped any year missing its daily or population input. For a product that must be square, that silently punches holes instead of surfacing a real gap. Drop the try/except so a missing file propagates from load_daily_results / load_raw_daily_results / load_results. The span is still derived from the temperature_zone on disk. Reword the docstring and the no-output guard to match. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Review (rmbarber): compile_person_days built the block path and called pd.read_parquet inline. Move the I/O into ClimateAggregateData.load_person_days (mirrors the load_raw_results convention) so the data module owns the layout. Keep the block-loading loop imperative and promote ruff PERF401 to the global ignore list (it was already ignored per-file for jobmon_utils.py with the note "billg hates list comprehensions"). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
temperature_person_days loaded the raw population array (nodata = NaN) and summed it into the compute_person_days accumulator (out += pop), so a NaN population pixel poisoned every output cell it touched and was read back as 0 -- zeroing small and coastal locations (American Samoa, the England regions, etc.). Zero-fill nodata before the kernel; a modeled-population-free pixel contributes no person-days. Adds a regression test (NaN pixel must not poison the output) and a CHANGELOG entry. Latent bug exposed by a population-model nodata-encoding change. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Implements CLIMATE-22. Stacked on #PR40 (CLIMATE-21 dry-run) — review/merge that
first, then this retargets to
main.What
gbd_2021/gbd_2025,adds
GBD_HIERARCHIES, and a--hierarchyoption (defaultgbd_2023) on theperson-days runners.
temperature_zoneand the person-days steps.drawsnow prefers historical ERA5 over scenario data for overlapping years.Changed
HISTORY_YEARSextended through 2025 (so the historical-preference filter coversthe 2024–2025 overlap).
MODEL_ROOT→/mnt/share/geospatial/climate/(AGGREGATE_ROOTfollows it).
🤖 Generated with [Claude Code]