Repository navigation
Project labelled energy inputs from their declared price year - #2571
Conversation
Aggregate impact on certified data: passBase Checks: 0 broken by this change, 1 failing on both sides, 0 fixed. Moves flagged for review: 0 (thresholds in Notes
Moves flagged for reviewNone. Spending by benefit and service
Caseloads
Household net income by decileNo household's net income changes in any year. No change in any year: receipts by tax head; government balance, each component counted once; model totals; components the model counts twice; household income totals; population; poverty rates; people in poverty; inequality; mean household net income in each side's own deciles; standard reform costings (change in gov_balance); standard reforms: change in each reform's target. Checks failing on both base and head (1)
Counted-once checkEach of 84 components was raised by £1 per unit in 2026-27, and every head of its totals was read household by household: its own head must rise by exactly £1, no other tax head may move, no other benefit may rise, and the model's own total must move by what its heads moved. A head that breaks a rule is measured again twice: with every other head held at its baseline value, and with every other head held at its value after the first £1 while the component rises again. It fails where either still shows the response. 0 failed; 0 were skipped.
Provenance
Dataset: Receipts by tax head: head levels
Spending by benefit and service: head levels
Caseloads: head levels
Government balance, each component counted once: head levels
Model totals: head levels
Components the model counts twice: head levels
Household income totals: head levels
Population: head levels
Poverty rates: head levels
People in poverty: head levels
Inequality: head levels
Mean household net income in each side's own deciles: head levels
Standard reform costings (change in gov_balance): head levels
Standard reforms: change in each reform's target: head levels
|
vahid-ahmadi
left a comment
There was a problem hiding this comment.
@juaristi22 thanks, this is a careful consumer contract and the code does what the body says. Review at head ff134627, round 1. No must findings; two shoulds, mostly about the index and about #2573.
What I checked
- New and touched tests in a fresh env at the head:
test_energy_dataset_projection.py,test_energy_price_basis.py,test_energy_reset_coherence.py,code_health/test_energy_price_level.py,code_health/test_uprating_declarations.py: 90 passed. The reproducers cannot run on the base, which has noenergy_price_basisargument. - CI at the head is green, including Test / Result and Aggregate impact.
- A one-household projection from a 2024 dataset: unlabelled stays at 1,000 in every year to 2032; a
calendar_year 2024declaration gives 1,034 (2025), 1,057.8 (2026), 1,191.2 (2032), so there is no second growth after 2030 and no double projection. - Aggregate impact: no change in any year on
enhanced_frs_2024_25(1.58.1), as expected, since no released file carries the attribute. A code search across PolicyEngine finds no producer writingenergy_price_basisyet, so the PR is inert for every current dataset until Microcosm or a caller adopts it.
Findings
-
should: the index is headline OBR CPI, and the CY/FY collapse matters for energy.
economic_assumptions.py:140setsENERGY_PRICE_INDEXtogov.economic_assumptions.indices.obr.consumer_price_index, anduprate_energy(economic_assumptions.py:396-400) reads it at{year}-01-01for both calendar and fiscal declarations. #2526 asked for an energy price index. The repo has no ONS CPI energy component series (COICOP 04.5) to switch to, but it does hold Ofgem's quarterly cap ingov.ofgem.energy_price_cap(each quarter referenced to an Ofgem release; I checked the July and October 2026 releases). On the previous typical consumption values, so the quarters compare like for like:- FY2024-25 average: (1,690 + 1,568 + 1,717 + 1,738) / 4 = £1,678.
- CY2024 average: (1,928 + 1,690 + 1,568 + 1,717) / 4 = £1,726, 2.8% above FY2024-25. The PR maps both to
I[2024]. - FY2026-27 so far: £1,641 (April), £1,862 (July; Ofgem gives this as the bill on the old values, £1,663 on the new), and about £1,929 for October (£1,723 on the new values, scaled by 1,862/1,663).
- Three-quarter average about £1,811: +7.9% on FY2024-25 and +4.9% on CY2024. The CPI ratio
I[2026]/I[2024]is 1.058.
So from an FY2024-25 basis CPI understates 2026-27 energy prices by about 2pp, and from CY2024 it overstates by about 1pp. The sign depends on a distinction the projector drops. Both are small next to the current flat projection, but energy is the one input where CPI and the price path diverge most. I'd suggest either (a) projecting outturn quarters with the Ofgem cap series (on a constant consumption basis) and CPI only beyond the last announced cap, or (b) keeping CPI but saying in the README and changelog, with the numbers, that it is a placeholder and the CY/FY collapse is about 3% for 2024. Your call which; (b) is enough for this PR if a follow-up issue tracks (a).
-
should:
uprate_energyand the reset group take their variable set fromDATASET_UPRATING_EXCLUSIONS, which #2573 widens.economic_assumptions.py:388-391,:712,:716-728. Your draft #2573 addsliquid_fuel_consumptionandsolid_fuel_consumptionto that set. After both merge, a declared basis would also CPI-project heating oil, LPG, coal and wood, and the "one declaration describes every stored energy amount" rule would cover them, although the declaration a Microcosm release would carry describes its DESNZ-priced electricity and gas, not fuels that #2573 takes from the LCFS (COICOP 04.5.3 and 04.5.4). Please give the declared group its own constant (for exampleDECLARED_ENERGY_INPUTS, the three current inputs) and keepDATASET_UPRATING_EXCLUSIONSfor "not in the YAML or fallback". Then #2573 can decide on its own whether the fuels join the declared group. The two PRs also edit the same comment block aboveLEGACY_PROJECTION_END_YEAR, theuprating_indices.yamlheader, the economic assumptions README andKNOWN_UNUPRATED, and #2573 still says energy "already [has] FY26/27 prices" there. I'd merge this one first and rebase #2573 on it. -
nit: a declared year before the index starts gives a silent wrong ratio.
EnergyPriceBasisaccepts years from 1 to 9999 (energy_price_basis.py:30), but Core's_uprating_index_valuereturns the CPI index's first value (1.0 at 2009) for any earlier date. Afiscal_year 1990declaration projects 1,000 to 1,610.6 in 2025. Rejecting years before the index's first date (or treating them as undeclared with a warning) would close this. -
nit: the base-year observation is never repriced. With a
fiscal_year 2026declaration on a 2024 dataset, 2024 stays at 1,000 while 2025 drops to 977.5. That is the documented "the observation remains unchanged", and harmless for Microcosm's CY2024 basis on a 2024 dataset, but a line indataset-sources.mdsaying the declared year should match the data year (or that the base year keeps the declared prices) would help a caller. -
nit: PR body and changelog. The body ends
axiom: needed; as a maintainer you'd normally give the axiom line a decision rather thanneeded.changelog.d/2526.fixed.mdis afixedfragment but reads "Added ... declarations"; either type is defensible, but the text should say what changes for users: nothing until a dataset declares its price year.
Related PRs
- #2189 (0% VAT on GB domestic electricity, mine): no shared files, so no conflict in either merge order, and its 2026-27 costing (£0.62bn on Microcosm
f9d1922c) does not move until a release declares a basis. Once one declares FY2024-25, CPI scales the electricity base by 1.058 in 2026-27, so roughly £0.66bn. #2189's note that projecting energy is "outside this PR" will then need to point here. - #2504 (Warm Home Discount, mine): uses no energy spending input, so no effect.
- #2573: see finding 2.
One correction on our side: #2526 proposed "falling back to the dataset year" when no price year is declared. That would have been wrong: the legacy uk-data build is labelled 2024 but priced at FY2026-27, so the fallback would have double-counted 2024 to 2026 price growth. Keeping unlabelled files flat, as you did, is right.
🤖 Generated with Claude Code
The fuels had joined DATASET_UPRATING_EXCLUSIONS, which holds the electricity and gas inputs flat because their stored amounts are NEED kWh priced at Ofgem's 2026-27 unit rates. Ofgem's cap does not cover heating oil, LPG, coal or wood, and the planned source is Microcosm's COICOP 04.5.3 and 04.5.4 figures at data-year prices. Holding them flat would leave projected years at 2024 prices, and #2571, which projects that exclusion set from a declared price year, would then apply an energy declaration to them that does not describe them. They now leave the exclusions and are listed under CPI in uprating_indices.yaml, as the other LCFS consumption variables are, until a dataset declares their own price year. This also makes this PR and #2571 independent in meaning. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Merged by the UK hub (Claude session 8e19f9d5) at head
|
Refs #2526
Project explicitly labelled domestic-energy, electricity and gas inputs from their declared price year using the existing annual OBR CPI assumption. Unlabelled files keep their current flat projection, as agreed. Calendar-year and fiscal-year declarations use the existing start-year/January index approximation.
Preserve the declaration through dataset loading, filtering and projection. Reset energy amounts and their basis as a coherent group, retaining authoritative later observations when an incomplete base cannot replace them consistently. Keep direct and multi-year inputs authoritative and prevent repeated projection from applying inflation twice.
This adds the consumer contract. It does not inspect or retrofit private metadata-free releases; verified producer or caller adoption remains open under #2526.
Validation:
Reproducer: policyengine_uk/tests/test_energy_dataset_projection.py::test_declared_2024_prices_reach_2026_without_changing_the_source
Reproducer: policyengine_uk/tests/test_energy_reset_coherence.py::test_external_energy_reset_survives_loading_and_extension
axiom: needed