Skip to content

Fabricate CMIP7 coverage by repeating the final year - #887

Open
lewisjared wants to merge 4 commits into
mainfrom
fix/fabricate-by-repeating-last-year
Open

Fabricate CMIP7 coverage by repeating the final year#887
lewisjared wants to merge 4 commits into
mainfrom
fix/fabricate-by-repeating-last-year

Conversation

@lewisjared

@lewisjared lewisjared commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Reworks how fabricated CMIP7 test data reaches years the CMIP6 source never ran. The series used to be relabelled onto the requested end date, which dragged every real year along with it. It is now padded instead, by repeating the final year until it reaches that date, so the real timesteps keep their real dates and values.

Two things worth a close look.

  • The relabelling was applied per file, so a dataset split into chunks (GFDL-ESM4 toz is 1850-1949 plus 1950-2014) produced overlapping files that all ended on the same month. Padding only ever touches the tail, so that goes away.
  • The fabricated tail is now a repeat of the last real year rather than real data under a different label. Either way it is invented, but a reviewer should be happy with a flat repeated year showing up in the fire and cloud radiative effects plots.

Re-mints the two affected CMIP7 baselines. Their fabricated files move from 185701-202112 to 185001-202112.

The ozone diagnostics need this to ask for a 1950 start on CMIP7, so #886 should land after it.

Summary by CodeRabbit

  • Bug Fixes

    • Historical CMIP7 data now preserves real dates and values while extending coverage by repeating only the final available year.
    • Added handling for leap days and validation of complete final years.
    • Earlier dataset files remain unchanged when extending historical coverage.
  • Tests

    • Expanded coverage for date preservation, value repetition, file selection, and invalid time ranges.
  • Documentation

    • Updated changelog and explanatory comments to describe the revised historical-extension behaviour.

The CMIP6 source for a fabricated CMIP7 series stops in 2014, so the series was relabelled
onto the requested end date. This moved every real year with it, and each file was relabelled
on its own, so a dataset split across several chunks ended up with overlapping files that all
finished on the same month.

Replaces the relabelling with padding. The real timesteps keep their dates and values, and the
final year is tiled forward until the series reaches the requested end.

Re-mints the fire and cloud radiative effects CMIP7 baselines, whose fabricated files now start
in 1850 rather than 1857.
@coderabbitai

coderabbitai Bot commented Aug 28, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

CMIP7 historical extension now repeats the final complete year instead of relabelling the full time axis. Only the latest temporal file is extended. Unit tests, catalogues, manifests, regression data, and related documentation were updated.

Changes

CMIP7 historical padding

Layer / File(s) Summary
Final-year repetition helper
packages/climate-ref-core/src/climate_ref_core/cmip6_to_cmip7.py, packages/climate-ref-core/tests/unit/test_cmip6_to_cmip7.py, changelog/887.fix.md
repeat_final_year_to pads missing months with repeated final-year values. It validates complete December-ending input, preserves real dates and bounds, handles leap days, and leaves already-complete data unchanged.
Latest-file extension routing
packages/climate-ref-core/src/climate_ref_core/esgf/cmip7.py, packages/climate-ref-core/tests/unit/esgf/test_cmip7.py
fetch_datasets finds the source file with the latest timestep and applies extend_historical_to only to that file. Tests cover file selection, missing time axes, routing, and no-extension behaviour.
CMIP7 fixture metadata and outputs
packages/climate-ref-esmvaltool/src/climate_ref_esmvaltool/diagnostics/climate_drivers_for_fire.py, packages/climate-ref-esmvaltool/tests/test-data/*/cmip7/*
CMIP7 catalogue dates, tracking IDs, manifests, regression values, test-case versions, and diagnostic comments reflect the updated historical extension outputs.

Sequence Diagram(s)

sequenceDiagram
  participant fetch_datasets
  participant _latest_file
  participant _convert_file_to_cmip7
  participant repeat_final_year_to
  fetch_datasets->>_latest_file: inspect source files
  _latest_file-->>fetch_datasets: return latest temporal file
  fetch_datasets->>_convert_file_to_cmip7: convert each source file
  _convert_file_to_cmip7->>repeat_final_year_to: extend latest file to target month
  repeat_final_year_to-->>_convert_file_to_cmip7: return padded dataset
Loading

Merge Risk: 🟡 Moderate · up to 4daed

The PR changes CMIP7 coverage generation to repeat the final year, but the current implementation can fabricate values into incorrect months, return incomplete data when validation fails, or abort a request when a source file is unreadable. These correctness and availability risks should be fixed or explicitly accepted before merge.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 87.18% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 39 functions across 5 files. (6 skipped: 6 …
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly and concisely describes the main change: extending fabricated CMIP7 coverage by repeating the final year.
Description check ✅ Passed The description clearly explains the implementation, affected datasets, edge cases, test impact, and changelog update. It does not use the required "## Description" and "## Checklist" headings or conf…
Full details: Docstring Coverage

Explanation

Docstring coverage is 87.18% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 39 functions across 5 files. (6 skipped: 6 unsupported.)

Full details: Description check

Explanation

The description clearly explains the implementation, affected datasets, edge cases, test impact, and changelog update. It does not use the required "## Description" and "## Checklist" headings or confirm the checklist items explicitly, but the required information is mostly present.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/fabricate-by-repeating-last-year

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Aug 28, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.91667% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...limate-ref-core/src/climate_ref_core/esgf/cmip7.py 93.75% 1 Missing ⚠️
Flag Coverage Δ
core 93.76% <97.91%> (+<0.01%) ⬆️
providers 87.70% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
...te-ref-core/src/climate_ref_core/cmip6_to_cmip7.py 94.39% <100.00%> (+0.04%) ⬆️
...esmvaltool/diagnostics/climate_drivers_for_fire.py 100.00% <ø> (ø)
...limate-ref-core/src/climate_ref_core/esgf/cmip7.py 88.65% <93.75%> (+1.35%) ⬆️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

…y label

Padding every file in a dataset ran each chunk out to the same end month, so a dataset split
across several files still produced overlapping converted files. Only the file holding the
final timestep is extended now.

Simplifies the padding itself. The input is always monthly, so the final year is selected by
label and each repeat is that year with its year stamp moved. This drops the month arithmetic,
the day clamping, and the partial-year tiling. A series that does not end in December, or whose
final year is incomplete, now fails rather than producing a gap.
Adds the end-to-end check that only the file ending last is extended, which is what the
overlapping converted files came down to.

Falls back a day when a leap day is restamped onto a common year, rather than letting cftime
raise from inside the relabelling.
@lewisjared
lewisjared marked this pull request as ready for review August 31, 2026 00:46

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/climate-ref-core/src/climate_ref_core/esgf/cmip7.py (1)

352-354: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Do not suppress final-year validation errors.

When extension is requested and the latest file ends outside December or has an incomplete final year, repeat_final_year_to raises ValueError. Lines 352-354 catch the error and continue. If an earlier chunk converted, this method returns that incomplete dataset row instead of failing the request. Re-raise the helper validation errors.

Proposed fix
+                    except (TypeError, ValueError):
+                        raise
                     except Exception as e:
                         logger.exception(f"Failed to convert {cmip6_path.name}: {e}")
                         continue

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: feefc726-ec16-4852-b8e4-ec9ff4bfaf85

📥 Commits

Reviewing files that changed from the base of the PR and between 8991c0e and 4daedbe.

📒 Files selected for processing (11)
  • changelog/887.fix.md
  • packages/climate-ref-core/src/climate_ref_core/cmip6_to_cmip7.py
  • packages/climate-ref-core/src/climate_ref_core/esgf/cmip7.py
  • packages/climate-ref-core/tests/unit/esgf/test_cmip7.py
  • packages/climate-ref-core/tests/unit/test_cmip6_to_cmip7.py
  • packages/climate-ref-esmvaltool/src/climate_ref_esmvaltool/diagnostics/climate_drivers_for_fire.py
  • packages/climate-ref-esmvaltool/tests/test-data/climate-drivers-for-fire/cmip7/catalog.yaml
  • packages/climate-ref-esmvaltool/tests/test-data/climate-drivers-for-fire/cmip7/manifest.json
  • packages/climate-ref-esmvaltool/tests/test-data/cloud-radiative-effects/cmip7/catalog.yaml
  • packages/climate-ref-esmvaltool/tests/test-data/cloud-radiative-effects/cmip7/manifest.json
  • packages/climate-ref-esmvaltool/tests/test-data/cloud-radiative-effects/cmip7/regression/series.json

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.

Comment on lines +572 to +573
if len(final_year["time"]) != _MONTHS_PER_YEAR:
raise ValueError(f"A full final year is needed to repeat, got {len(final_year['time'])} months")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Validate the complete January-to-December month sequence.

Line 572 only checks for 12 records. A final year with a duplicate month and a missing month passes this check, then repeats values into the wrong fabricated months. Verify that the selected timestamps contain each month from January through December exactly once. Add a regression test for this input.

Proposed fix
 final_year = ds.sel(time=str(last.year))
-if len(final_year["time"]) != _MONTHS_PER_YEAR:
+final_year_months = [_month_index(t) for t in final_year["time"].values]
+expected_months = list(range(last.year * _MONTHS_PER_YEAR, (last.year + 1) * _MONTHS_PER_YEAR))
+if final_year_months != expected_months:
     raise ValueError(f"A full final year is needed to repeat, got {len(final_year['time'])} months")

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