Skip to content

Uprate electricity_consumption and gas_consumption - #1860

Merged
vahid-ahmadi merged 1 commit into
mainfrom
uprate-electricity-and-gas-consumption
Sep 18, 2026
Merged

vahid-ahmadi merged 1 commit into
mainfrom
uprate-electricity-and-gas-consumption

Conversation

@vahid-ahmadi

@vahid-ahmadi vahid-ahmadi commented Sep 18, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #1859.

Root cause

The class-level uprating = "..." attribute on a Variable is dead metadata: nothing in policyengine-uk reads it, and policyengine-core only inspects it to refuse combining it with its own @uprated decorator. data/uprating_indices.yaml is the only mechanism that actually applies uprating (apply_single_year_uprating / reset_growthfactor_uprating in data/economic_assumptions.py).

So a variable can declare an index and silently never be uprated. That is exactly what happened here.

The bug

electricity_consumption and gas_consumption both declare

uprating = "gov.economic_assumptions.indices.obr.consumer_price_index"

in variables/input/consumption/energy.py, but they were missing from data/uprating_indices.yaml — the list reset_growthfactor_uprating actually reads. So they returned identical values in every year while every other consumption variable on the same index rose with CPI.

Effect, on enhanced_frs_2024_25

variable 2024 2027 before 2027 after
electricity_consumption 838.29 838.29 (+0.0%) 904.47 (+7.9%)
gas_consumption 658.36 658.36 (+0.0%) 710.33 (+7.9%)
domestic_energy_consumption 1496.65 1614.79 (+7.9%) 1614.79 (unchanged)

It also fixes the second symptom in the issue

domestic_energy_consumption is documented as "Combined gas and electric bills". In the raw dataset it equals electricity_consumption + gas_consumption exactly — I verified this directly against the HDF5. But after uprating they diverged by 7.9% from 2025 onward, because only the combined variable was in the list.

With this change they agree in every year: elec+gas = 1614.79 = domestic in 2027. So the two symptoms had one cause, and there is no separate calibration difference to reconcile.

Why it matters

2024 was near the tail of the energy crisis — gov.ofgem.energy_price_cap falls from £2,389 (2024) to £1,641 (2026 onward) — so a frozen 2024 energy base is materially mis-levelled against later years. Any downstream study using these variables carried a 2024-priced energy baseline labelled as its target year. We hit this in a cost-of-living study where the energy channel is the largest single component.

Verification

  • 219 passed, 44 skipped — full policyengine_uk/tests suite, no failures.
  • Before/after measured in a clean virtualenv on the certified dataset.
  • The two entries are inserted alphabetically within the CPI block, matching the surrounding style. The list as a whole was already not fully sorted, so nothing else is reordered.

CI

The red Test job is not this PR: all failures are PermissionError: file /Users/runner/.cache/huggingface/hub/blobs/... exists but it can not be written at Microsimulation() construction — the macOS runner's HF cache. I counted 56 such errors in the failed log and zero mentions of electricity_consumption, gas_consumption or uprating_indices. Re-run requested.

Same gap elsewhere

Four more variables declare uprating but are missing from the YAML, and measure flat 2024 → 2027: water_and_sewerage_charges, housing_service_charges, pension_contributions_via_salary_sacrifice, rail_usage (plus an index mismatch on savings_interest_income). Filed separately as #1862 with a suggested code-health guard, to keep this PR to the energy fix.

Note on scope

This changes modelled energy spending in every year after the base year, so downstream results that use these variables will move by roughly CPI. That is the intended correction, but it is worth flagging for anyone with pinned regression values.

🤖 Generated with Claude Code

@juaristi22 juaristi22 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Verified locally on the cached enhanced_frs_2024_25 (household means, 2024 → 2027):

  • before: electricity_consumption 838.45 → 838.45 (0.0 %), gas_consumption 658.40 → 658.40 (0.0 %)
  • after: 838.45 → 904.64 (+7.9 %), 658.40 → 710.38 (+7.9 %); electricity + gas = domestic_energy_consumption = 1615.01 in 2027

So the diff does what it says and the documented identity is restored.

CI. The red Test job is not this PR. All 28 failures (3 failed + 25 errors) are the same PermissionError: file /Users/runner/.cache/huggingface/hub/blobs/... exists but it can not be written at Microsimulation() construction, i.e. the macOS runner's HF cache. main's last run (2026-09-16) and every other PR branch this month were green on the same job, so a re-run should clear it.

Root cause, worth stating in the PR body. The class-level uprating = "..." attribute is dead metadata: nothing in policyengine-uk reads it, and policyengine-core only looks at it to refuse combining it with its own @uprated decorator. data/uprating_indices.yaml is the only mechanism (apply_single_year_uprating / reset_growthfactor_uprating in data/economic_assumptions.py). A declaration without a YAML entry therefore does nothing, which is exactly what #1859 observed.

Same gap elsewhere — see the inline comment on the YAML. Happy for that to be a follow-up issue rather than scope creep here.

Downstream note: the populace UK build levels LCFS energy to the FY2024 base year and binds the NEED/DESNZ targets there, so base-year targets don't move; projected-year energy outputs from populace datasets will rise with CPI once this lands, which is the intended correction.

- communication_consumption
- domestic_energy_consumption
- education_consumption
- electricity_consumption

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Four more dataset columns declare uprating but are missing from this list, and I measured them flat 2024 → 2027 on the same dataset:

  • water_and_sewerage_charges (declares indices.ofwat.water_bills) — 375.34 in both years
  • housing_service_charges (indices.obr.lagged_average_earnings) — 91.89
  • pension_contributions_via_salary_sacrifice (indices.obr.average_earnings) — 417.07
  • rail_usage (gov.dft.rail.ridership_index) — 483.10

Also savings_interest_income declares indices.ons.household_interest_income, but this file applies per_capita.gdp to it.

Since the class attribute is never read, the durable fix is a code-health test (natural home: policyengine_uk/tests/code_health/) asserting that every variable declaring uprating appears in this file under the matching index. Two of the four above point at indices that may have no yoy_growth parameter yet, so they probably need their own PR — but the guard would make the gap visible instead of silent.

@vahid-ahmadi

Copy link
Copy Markdown
Collaborator Author

Thanks — the root-cause framing is better than mine and I've moved it into the PR body: the class-level uprating attribute is dead metadata, and uprating_indices.yaml is the only mechanism, so a declaration without a YAML entry does nothing. That is the generalisable point, and it explains why the declaration in energy.py looked correct.

CI. I verified your diagnosis independently before accepting it: 56 PermissionError hits on the runner's HF cache in the failed log, and zero mentions of electricity_consumption, gas_consumption or uprating_indices. Re-run requested.

The other four. Reproduced all of them on enhanced_frs_2024_25 — water_and_sewerage_charges 375.51, housing_service_charges 91.81, pension_contributions_via_salary_sacrifice 418.69, rail_usage 515.59, each identical in 2024 and 2027. Filed as #1862 with the savings_interest_income index mismatch and your code-health-test suggestion, rather than widening this PR. You're right that two of them point at indices which may have no yoy_growth parameter yet, so they need their own change.

I'd agree the code-health guard is the durable fix: it would have caught this at the point it was introduced, rather than in a downstream study three data releases later.

On the downstream note — that is exactly our situation. Our cost-of-living study has the energy channel as its largest component, and the baseline it multiplies was carrying 2024 prices labelled 2027-28. Once this lands we regenerate, and the headline moves by roughly CPI.

@vahid-ahmadi

Copy link
Copy Markdown
Collaborator Author

Test job: three re-runs, three identical failures — it is the runner's Hugging Face cache, not this PR. I am not going to re-run it a fourth time; flagging for whoever owns the workflow instead.

Evidence:

  • 56 errors, all the same message, and all pointing at a single blob: PermissionError: file /Users/runner/.cache/huggingface/hub/blobs/19/19cb8246dc50358976b2344dfdc981b24a3be2d01f3c69fb2e34f3c86b737e7e exists but it can not be written. One distinct hash across all 56.
  • Zero occurrences of electricity_consumption, gas_consumption or uprating_indices anywhere in the failed logs.
  • The failures are in test_reform_impacts.py, test_uc_deductions_aggregates.py and test_behavioral_responses.py — all failing at Microsimulation() construction, before any variable this PR touches is evaluated.
  • It is not specific to this branch. cgt-schedules-1858 hit the same failure at 10:27 today and passed on re-run at 10:55 (pr_code_changes.yaml). So the cache does recover; my three attempts happened to land while it was broken.
  • The affected tests pass locally on this branch: 219 passed, 44 skipped.

It is a write-permission failure on an already-present cached blob, so an HF token would not change anything — the fix is on the runner side, most likely a restored cache with bad ownership, or not restoring the HF cache across runs.

Happy to rebase on main if that would pick up a cache-key change, but I do not think there is anything to fix in the diff.

@juaristi22 juaristi22 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Correction to my earlier review: this is not a runner flake, and re-runs won't help.

huggingface_hub 1.32.0 (released 2026-09-17) introduced a shared blob store that keeps downloads read-only under hub/blobs/<prefix>/<hash> — the exact path in every error here — and dataset_schema.py opened HDF5 files in pandas' default append mode. The last green PR run on 2026-09-16 installed hub 1.31.0; every run today installed 1.32.0. So the failure is deterministic on any branch without the loader fix.

The fix (mode="r" in dataset_schema.py) reached main today via #1861 (b1e5305). The cgt-schedules-1858 branch went green at 10:55 on that commit, not from cache recovery — its 10:27 failure was on the commit before it. This PR is based on 709a9b6, before the fix, which is why three re-runs failed identically.

Verified the post-rebase state locally. Cherry-picking this PR onto current main applies cleanly (#1861's edit to uprating_indices.yaml is in the per_capita.gdp block). On that tree the full policyengine_uk/tests suite passes with zero failures, and the five modules that were red here — test_reform_impacts, test_uc_deductions_aggregates, test_salary_sacrifice_cap_reform, test_state_pension_reform, test_behavioral_responses — pass 37/37 with the dataset token set.

Approving on the condition of a rebase onto main. The diff is correct and needs no change; please rebase (or merge main), let the run go green, and merge then. #1862 stands for the other four columns.

Both variables declare

    uprating = "gov.economic_assumptions.indices.obr.consumer_price_index"

but were missing from data/uprating_indices.yaml, which is the list
reset_growthfactor_uprating actually reads. They therefore returned identical
values in every year while every other consumption variable on the same index
rose with CPI.

On enhanced_frs_2024_25, 2024 -> 2027:

  before   electricity_consumption       838.29 ->  838.29   (+0.0%)
           gas_consumption               658.36 ->  658.36   (+0.0%)
           domestic_energy_consumption  1496.65 -> 1614.79   (+7.9%)

  after    electricity_consumption       838.29 ->  904.47   (+7.9%)
           gas_consumption               658.36 ->  710.33   (+7.9%)
           domestic_energy_consumption  1496.65 -> 1614.79   (+7.9%)

This also restores the identity the documentation implies. In the raw dataset
domestic_energy_consumption equals electricity_consumption + gas_consumption
exactly; after uprating they diverged by 7.9% from 2025 onward, because only
the combined variable was in the list. They now agree in every year.

The entries are inserted alphabetically within the CPI block, matching the
surrounding style. The list as a whole is not fully sorted, so this does not
reorder anything else.

Fixes #1859.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@vahid-ahmadi
vahid-ahmadi force-pushed the uprate-electricity-and-gas-consumption branch from 73f65e4 to c71d5ea Compare September 18, 2026 13:56
@vahid-ahmadi

Copy link
Copy Markdown
Collaborator Author

Rebased onto main (c71d5ea) — thank you for the correction, and I was wrong.

I had concluded "flaky runner cache" from the fact that cgt-schedules-1858 failed at 10:27 and passed at 10:55. You found the actual cause: that branch went green because it contained #1861's mode="r" fix, not because a cache recovered. huggingface_hub 1.32.0 landed on 2026-09-17 with a read-only shared blob store, and dataset_schema.py was opening HDF5 files in pandas' default append mode — so it was deterministic on any branch without the fix, and my three re-runs were never going to help.

Verified your explanation before acting on it: mode="r" is on main at lines 56 and 198 of dataset_schema.py, via 06e81c0, and my branch was based on 709a9b6 — before it.

Post-rebase, as you predicted:

The lesson I'll take: "it passed on re-run elsewhere" is weak evidence for a flake when the two runs sat on different base commits. I should have diffed the bases before concluding infrastructure.

Waiting on the run to go green, then this is ready to merge. #1862 still stands for the other four columns and the code-health guard.

@vahid-ahmadi
vahid-ahmadi merged commit 1b62c74 into main Sep 18, 2026
6 checks passed
@vahid-ahmadi
vahid-ahmadi deleted the uprate-electricity-and-gas-consumption branch September 18, 2026 14:44
vahid-ahmadi added a commit that referenced this pull request Sep 22, 2026
Addresses the three findings on this PR.

C1: the changelog fragment had no type component, so towncrier omitted it
entirely — the draft rendered "No significant changes". Renamed to
energy-not-cpi-uprated.fixed.md, per changelog.d/<branch>.<type>.md in
CONTRIBUTING, and the leading bullet removed so towncrier does not double it.
Verified the draft now renders it under Fixed.

A1: the wording claimed more than the change delivers, and the reviewer is
right. uprating_indices.yaml governs dataset projection. It does not govern
the class-level `uprating` attribute, which policyengine-core still uses to
carry a direct situation input forward: a 2024 electricity_consumption input
queried at 2027 returns 1078.93 against 1000, which I confirmed directly.

That path is deliberately left alone. A value a user supplies for 2024 may be
their own contemporaneous bill rather than this build's artifact, so it has no
claim on the build's price basis. The YAML header, the test docstring and the
test name now say "projection" rather than making a global claim.

Worth recording because #1860 and #1862 both described the class-level
attribute as dead metadata that nothing reads. That is true of dataset
projection and false of the direct-input path; core reads it there.

A2: docs/book/assumptions/growthfactors.md still listed
domestic_energy_consumption under "all consumption categories" get CPI. The
list now excludes the three energy variables and states why.

Full suite: 266 passed, 45 skipped.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
vahid-ahmadi added a commit that referenced this pull request Sep 23, 2026
* Stop CPI-uprating energy, which the data build already prices

Reverses the direction of #1860, which I wrote. Fixes #1867.

policyengine-uk-data calibrates the energy variables to NEED mean kWh by
income band and converts to pounds at Ofgem Q2 2026 unit rates. Its
imputations/consumption.py says so outright:

  "the stored £/yr values represent FY26/27 price levels. Energy variables
   are not CPI-uprated, so this avoids the need for price-level adjustment
   at simulation time."

uprating_indices.yaml uprates from the dataset's DATA year, 2024. So applying
CPI to these variables re-applies 2024-2026 price changes the build had
already priced in — about 5.8pp of a 7.9% uplift.

The cap parameter falls over this period, which makes the stored price level
testable. On enhanced_frs_2024_25 the modelled mean of electricity + gas is
£1,584, against cap parameters of £2,389 (2024), £1,849 (2025) and £1,641
(2026). A 2024-priced series would have to sit near the 2024 cap; it sits just
below the 2026 one, which is the April 2026 level the docstring describes.

#1859 read the symptom backwards and #1860 followed it. The flat pair was
right — accidentally, being absent from the YAML rather than deliberately
excluded — and domestic_energy_consumption, on the CPI list since before
2.90.2, was carrying the double count. #1860 restored the documented identity
by propagating the defect to all three.

Removing all three restores the identity AND holds the level: electricity
£895 + gas £689 = £1,584 = domestic, in every year from 2024 to 2027.

Other consumption variables come from the LCFS at data-year prices and do
need uprating, so only these three are excluded. The rationale is recorded at
the top of the YAML, and a code-health test asserts they appear under no
index. Verified that test fails when one is re-added rather than only passing
today.

Downstream: modelled energy spending in projected years falls about 8%, back
to the build's own price level. Anyone with pinned regression values on
domestic_energy_consumption will see them move; that movement is the
correction.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Scope the energy exclusion to dataset projection, and fix the fragment

Addresses the three findings on this PR.

C1: the changelog fragment had no type component, so towncrier omitted it
entirely — the draft rendered "No significant changes". Renamed to
energy-not-cpi-uprated.fixed.md, per changelog.d/<branch>.<type>.md in
CONTRIBUTING, and the leading bullet removed so towncrier does not double it.
Verified the draft now renders it under Fixed.

A1: the wording claimed more than the change delivers, and the reviewer is
right. uprating_indices.yaml governs dataset projection. It does not govern
the class-level `uprating` attribute, which policyengine-core still uses to
carry a direct situation input forward: a 2024 electricity_consumption input
queried at 2027 returns 1078.93 against 1000, which I confirmed directly.

That path is deliberately left alone. A value a user supplies for 2024 may be
their own contemporaneous bill rather than this build's artifact, so it has no
claim on the build's price basis. The YAML header, the test docstring and the
test name now say "projection" rather than making a global claim.

Worth recording because #1860 and #1862 both described the class-level
attribute as dead metadata that nothing reads. That is true of dataset
projection and false of the direct-input path; core reads it there.

A2: docs/book/assumptions/growthfactors.md still listed
domestic_energy_consumption under "all consumption categories" get CPI. The
list now excludes the three energy variables and states why.

Full suite: 266 passed, 45 skipped.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
vahid-ahmadi added a commit that referenced this pull request Oct 7, 2026
Follow-up to #1860, closing most of #1862.

Three variables declared an uprating index but were missing from
uprating_indices.yaml, so they were never uprated. On enhanced_frs_2024_25,
2024 -> 2027:

  water_and_sewerage_charges                  375.51 -> 522.12  (+39.0%)
  housing_service_charges                      91.81 -> 105.06  (+14.4%)
  pension_contributions_via_salary_sacrifice  418.69 -> 466.81  (+11.5%)

Each matches its index compounded over the same years exactly: ofwat water
bills +39.0%, OBR lagged average earnings +14.4%, OBR average earnings +11.5%.
All three indices already had the yoy_growth twin the YAML needs, so this is
a YAML-only change.

rail_usage is deliberately NOT included. Its declared index,
gov.dft.rail.ridership_index, is a CUMULATIVE index with 2020 = 1.0 —
"Used for uprating the quantity component of rail subsidy spending" — not a
year-on-year rate. Adding it to a yoy_growth block compounds the level and
uprates the variable by about 684% over 2024-27. Uprating it properly needs a
year-on-year series, which is more than a YAML entry.

The durable fix is the code-health test suggested in review of #1860:
policyengine_uk/tests/code_health/test_uprating_declarations.py asserts that
every variable declaring `uprating` appears in the YAML under the matching
index. Verified it fails when an entry is removed, rather than only passing
today.

Three exceptions are listed with reasons rather than silently tolerated:
rail_usage and employee_pension_contributions_reported as not uprated, and
savings_interest_income as uprated by a different index from the one it
declares — the mismatch noted in #1862, left for someone with the context to
say which is intended. A third test keeps those lists honest by asserting each
entry still exists and still declares uprating.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
vahid-ahmadi added a commit that referenced this pull request Oct 12, 2026
Follow-up to #1860, closing most of #1862.

Three variables declared an uprating index but were missing from
uprating_indices.yaml, so they were never uprated. On enhanced_frs_2024_25,
2024 -> 2027:

  water_and_sewerage_charges                  375.51 -> 522.12  (+39.0%)
  housing_service_charges                      91.81 -> 105.06  (+14.4%)
  pension_contributions_via_salary_sacrifice  418.69 -> 466.81  (+11.5%)

Each matches its index compounded over the same years exactly: ofwat water
bills +39.0%, OBR lagged average earnings +14.4%, OBR average earnings +11.5%.
All three indices already had the yoy_growth twin the YAML needs, so this is
a YAML-only change.

rail_usage is deliberately NOT included. Its declared index,
gov.dft.rail.ridership_index, is a CUMULATIVE index with 2020 = 1.0 —
"Used for uprating the quantity component of rail subsidy spending" — not a
year-on-year rate. Adding it to a yoy_growth block compounds the level and
uprates the variable by about 684% over 2024-27. Uprating it properly needs a
year-on-year series, which is more than a YAML entry.

The durable fix is the code-health test suggested in review of #1860:
policyengine_uk/tests/code_health/test_uprating_declarations.py asserts that
every variable declaring `uprating` appears in the YAML under the matching
index. Verified it fails when an entry is removed, rather than only passing
today.

Three exceptions are listed with reasons rather than silently tolerated:
rail_usage and employee_pension_contributions_reported as not uprated, and
savings_interest_income as uprated by a different index from the one it
declares — the mismatch noted in #1862, left for someone with the context to
say which is intended. A third test keeps those lists honest by asserting each
entry still exists and still declares uprating.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.

electricity_consumption and gas_consumption declare CPI uprating but are never uprated

2 participants