Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/energy-not-cpi-uprated.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Stop projecting `domestic_energy_consumption`, `electricity_consumption` and `gas_consumption`. The data build calibrates them to NEED mean kWh at Ofgem Q2 2026 unit rates, so the stored values already carry FY26/27 price levels and projecting from the data year re-applied price changes that were already included. This reverses the direction of the fix in #1860, which resolved the #1859 inconsistency by uprating electricity and gas to match the aggregate rather than by removing all three. Modelled energy spending in projected years falls by about 8%.
5 changes: 4 additions & 1 deletion docs/book/assumptions/growthfactors.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ We use the OBR's CPI projections to drive most benefit and consumption variables

We apply CPI to these variables:
- All reported benefits– `carers_allowance_reported`, `child_benefit_reported`, `child_tax_credit_reported`, `esa_contrib_reported`, `esa_income_reported`, `housing_benefit_reported`, `iidb_reported`, `incapacity_benefit_reported`, `income_support_reported`, `jsa_contrib_reported`, `jsa_income_reported`, `maternity_allowance_reported`, `pension_credit_reported`, `sda_reported`, `state_pension_reported`, `universal_credit_reported`, `winter_fuel_allowance_reported`, `working_tax_credit_reported`
- All consumption categories– `alcohol_and_tobacco_consumption`, `clothing_and_footwear_consumption`, `communication_consumption`, `domestic_energy_consumption`, `education_consumption`, `food_and_non_alcoholic_beverages_consumption`, `health_consumption`, `household_furnishings_consumption`, `housing_water_and_electricity_consumption`, `miscellaneous_consumption`, `recreation_consumption`, `restaurants_and_hotels_consumption`, `transport_consumption`
- All consumption categories EXCEPT energy– `alcohol_and_tobacco_consumption`, `clothing_and_footwear_consumption`, `communication_consumption`, `education_consumption`, `food_and_non_alcoholic_beverages_consumption`, `health_consumption`, `household_furnishings_consumption`, `housing_water_and_electricity_consumption`, `miscellaneous_consumption`, `recreation_consumption`, `restaurants_and_hotels_consumption`, `transport_consumption`

`domestic_energy_consumption`, `electricity_consumption` and `gas_consumption` are deliberately excluded. The data build calibrates them to NEED mean kWh and converts to pounds at Ofgem Q2 2026 unit rates, so the stored values already carry FY26/27 price levels; uprating them from the dataset's data year would re-apply price changes that are already included. See PolicyEngine/policyengine-uk#1867.

- Other variables– `afcs_reported`, `bsp_reported`, `childcare_expenses`, `free_school_fruit_veg`, `free_school_meals`, `free_school_milk`, `maintenance_expenses`, `statutory_maternity_pay`, `statutory_paternity_pay`, `statutory_sick_pay`, `state_pension`

## Road fuel volume
Expand Down
26 changes: 23 additions & 3 deletions policyengine_uk/data/uprating_indices.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,26 @@
# Energy variables are deliberately ABSENT from every index below.
#
# SCOPE: this file governs DATASET PROJECTION only — the uprating applied to
# a dataset's stored values when it is projected to later years. It does not
# govern the class-level `uprating` attribute on the Variable, which
# policyengine-core still uses to carry a direct situation input forward from
# an earlier period. That path is unchanged and out of scope here: a 2024
# value a user supplies directly may be their own contemporaneous bill rather
# than this data build's artifact, so it has no claim on the build's price
# basis (#1868 review A1).
#
# policyengine-uk-data calibrates domestic_energy_consumption,
# electricity_consumption and gas_consumption to NEED mean kWh by income band
# and converts to pounds at Ofgem Q2 2026 unit rates, so the stored values
# already carry FY26/27 price levels. 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."
#
# Projecting them from the dataset's DATA year (2024) would therefore
# re-apply 2024-2026 price changes the build has already priced in (#1867).
# Other consumption variables come from the LCFS at data-year prices and do
# need uprating, which is why only these three are excluded.
gov.economic_assumptions.yoy_growth.obr.average_earnings:
- employee_pension_contributions
- employer_pension_contributions
Expand All @@ -16,16 +39,13 @@ gov.economic_assumptions.yoy_growth.obr.consumer_price_index:
- childcare_expenses
- clothing_and_footwear_consumption
- communication_consumption
- domestic_energy_consumption
- education_consumption
- electricity_consumption
- esa_contrib_reported
- esa_income_reported
- food_and_non_alcoholic_beverages_consumption
- free_school_fruit_veg
- free_school_meals
- free_school_milk
- gas_consumption
- health_consumption
- household_furnishings_consumption
- housing_benefit_reported
Expand Down
51 changes: 51 additions & 0 deletions policyengine_uk/tests/code_health/test_energy_price_level.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
"""Energy variables must not be uprated by DATASET PROJECTION.

policyengine-uk-data calibrates the energy variables to NEED mean kWh and
converts to pounds at Ofgem Q2 2026 unit rates, so the stored values already
carry FY26/27 price levels. Its imputations/consumption.py states that they
"are not CPI-uprated, so this avoids the need for price-level adjustment at
simulation time".

Projecting them from the dataset's data year re-applies price changes the
build has already applied. #1859 reported the resulting inconsistency and
#1860 resolved it the wrong way round — by uprating electricity and gas to
match domestic_energy_consumption, rather than by removing all three (#1867).

SCOPE. This guard covers uprating_indices.yaml, which governs dataset
projection. It deliberately does NOT cover the class-level `uprating`
attribute, which policyengine-core still uses to carry a direct situation
input forward from an earlier period. That path is unchanged: a value a user
supplies directly 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. Worth
noting because #1860 and #1862 described that attribute as dead metadata —
it is inert for dataset projection, but core does read it for direct inputs
(#1868 review A1).
"""

from pathlib import Path

import yaml

UPRATING_INDICES = Path(__file__).parents[2] / "data" / "uprating_indices.yaml"

# Calibrated to NEED at Ofgem Q2 2026 rates, so already at FY26/27 prices.
ENERGY_VARIABLES_AT_BUILD_PRICE_LEVEL = {
"domestic_energy_consumption",
"electricity_consumption",
"gas_consumption",
}


def test_energy_variables_are_not_projected_by_any_index():
listed = yaml.safe_load(UPRATING_INDICES.read_text())
offenders = sorted(
f"{variable} (under {index})"
for index, variables in listed.items()
for variable in variables
if variable in ENERGY_VARIABLES_AT_BUILD_PRICE_LEVEL
)
assert not offenders, (
"these variables are stored at the data build's own FY26/27 price "
f"level and must not be projected by an index: {offenders}. "
"See #1867."
)
Loading