Repository navigation
Conversation
Adds gov.economic_assumptions.energy_prices: ONS CPI electricity (D7DT), gas (D7DU) and electricity, gas and solid fuels (DK9U) to the latest month, then the OBR October 2026 utilities inflation forecast (Table 1.19), with the electricity VAT cut and its Budget 2026 extension divided out so every level holds VAT at the energy input basis rate. Consumption-weighted calendar and fiscal year indices feed uprate_energy and the energy variables' uprating. no_economic_assumptions holds them flat from its first input year. WIP: tests and docs follow. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- test_energy_price_indices.py: coverage, the outturn VAT division, the OBR round trip, quarterly and annual alignment, held_flat, statutory VAT invariance (Hypothesis), Core uprating against dataset projection. - test_import_energy_prices.py and cut-down real ONS, OBR and DESNZ fixtures: the importer reproduces the committed YAML byte for byte. - Energy projection, reset and ISA tests control the energy indices rather than CPI; process-time reform tests expect the energy_prices tree. - Check ONS gaps before the forecast in quarterly_levels. - README section on household energy prices and refresh step; dataset sources and growth factor docs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…dates
A reform keyed by year, such as {"...energy_prices.obr.utilities": {"2027":
0.5}}, leaves one stated value for the year, and the generator read only
stated values, so it found no rate for 2027Q2 (Sol review r1). Reforms to
the ONS monthly prices failed the same way.
- The generator reads each input's effective value at the month or quarter it
applies to.
- The outturn ends at ons.metadata.last_full_quarter, which the importer
writes and a reform cannot move. Before, it ended where the stated months
ran out, and a reform's re-inserted value would have moved it.
- Source gaps: a test that ons.yaml states every month to that quarter, and
errors for removed values and for prices that end before it.
- Tests: a Hypothesis property over year- and quarter-span forecast reforms,
a public-API regression and year-keyed ONS reforms.
- The docstring no longer says "before the Budget measures".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Parameter.update with a start and no stop removes every stated value after its start. The generator checked that each ONS series' stated values reached the end of last_full_quarter, so such a change raised "ONS electricity prices end in 2025-01" although every month still had an effective price. Drop the stated-date checks on the ONS series: ons_price already stops generation at any month with no effective value, and a test keeps the committed file stating every month. Add a Hypothesis property for open-ended ONS changes, an open-ended shape to the forecast property, and gap tests at 2015-01 and 2026-09. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Both sides added entries to two lists. GENERATION_STEPS keeps add_energy_price_indices after create_economic_assumption_indices, before the Pension Credit and Housing Benefit projections, which do not read it. The generated-parameter test adds the energy index prefixes to the base's generated_prefixes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Name who acts in the README section, docstrings, comments, YAML descriptions and the changelog entry. The importer writes the new headers and descriptions, so the generated YAML still matches it byte for byte. List the energy step in process_parameter_tree's docstring. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The importer checked the monthly prices for gaps but not the annual CPI weights. A blank weight (say CJXA for 2026) rendered, and generation then gave that year the year before's weight: FY2026 electricity came out at 207.293 instead of 207.392. Require a weight for every year from 2015 to the last year with prices, for each series, and test a missing weight in each series in 2020 and 2026. The committed inputs have every weight, so nothing they produce changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Hand-off to the UK hub (from the energy price index session)
|
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
Standard reform costings (change in gov_balance)
Standard reforms: change in each reform's target
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; household income totals; population; poverty rates; people in poverty; inequality; mean household net income in each side's own deciles. Checks failing on both base and head (1)
Counted-once checkEach of 91 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
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
|
Built on #2717 (October 2026 EFO), which merged on 29 Oct; GitHub retargeted this PR to
main.Datasets that declare an
energy_price_basisused to project household electricity and gas spending with headline OBR CPI. This PR replaces that with household energy price indices,gov.economic_assumptions.energy_prices, which it builds from ONS retail energy prices and the OBR's utilities forecast, holding VAT on electricity at the inputs' 5% basis. Part of #2526: its first remaining item, an energy-specific index, which this PR takes from ONS CPI energy prices rather than the Ofgem cap.Why hold VAT at 5%
domestic_energy_vatdivides the energy inputs by one plusgov.simulation.vat.energy_input_price_basis_rate(5%) and applies the statutory rate in force. An index that included the zero rate on domestic electricity would count the cut twice: once in lower spending and again in the tax. The published prices and the OBR forecast both include it (EFO para 3.10). So the index divides out the VAT rate each month's price includes, and its change in the forecast.embedded_electricity_vat.yamlrecords the rates the published prices include:Otherwise the rate is 5%. A reform to
gov.hmrc.vatnever moves the index.The other Budget 2026 measures that touch bills stay in: carbon price support abolition and CfD levy funding. The EFO gives them no inflation effect (para 3.10 covers fuel duty and the VAT cut). It says they "are intended to offset the increase in electricity bills" from BICS for consumers outside energy-intensive industry (para 3.19).
Method
Sources
Quarterly levels
ONS_m / (1 + s·((1 + r_m)/1.05 − 1)). That quarter islast_full_quarterin theonsnode metadata (2026Q3), which the importer writes.r_mis the UK electricity VAT on the 15th: the mean of the GB and NI rates, weighting NI by its 2.69% share.sis electricity's share of the series' CPI weight: 1 for electricity, 0 for gas, CJXA/A9F4 for the combined series.L(q) = L(q−4)·(1 + g_OBR)·u(q−4)/u(q).uis the same VAT factor for electricity's share of utilities, CJXA/(CJXA + CJXB), as the quarter's mean.Annual levels (
calendar_yearJan–Dec,fiscal_yearApr–Mar, 2015–2041, each at 1 Jan)Projection
uprate_energyreads the declared basis's kind at the basis year: calendar 2024 is the Jan–Dec 2024 level; fiscal 2024 is Apr 2024–Mar 2025.upratingnames the same fiscal-year indices, so Core's carry-forward of direct inputs agrees with dataset projection.no_economic_assumptionssets aheld_flatinput that holds the annual indices at the previous year's level.Importer:
policyengine_uk/utils/import_energy_prices.pywritesons.yaml,obr.yamlandconsumption_shares.yaml. It reproduces them byte for byte from cut-down real source fixtures; the SHA-256s of the full files are in the fixture README.Impact
Projection ratio from a calendar-2024 basis to model year Y (fiscal year Y–(Y+1)), against the old CPI ratio:
What changes and what doesn't
energy_price_basis, so its energy amounts stay flat through 2032 as before. After 2032, Core's carry-forward of its amounts grows them at the energy indices' rate (1.93% a year to 2041, then flat), not CPI.Invariants (tested)
obr.yaml). Without a VAT change, growth equals the OBR rate. A hand-computed 2027Q1 check confirms the adjustment, and gas 2028Q3 equals −7.0888%.domestic_energy_vatmoves.held_flatholds annual levels exactly (Hypothesis).no_economic_assumptionsholds direct amounts flat, alone and combined with a reform in either order.last_full_quarterwhatever a reform states, and a month or quarter with no value stops generation. Year-keyed reforms build through the public API.Limits
Review
{"…obr.utilities": {"2027": 0.5}}crashed the generator, which read only stated dates. ONS reforms crashed too. Fixed: the generator reads effective values, and the outturn boundary is node metadata a reform cannot move. Four new tests failed before the fix and pass after.Parameter.update(start=…)with no stop removes the stated values after its start, and the generator's stated-date check then rejected the ONS series as ending there, although every month has an effective value. Fixed: the stated-date checks on the ONS series are gone; a month with no effective value still stops generation, and a test guards that the committed file states every month. A new Hypothesis property covers open-ended ONS changes, and the forecast property gains an open-ended shape. The starts it reported fail on the old generator and build on the new one.GENERATION_STEPSand the generated-prefix test, and the merge keeps both.~/reviews/ab26-baseline/energy-price-index/review_r2b.md): CHANGES, one minor finding. It found the reform fixes, the merge and the active-voice edits sound, and its own recalculation matched the impact table and all 489 baseline levels.review_r3.md): APPROVE, no findings. It rejected all 36 missing-weight combinations, and the pre-fix parser accepted them.Tests run locally
On the merged branch at edc2ce7, all 4,145 tests passed across the 45 test files that touch parameters, uprating, scenarios, VAT and energy, plus
code_health(-n 4), as did the 37 energy and VAT YAML policy tests. At 3ddc4b1 the 16 importer tests passed, and Sol r3 ran 69 importer and index tests. ruff is clean.Docs
docs/engineering/skills/dataset-sources.mdanddocs/book/assumptions/growthfactors.mddescribe the energy indices.axiom: n/a: data/microsim-only change
🤖 Generated with Claude Code