Skip to content

Set UK road-fuel and energy levels in the LCFS stage on a calendar-2024 basis (#1113) - #1167

Merged
juaristi22 merged 18 commits into
mainfrom
uk-consumption-tax-levels
Oct 11, 2026
Merged

juaristi22 merged 18 commits into
mainfrom
uk-consumption-tax-levels

Conversation

@juaristi22

@juaristi22 juaristi22 commented Oct 9, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Closes #1113. Also covers item 3 of #1123. The UK LCFS stage now sets household road fuel and domestic energy at their published levels, at calendar-2024 prices, instead of leaving the calibration weights to reach them. The rows whose gap is not a weight question become measured diagnostics. This follows María's rulings of 2026-10-08 (D1–D3), plus two of 2026-10-09: the ONS road-fuel row is bound, and every LCFS spend column sits on one calendar-2024 basis.

  • Calibration rows. obr.vat, ons.household_electricity_expenditure, ons.household_gas_expenditure and obr.fuel_duties_cars move to the measure-exclusion register. They are still measured on every evaluation, and the entries expire 2027-01-08. The contract binds a new household-scope row in their place: ons.household_road_fuel_expenditure (ONS Consumer Trends 07.2.2, calendar 2025, measured as petrol plus diesel spend). The class also carries other motor fuels and lubricants, so the row reads about 0.4% low from scope alone; the binding notes record that offset.
  • What no longer holds what. After this PR no bound row and no release-blocking check holds calibrated domestic energy, the VAT base or fuel duty. The stage gates hold the energy and fuel levels at prior weights, the bound ONS row holds household road-fuel spend, and the report-only consumption_drift shows the rest. That follows from D1–D3, and the gate and register notes say so.
  • LCFS stage. Three declared steps:
    • redraw_zero_road_fuel gives every flagged fuel-car household a positive fuel spend;
    • level_road_fuel scales petrol and diesel to ONS 07.2.2 for 2024, less the donor's other-fuels share;
    • recompose_from_remainder writes housing around the levelled electricity and gas, and transport around the levelled petrol and diesel and the priced bus fares.
  • Calendar-2024 basis.
    • The diary columns are carried on the engine's CPI index from the April-to-March 2023-24 diary year to calendar 2024. That is three quarters of a year's step (1.0186), not the full year's (1.025), which would land a quarter past it.
    • Energy is priced at QEP's calendar-2024 averages and levelled to the DESNZ volume over the four quarters of 2024.
    • Each area's bus yield is re-priced to 2024 by its fares index.
    • policyengine-uk can then project every column from the H5's year.
  • Gates. Four new release-blocking stage-health gates check the receipts: road_fuel_incidence, road_fuel_level, recomposed_totals and consumption_basis. The last reads each diary column's declared window. The housing and transport totals also keep their committed support bounds, which the terminal support gate applies to the emitted frame.
  • Diagnostics (report-only; a block records why it has no figures when a published row is missing, and does not stop the stage):
    • uk_diagnostics.consumption_drift: the levelled totals and shares at design and final weights;
    • division_capture in the stage receipt: each COICOP division against ONS Consumer Trends;
    • the litres audit: each fuel against its cars share of road use.
  • Feed. The UK Chronicle feed is re-pinned to 1ee7dfe (UK consumption-tax facts: Consumer Trends for every COICOP division, and road fuel by vehicle type and fuel (PolicyEngine/microcosm#1113, PolicyEngine/microcosm#1123) chronicle#322 via Expand UK consumption-tax expenditure and road fuel facts chronicle#323, 352,549 rows), which brings Consumer Trends by division and DESNZ road fuel by vehicle type and fuel. The re-pin moves no compiled target value on either surface and leaves the three compile-parity receipts as they were. The new road-fuel row adds one ledger-only row to each national receipt.

Receipts, findings and rulings are in experiments/1113-consumption-tax-levels-receipts.md. The measurement scripts and their aggregate outputs are in docs/evidence/uk-consumption-taxes-1113/.

Why the levels belong to the stage

On #1121's head calibration (policyengine-uk 2.122.2, 1,167 rows):

  • obr.vat reads +24.7% at design weights, against a 25% bound. Its level is the engine's microdata_vat_coverage factor (0.38, with no recorded derivation; policyengine-uk#1996), not a weight question.
  • The ONS electricity and gas rows sit below the unit cost of the DESNZ volume at QEP prices, so they are a published-source residual.
  • obr.fuel_duties_cars reads −34.5% at design weights. It is a fiscal-year duty total that includes business use of cars. The LCFS diary records about a quarter less road fuel than ONS, and a third of fuel-car households show no fuel purchase in the two-week diary.
  • Closing these rows through the weights distorts the frame. From design to final weights the gas-connected share fell from 84.5% to 78.9%, petrol rose 21% and diesel 34%.

On the 2026-09-30 national problem (1,090 rows, before this PR's stage levels), a re-solve with the four rows masked together left the other 1,086 rows fitting no worse: 98.5% within 10%, against 98.4%. No arm masks one row alone.

Stage-only measurements

These re-run lcfs_consumption on #1121's head-r3 spine (63,956 households). All seven lcfs stage-health gates pass. A re-run redraws most households, so the comparison is with main's stage re-run on the same spine, not with the spine's stored columns.

  • Total consumption moves from £969.6bn to £966.8bn (−0.3%):
    • the ten diary divisions move −0.7%, the calendar bridge;
    • housing moves from £180.1bn to £172.1bn;
    • transport moves from £173.0bn to £182.4bn.
  • Road fuel. No flagged fuel-car household is left at zero fuel (33.1% of them by weight before the redraw). Petrol plus diesel is levelled to £36.89bn: ONS 07.2.2 for 2024 (£37.03bn) less the donor's 0.37% other-fuels share.
  • Energy. Electricity is £29.5bn and gas £19.2bn at calendar-2024 prices, with 84.6% of households connected to gas.
  • Totals. No household's housing total is below its electricity and gas, and none's transport total is below its petrol, diesel and bus fares.
  • Division capture. The frame's twelve divisions are 72.4% of ONS's 2024 spending in survey scope; the donor's own diary is 58.5%.
    • Housing reads 1.051. D1's energy level is £8.0bn of its £8.3bn excess.
    • Furnishings read 1.242: the frame draws 61% more per household than the donor. That is not investigated here.
  • Litres audit. Against HMRC clearances times DESNZ's cars share of each fuel, petrol reads 1.008 and diesel 0.713.
  • The bound row at design weights (desk figure). ONS's own move from 2024 to 2025 is −5.2%; policyengine-uk 2.122.2 carries petrol and diesel −2.7%. With the scope offset the row reads about +2.3% on that version. R1 records the measured value.

Not in this PR

#1207 collects these open items, under the two deferral registers #736 and #796. The three measure exclusions that are not VAT track it.

Merge order with #1198 and #1145

Testing

  • Targeted engine-free tests for each commit's surface, and a broad funnel at the head (counts in the review replies).
  • During the rebase onto 7f235941c, a check ran after every commit: each loads the UK spec, has a current release-input coverage manifest, and has current gate digests mirrored in microcosm-data. The review commits pass the same check.
  • The charter-H2 spine fixture is regenerated by each commit that moves it. The commits before 3ed090d03 fail test_h2_uk_spine_parity on their own.

🤖 Generated with Claude Code

juaristi22 and others added 14 commits October 9, 2026 14:50
…ith a diagnose tool (#1113)

The national solve was closing three level gaps by moving weights. obr.vat
reads +24.7 percent at design weights and +24.1 percent calibrated on
#1121's head calibration (policyengine-uk 2.122.2), against the 25 percent
bound: the engine divides household VAT by microdata_vat_coverage (0.38, a
constant since January 2023), and the frame's raw household VAT is about 46
percent of the OBR receipts (policyengine-uk#1996). The ONS 04.5.1 and
04.5.2 rows sit below the stage's DESNZ volume at QEP prices on every
aligned basis (gas below unit cost alone), and fitting them cut electricity
8.6 percent, gas 18.9 percent and the gas-connected share from 84.5 to 78.9
percent. A re-solve of the 2026-09-30 problem with the rows masked changed
no other row's fit (98.5 against 98.4 percent within 10 percent).

The three rows join the measure-exclusion register on María's 2026-10-08
ruling (VAT until the engine factor is split; the energy rows as a
published-source residual, with DESNZ volume at QEP prices kept as the
level), windowed to 2027-01-08. tools/diagnose_uk_consumption_taxes.py keeps
them measured on any H5 the engine loads, against the values the run's
contract registry declares, beside the fuel and energy composition the rows
pulled. The twelve COICOP division columns become a named view beside the
LCFS rename map so the tool imports them rather than copying them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The LCFS diary records about a quarter less road fuel than ONS household
spending, and binding the OBR cars receipts closed that gap by moving weight
onto fuel-buying households. A declared level_road_fuel step after the fuel
flag now scales petrol and diesel by one factor, so their prior-weighted total
equals ONS Consumer Trends COICOP 07.2.2 for calendar 2024 (the year of the
DESNZ pump prices the donor uprating uses) less the donor's own c72213
other-fuels share. The drawn mix and each household's relative spend are kept.

- lcfs_consumption reads ons_household_expenditure_facts.json, now its
  declared consumer (re-vendored; values unchanged).
- The uk_stage_lcfs_consumption_road_fuel_level stage-health gate recomputes
  the level from the vendored row and checks that the factor reproduces it;
  the licensed donor's other-fuels share is bounded at 5%.
- Petrol and diesel join the levelled columns, so they carry no committed
  support bound (regenerated from the pinned LCFS tabs).
- The existing litres audit runs after the step, so its frame litres at DESNZ
  pump prices describe the levelled frame.
- Mirrors: operation schema and kind, stage_health parameter vocabulary,
  spine gate scope, gate-battery digests and entry lists, coverage manifest.
  The graph parity fixture is regenerated once at the end of the series.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The LCFS diary covers two weeks, so about a third of car-owning donors record
no fuel purchase, and the chain reproduced that zero share among the
households the fuel flag marks as buying petrol or diesel (34.3% at prior
weights on #1121's head). Over a year such a car is refuelled; binding the
OBR cars row had moved weight off those households to compensate.

A declared redraw_zero_road_fuel step, between the fuel flag's zeroing and
the level step, redraws a flagged household whose chain draw is zero from the
donor's distribution among households with positive road fuel, on the
chain's predictors. A weighted regime-gated QRF draws the total, positive by
construction, then the petrol share of it, which keeps diesel-only and mixed
households. The quantiles are keyed on the household id
(predict_from_uniforms), so a household's draw doesn't depend on row order.
Positive chain draws are kept, and the level step then rescales the total.

- The receipt records the flagged zero share before and after, the mean
  among positive households before, the redrawn mean and mix, and the model's
  regimes. Its two fits add fit-weight records.
- The uk_stage_lcfs_consumption_road_fuel_incidence gate checks the declared
  rule, seed, salt and trees; that no flagged household is left at zero and
  no unflagged one carries fuel; and the total's positive-only regime. It
  fails if the chain's zero share passes one half, which would point at the
  chain rather than the diary.
- The end-to-end stage test's synthetic donor now has one-car diaries with no
  purchase, so the redraw runs there, and the stage's receipts pass both
  road-fuel gates.
- Mirrors: operation schema and kind, stage_health parameter vocabulary,
  spine gate scope, gate-battery digests and entry lists, coverage manifest,
  seed pin.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…#1113)

The contract declares ons.household_road_fuel_expenditure: ONS Consumer
Trends COICOP 07.2.2 (fuels and lubricants for personal transport), calendar
year, household scope, with value_expression "petrol_spending +
diesel_spending". It resolves to GBP 35.099bn for 2025. Its notes cover the
other motor fuels and lubricants the class carries and the measure doesn't
(0.4% on the LCFS diary), and why its design-weight gap is the engine's
2024-to-2025 uprating, since the stage levels the columns to the 2024 row.

obr.fuel_duties_cars moves to the measure-exclusion register, still measured
on every evaluation. Its gap breakdown:
- The OBR cars line includes business-paid car fuel. ONS 07.2.2 at DESNZ
  pump prices implies about GBP 13.5bn of duty, 7% under it.
- With the level step in place, the engine's fuel_duty reads -9.9% against
  it (-8.1% on DESNZ divisors; policyengine-uk#2169 A). Binding it would move
  weight onto fuel buyers to stand in for business use.

This commit is the only one that changes if the OBR cars row is kept bound
instead.

- National references regenerated: 1,232 active, with 7
  no_fact_at_or_before_period and 16 signed excluded unchanged. The local
  census is unchanged.
- Both compile-parity receipts gain the row as ledger_only.
- tools/diagnose_uk_consumption_taxes.py measures the new row beside the
  held-out ones. The register census and the contract and reference counts
  are re-pinned.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The chain draws COICOP 04 (housing, water and fuel) and 07 (transport) as
whole diary totals, while the stage re-levels the energy and road fuel inside
them. Electricity and gas move to DESNZ volume at QEP prices, and petrol and
diesel to ONS 07.2.2, so the totals no longer contained their own parts: 2.7%
of households had energy above their COICOP 04 total. PE-UK's explicit
energy VAT base (policyengine-uk#2166) can only take energy out of the
generic base if the totals hold it.

A declared recompose_from_remainder step runs after the levelling steps. Each
total keeps its own chain draw, less the chain's draws of the parts the stage
re-levels, and adds the levelled parts back:
- housing_water_and_electricity_consumption: its draw, less the drawn
  electricity and gas, plus the levelled electricity and gas;
- transport_consumption: its draw, less the drawn petrol and diesel, plus the
  levelled petrol and diesel.
The drawn parts are kept right after the support clip, before any step
touches them. A remainder floors at zero where the parts outgrow the total:
2,056 and 1,240 of 63,956 households on the head-r3 spine, about GBP 1.0bn
and 0.3bn. The chain draws each part after, and conditional on, its total, so
the remainder is the chain's own split.

Drawing the remainders as chain targets of their own (the plan's first
version) over-drew the vehicle-purchase tail of transport: 10 to 13% above
the donor in-sample, and GBP 27bn on the spine. The chain and its targets are
unchanged, and output columns are unchanged.

On the head-r3 spine, consumption across the twelve divisions moves -0.7%
(GBP 979.0bn to 972.3bn):
- housing -3.4%, as DESNZ x QEP energy (GBP 47.9bn) replaces the drawn
  electricity and gas (GBP 54.7bn);
- transport +0.2%, as the levelled fuel (GBP 36.9bn) replaces the chain's
  fuel draws (GBP 28.7bn), including those zeroed for households without a
  fuel car.

- The chain's domestic-energy target is the diary's electricity plus gas, so
  the liquid and solid fuels of COICOP 04.5, which have no column of their
  own, stay inside the housing remainder at the diary's level. The receipt
  records their published ONS 04.5.3 and 04.5.4 spend beside it (CY2024,
  vendored rows): GBP 1.55bn.
- The uk_stage_lcfs_consumption_recomposed_totals gate checks each total
  against its declaration. It requires no negative remainder and no
  household below its components. The remainder must equal the draw less the
  drawn parts plus the floored mass, and the total must equal the remainder
  plus the parts. It also recomputes the uncarried ONS spend from the
  vendored rows.
- The two totals leave the committed support bounds (regenerated); the gate
  holds them instead.
- Mirrors: operation schema and kind, spine gate scope, gate-battery digests
  and entry lists, coverage manifest, source-stage pins.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The audit scales each fuel's all-road-user HMRC litres by the all-fuel OBR
cars share (58.3%), giving petrol x1.30 and diesel x0.37. Diesel is mostly
vans and lorries, so those per-fuel ratios mislead; only the total ratio
compares like with like. The receipt now says so in per_fuel_ratio_basis
until a per-fuel cars benchmark exists (DESNZ sub-national road fuel by
vehicle and fuel, requested in PolicyEngine/chronicle#322).

The plan's quarterly 04.5.x and 07.2.2 vendoring is dropped: no code would
read those rows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The lcfs_consumption stage now sets the levels of electricity, gas, petrol and
diesel, the gas connection and the fuel incidence, so calibration should
barely move them. Before this series, the bound rows moved them a lot: gas
fell 19% through the weights and the gas-connected share 84.5% to 78.9%.

uk_diagnostics gains a report-only consumption_drift block, computed from the
calibration's initial and final weights on the frame's own columns:
- totals: electricity, gas, domestic energy, petrol, diesel, and consumption
  across the twelve COICOP divisions;
- shares: gas-connected households, fuel-buying households, and flagged
  fuel-car households without fuel.
Each value carries design, final, change and relative change. The block is
omitted for a frame without the consumption columns. The typed schema
(UKConsumptionDrift, UKDriftValue) is optional and forbids undeclared
fields, as the rest of the UK extension does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PE-UK projects each household spend column from the H5's year, 2024, with
calendar-year growth, and Microcosm's calibration uprates the same values to
the calendar-2025 window, so both read every column as calendar-2024 prices.
Two of the stage's levels were on FY2024-25 instead:
- energy: DESNZ volume and QEP prices for FY2024-25;
- bus fares: the yield per fare-paying boarding from FY2024-25 receipts and
  journeys.
Each column would then have needed its own basis to be projected correctly.
Instead every column now sits on calendar 2024, so none does:
- Energy is priced at QEP's calendar-2024 averages paid and levelled to the
  DESNZ Energy Trends volume over the four quarters of 2024, both vendored
  beside the fiscal rows. The level now takes a calendar or fiscal year.
- Each area's FY2024-25 bus yield is re-priced to calendar 2024 by its fares
  index:
  - London and England outside London: DfT's quarterly local bus fares
    index, calendar-year mean over fiscal-year mean;
  - Scotland: its calendar-year index, taking the fiscal year as three
    quarters of 2024 and one of 2025;
  - Northern Ireland: England's index.
  The factors are 1.000 for London (fares held flat), 0.965 for England
  outside London (the fare cap rose in January 2025), 0.988 for Scotland and
  0.982 for Northern Ireland. The bus pricing gate checks them.
- The diary columns (CPI-uprated to 2024) and road fuel (ONS 07.2.2 for 2024)
  were already on calendar 2024.

The uk_stage_lcfs_consumption_basis gate holds every level-setting step to
calendar 2024: the donor uprating's target year, the energy prices' and the
DESNZ level's periods, the bus prices' year and the road-fuel level's period.
A step whose receipt is absent fails.

On the head-r3 spine all seven lcfs stage-health gates pass. Energy moves
+1.7% (GBP 47.9bn to 48.8bn) and bus fares -2.1%; fuel and the diary columns
don't move. PE-UK can then project every column from the H5's year with no
basis of its own. It still has to project energy: an energy price index from
2024 in place of the uk-data assumption that bills are stored at FY2026-27
prices.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
experiments/1113-consumption-tax-levels-receipts.md is the receipt the
register entries cite. It holds:
- the commit list;
- microcosm#1121's head calibration;
- the measurements that put the levels in the stage (energy volumes and
  sources, road fuel, the VAT factor);
- the masked re-solve of the 2026-09-30 problem;
- the stage-only development run on the head-r3 spine, where all seven lcfs
  stage-health gates pass, with the in-sample check that moved the
  recomposed totals off chain-drawn remainders.

One changelog fragment per change. The charter-H2 graph parity fixture and
the national remeasure follow Group B (PolicyEngine/chronicle#322), whose
feed re-pin moves the fixture again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#323) (#1113)

The artifact is chronicle main at the #323 merge: 352,549 rows (825406f's
344,402 plus 8,147), consumer_fact.v4 with an unchanged schema. It brings ONS
Consumer Trends by COICOP division and class, with the national and domestic
totals, and DESNZ road transport fuel consumption by vehicle type and fuel.

- No existing value moved. Every 825406f row is byte-identical except the seven
  Consumer Trends series already in the feed. #323 restates their 217 rows on
  ONS's domestic concept (territory role; price-basis, seasonal-adjustment,
  unit, sheet and concept dimensions), under new keys with the same values.
- The national and local reference files are byte-identical. The electricity,
  gas and road-fuel targets resolve to the same values through the new keys,
  which is all the national membership records beside the feed label.
- The three compile-parity receipts are unchanged.
- The vendored resources carry the same rows under the new feed identity. The
  Consumer Trends resource names the domestic concept, and the road-fuel
  target's note says why no national bridge applies: ONS splits the tourism
  adjustment by no class.
- The charter-H2 fixture moves at the branch head, after the stage changes that
  follow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The stage receipt gains a report-only division_capture block. For each of the
twelve COICOP divisions it sets the prior-weighted frame total against ONS
Consumer Trends for the uprating's target year (calendar 2024), less the classes
a household diary does not record: 02.3 narcotics, 04.2 owner-occupiers'
imputed rent and 12.6.1 FISIM.

- The divisions are on ONS's domestic concept. Consumer Trends publishes the
  tourism adjustment for the total only, so only the twelve together are also
  compared on the national concept (NAT0). International students' tuition has
  no published split either, and stays in the domestic gap of division 10.
- The Consumer Trends resource vendors the 0CN totals and divisions and the
  three out-of-survey classes for 2023 to 2025 (42 to 93 rows).
- Nothing is gated on it. The engine-free stage test has no uprating step, so
  it checks the block is absent there; a unit test checks the arithmetic
  against the vendored rows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The litres audit's per-fuel benchmark is now HMRC's clearances of that fuel
times the cars share of its UK road use, from DESNZ sub-national road transport
fuel consumption by vehicle type and fuel (PolicyEngine/chronicle#322), for the
uprating's target year. Within one fuel the ktoe cancel, so no conversion factor
enters.

- For 2024 cars burn 97.8% of road petrol and 39.2% of road diesel; the OBR cars
  share of fuel duty receipts applied one share to both, which A8 labelled. The
  OBR-based total stays on the receipt beside the new one.
- desnz_road_fuel_by_vehicle.json vendors the UK rows for 2022 to 2024 (30
  rows), and the lcfs_consumption stage declares it as an artifact.
- Report-only, as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The fixture moves once, at the branch head, for every stage change above.

- The bundle gains the lcfs_consumption steps: redraw_zero_road_fuel,
  level_road_fuel and recompose_from_remainder. It also gains the
  calendar-2024 energy and bus-fare parameters, the ONS and DESNZ artifact
  roles, and the updated source and notes text.
- The synthetic LCFS donor gains c72213, the other motor fuels whose donor
  share level_road_fuel nets from ONS 07.2.2. It is added after the positional
  columns, so no other synthetic value moves.
- In the graph JSON only stage_contract_sha256 moves: for lcfs_consumption and
  for the ten stages that declare a vendored resource the 1ee7dfe re-pin
  restamped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The receipts gain B1 to B3 and the fixture commit, plus the stage-only run at
B3: all seven lcfs gates pass, and the Group A figures hold.

- Division capture: the frame's twelve divisions are 72.8% of ONS's survey
  scope for 2024, against 58.5% for the donor's own diary. Housing reads 1.059,
  GBP 8.0bn of its excess being D1's energy level. Furnishings read 1.251: the
  frame draws 66% more per household than the donor.
- Litres audit: petrol reads 1.011 of its cars benchmark and diesel 0.709. The
  total is 0.894, against 0.951 on the OBR cars share.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@vahid-ahmadi

Copy link
Copy Markdown
Contributor

Automated review pass (Claude Code, high effort) — round 1 at 2e32d7bd

Verdict: nothing blocks this. The stage steps are sound, declared and gated, and the four rows it moves out of the fit are justified and signed. Two should-fixes on the register wording and the bound row, and one to note for the engine side before the national remeasure.

What checks out

  • level_road_fuel is one factor on petrol and diesel at prior weights, so the drawn mix and each household's relative spend are kept (lcfs_consumption.py:1151-1182). The other-fuels share comes from the donor's own c72213, and the step refuses a fiscal-year level or a share outside [0, 1).
  • redraw_zero_road_fuel is seeded, identity-keyed and redraws only flagged households that drew zero, so households without a fuel car stay at zero.
  • recompose_from_remainder refuses a declaration that differs from the parents and components it recomposes, and floors the remainder at zero.
  • Calendar 2024 is applied everywhere: energy sums the four Energy Trends quarters of 2024 (energy_pricing.py, period_quarters); road fuel levels to ONS 07.2.2 for 2024; bus yields are re-priced to 2024. The consumption_basis gate holds every level step to that year.
  • Four new release_blocking gates (road_fuel_incidence, road_fuel_level, recomposed_totals, consumption_basis) are in gates.json, with digests mirrored.
  • The four exclusions are signed, carry a tracking issue, an adjudication with receipts and an expiry (2027-01-08), and stay measured through tools/diagnose_uk_consumption_taxes.py.
  • The Chronicle pin 1ee7dfe is the merge commit of chronicle#323 on main. The selectors resolve 07.2.2 as text, and the 217 restated rows keep their values under the new keys.
  • Tests: the 17 touched test files pass locally (778 passed, 6 skipped). Against the base 7f235941, the new stage-health tests fail and the new diagnostics test doesn't import, so they cover the change.

Should

  1. The electricity and gas exclusion reasons describe the old basis (calibration_measure_exclusions.json:590, :599). They say the stage sets spend from "Energy Trends FY2024-25 domestic volume at QEP FY2024-25 average prices", but the stage now uses calendar 2024 (the 2026-10-09 ruling), and the quoted ratios (0.90, 0.81) were measured on the fiscal-year basis. Restate both reasons on calendar 2024 and re-quote the ratios, so the register matches what the stage does.
  2. The bound row compares petrol plus diesel with all of 07.2.2 (uk_population_targets.json, ons.household_road_fuel_expenditure). The stage levels to 07.2.2 less the donor's other-fuels share (0.37%), but the target is the full class, so calibration pulls about 0.4% toward fuel buyers to close a gap that is only scope. Either scale the target by the same share the stage uses, or record it as a known 0.4% offset in the binding notes and the receipts.
  3. Non-residents' spending inside the bound row. The notes say 07.2.2 is on the domestic concept and no bridge is applied. That's fine as a choice, but the frame is residents only, so the row is an upper bound. Add one line with the size of the effect (or that ONS gives no split), so a later reader knows which way it leans.

Nits

  1. The model year: policyengine-uk reads the H5's 2024 as fiscal 2024-25, while the frame is priced at calendar 2024, about a quarter apart. Worth one sentence in the stage notes, since every levelled column inherits it.
  2. If the LCFS diary has a line for vehicle electricity inside 07.2.2, it should sit with c72213 in the other-fuels share; otherwise a sentence saying it doesn't exist.

Knock-on for policyengine-uk

  • #2186 (DESNZ litre divisors): still holds. Its divisors are DESNZ calendar-year averages, the same basis this stage prices road fuel on, and its spending proxy is derived to keep litres on the volume path whatever the level. What moves is the level: petrol plus diesel for 2024 goes from £34.81bn on the certified release to £36.89bn here (+6.0%), so litres and fuel duty rise by the same 6%, about 25.6bn litres in 2024 and about £13.3bn of duty in 2025. That matches the register's −8.1% against the OBR cars line. The new ONS row will read about −1% at design weights, because the engine's 2024-to-2025 spending growth (−6.2%) is a little below ONS's (−5.2%).
  • #2187 (VAT coverage split): the 0.67 coverage was derived on the certified release. Here total consumption is 0.8% lower and energy is levelled separately, which by itself moves coverage to about 0.665, still 0.67 to two places, but it should be re-derived on the R1 remeasure. Once #2187 is in the locked version, the obr.vat exclusion's own retire condition is met ("rebind when a locked policyengine-uk splits the factor"), and on the certified release the split factor put VAT within 0.4% of OBR.
  • #2189 (electricity and gas VAT base): energy here is priced at QEP calendar-2024 average prices including VAT, so the 5/105 extraction is still right. #2189's description says the bills are at Ofgem cap rates; that should say QEP average prices paid. With electricity at £29.5bn and gas at £19.2bn, domestic energy VAT is about £2.32bn, against about £2.02bn on the certified release and HMRC's implied £2.33bn, and the 2026-27 zero-rate cost rises from about £0.62bn to roughly £0.70bn.

@juaristi22
juaristi22 marked this pull request as ready for review October 10, 2026 22:42
Vahid's round-1 pass on #1167 found nothing blocking. This takes its three
should-fixes and two nits. María ruled on the first two on 2026-10-10.

- The electricity and gas exclusions named the FY2024-25 basis the stage left
  on 2026-10-09. Their reasons now name calendar 2024 and quote that year
  beside 2025: ONS is 0.88 of the stage's electricity level and 0.76 of its
  gas level. The 2025 ratios were already calendar-year. María approved the
  restatement; each adjudication records it and keeps the decision's date.
- The bound road-fuel row's notes record the 0.4% scope offset: the target is
  all of 07.2.2, and the stage levels to it less the donor's other-fuels
  share. María ruled that it stays recorded and is not scaled into the target.
- The same notes size the domestic concept as far as ONS allows: non-residents'
  spending in the UK and residents' spending abroad, which Consumer Trends
  publishes for total spending only and by no class.
- The stage notes say policyengine-uk applies the fiscal 2024-25 rules to model
  year 2024, a quarter after the calendar-2024 prices, and that the diary's
  07.2.2 has no line for vehicle electricity.
- The stage notes feed source_stages.json, the coverage manifest and the H2
  fixture bundle, which are regenerated. No stage contract hash or gate digest
  moves.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@juaristi22

Copy link
Copy Markdown
Collaborator Author

Round 1 addressed at 3523109, one commit on top of 2e32d7bd, so the delta reviews on its own. The branch is not rebased: main has not touched the spec, gates, feed or generated files since the base. The PR now closes #1113.

Should

  1. The energy exclusions describe calendar 2024. Both reasons now name the DESNZ volume over the four quarters of 2024 at QEP calendar-2024 prices, and quote that year beside 2025: ONS 04.5.1 is £26.10bn against the stage's £29.52bn (0.88), and ONS 04.5.2 is £14.64bn against £19.23bn (0.76). One correction: the 0.90 and 0.81 ratios were not on the fiscal-year basis. They compare ONS for calendar 2025 with Energy Trends 2025 volume at QEP 2025 prices, so they stay beside the 2024 ones. María approved the restatement; each adjudication records it and keeps the 2026-10-08 decision date.
  2. The 0.4% scope gap is recorded, not scaled (María's ruling of 2026-10-10). The binding notes now say the row reads about 0.4% low from scope alone: the stage levels petrol plus diesel to 07.2.2 less the donor's other-fuels share (0.37%), and the target is the full class. Scaling the target would put a diary-derived constant into the contract. The receipts carry the offset too.
  3. The domestic concept is sized as far as ONS allows. The notes give both flows for 2024: £64.7bn of non-residents' spending in the UK (3.8% of the domestic total) and £78.9bn of residents' spending abroad. Consumer Trends publishes them for total spending only (sheet TOURCN) and splits them by no class, so their size for road fuel is not known. One nuance: the row is not strictly an upper bound. It includes non-residents' purchases here and leaves out residents' purchases abroad, and across all spending the second flow is the larger.

Nits

  1. Model year. The stage notes now say policyengine-uk applies the fiscal 2024-25 rules to model year 2024 (it samples each parameter at 30 April), so the levelled columns sit about a quarter before the midpoint of that policy year. They also say the calibration binds calendar-year facts as published and fiscal-year facts at the calendar window (María's rule of 2026-09-22).
  2. Vehicle electricity. The 2023-24 derived household file has three 07.2.2 lines (c72211, c72212, c72213) and none for vehicle electricity, so nothing joins the other-fuels share. The stage notes say so.

policyengine-uk knock-ons. Noted for the national remeasure, which has not run yet. It will re-read the ONS row at design weights and the VAT coverage. The obr.vat exclusion's retire condition gets checked once a version with PolicyEngine/policyengine-uk#2187 is locked. The wording point on PolicyEngine/policyengine-uk#2189 (QEP average prices paid, not Ofgem cap rates) is for that PR.

Verification at 3523109: 870 tests pass across the register, contract, source-stage, LCFS stage, stage-health, coverage, gate, country-spec and microcosm-data contract suites, with test_h2_uk_spine_parity and test_uk_graph. load_country_spec("uk") loads, the coverage manifest --check passes and the mirrored gate digests are current. The stage notes feed source_stages.json, the coverage manifest and the H2 fixture bundle, which are regenerated; no stage contract hash or gate digest moves.

@juaristi22

Copy link
Copy Markdown
Collaborator Author

Independent adversarial review (Claude Code) at 352310965

Static review only: I read the code, the registers and experiments/1113-consumption-tax-levels-receipts.md against the base 7f235941c, and ran nothing. I've tried not to repeat round 1. Where a round-1 point was only partly fixed, I say so.

Verdict. The three stage steps do what the body says:

  • redraw_zero_road_fuel is identity-keyed and touches no global RNG, so later stages don't move.
  • level_road_fuel is one factor at prior weights, with a weighted c72213 share.
  • recompose_from_remainder floors the remainder at zero.
  • The re-pin changes headers only, and the restated rows keep their values.

But the headline "one calendar-2024 basis" isn't true for the diary columns, and the gate meant to hold it only checks a label. The PR also closes the issue its new exclusions point at. Several gate and exclusion texts still describe rows that no longer bind.

Should fix

  1. The diary columns are not at calendar-2024 prices, and consumption_basis can't tell. Round 1's "calendar 2024 is applied everywhere" is not right.

    • The uprating step: spec/sources.yaml:2846-2849 uprates the LCFS 2023-24 diary (April to March) by one year of engine CPI, from_period: 2023 → to_period: 2024 at January-first instants. A one-year ratio carries the diary's midpoint (about October 2023) to about October 2024. That's FY2024-25 prices, a quarter-year past calendar 2024.
    • The code says so itself:
      • donor_uprating.py:3: "one year behind the FRS 2024-25 spine".
      • The stage notes (sources.yaml:3286) say the diary "is uprated to the FRS 2024-25 base year", in the same paragraph as "every household spend column is at calendar-2024 prices … the diary columns by the CPI uprating to 2024".
    • The gate checks a label: stage_health.py:2681 sets found["donor_uprating"] = ("calendar_year", uprating.get("to_period")). The period type is made up, and only to_period == 2024 is checked.
    • Affected: the ten non-levelled divisions, the housing and transport remainders, and Wales's bus fares. That's most of the frame's consumption, so a quarter-year of CPI is likely several £bn, more than the energy fiscal-to-calendar move this PR made.
    • The PR already has the right bridge: it bridges Scotland's bus index with 0.75 · FY + 0.25 · FY+1 (bus_fare_pricing.py:248).
    • Receipts: experiments/…:143 says "the diary columns … were already on calendar 2024 and don't move", which is wrong for the diary.
    • Fix: either bridge the diary the same way, or restate the claim and the gate as "FY2024-25 for the diary".
  2. "Closes UK domestic electricity and gas: record why ONS 04.5.1/04.5.2 is preferred over DESNZ × QEP, reconcile the two, and declare the price basis PE-UK should project from #1113" orphans three new exclusions.

    • ons.household_electricity_expenditure, ons.household_gas_expenditure and obr.fuel_duties_cars all have "tracking": "microcosm#1113". The two energy rows retire "if the microcosm#1113 reconciliation finds a concept difference to bridge".
    • This PR rules the other way (D1) and does no reconciliation. After merge, three signed narrowings would point at a closed issue whose work was never done, and the expiry error would send people there.
    • Fix: open follow-up issues, one for the energy-concept reconciliation and one for business use of cars, and repoint tracking. Or drop "Closes".
  3. Round 1's stale-wording fix reached the exclusion register but not the gate manifest or the other registers.

    • gates.json:333 (energy_rake notes): still says the level is "over the four FY2024-25 quarters".
    • gates.json:888 (uk_aggregate_admin): says "the calibrated frame is held to the bound ONS 04.5.1 electricity and 04.5.2 gas rows". Both are now measure-excluded. The notes are hashed into gates_manifest_sha256, so every signed battery report attests this.
    • target_reference_signed_exclusions.json:55: obr.fuel_duties is signed out because "obr.fuel_duties_cars binds the cars receipts instead". It no longer binds, so no fuel-duty row binds at all.
    • target_reference_signed_exclusions.json:62-63: COICOP 04.5 is "superseded_by_component_targets", the per-fuel rows that "bind per fuel". They no longer bind.
    • Also stale:
      • uk_population_targets.json:708 ("F1 binds ONS … 04.5.1/04.5.2");
      • energy_pricing.py:5 ("base-year (FY2024-25) frame");
      • lcfs_consumption.py:1800 ("FY2024-25 regional average prices").
    • The substance behind the wording: after this PR, no release-blocking check holds calibrated domestic energy or the VAT base. The report-only consumption_drift is all that sees them, and the receipts show unbound VAT moving (+35%). That is a decision to record, not just text to fix.
  4. Two of the new gates can't fail on a real receipt, and they replace the frame bounds that could.

    • recomposed_totals: recompose_from_remainder sets rest = max(drawn_total − drawn_parts, 0) and total = rest + parts (lcfs_consumption.py:1313-1315). So rows_below_components is always 0, minimum_remainder is always ≥ 0, and the weighted checks are identities of the same receipt.
    • The removed bounds: the PR deletes housing_water_and_electricity_consumption [-20000, 300000] and transport_consumption [0, 200000] from lcfs_consumption_support_bounds.json. The terminal uk_support battery applied those to the emitted frame. The gate note says this check "holds them instead", but it doesn't.
    • road_fuel_level: checks the step's own frame_after, recomputed from the same vendored row. A unit error in that row, or a later stage moving these columns, passes.
    • Fix: keep frame-level bounds on the two totals. Dropping petrol and diesel follows the existing raked-columns convention, so that part is fine.
  5. The redraw now guarantees fuel spend to battery-electric households.

    • The flag is num_vehicles > 0 & Bernoulli(1 − ZEV share) (lcfs_consumption.py:1973-1975), blind to fuel type.
    • Before this PR, a flagged EV owner could draw zero; redraw_zero_road_fuel now forces them positive.
    • Small at today's ZEV share, but it grows each year. It's worth conditioning the flag on fuel type, or at least recording it as a known residual.

Minor

  1. Bus fares are levelled but transport isn't recomposed around them.

    • bus_fare_spending is in UK_LCFS_RAKED_COLUMNS and is re-priced here (−2.05% outside London).
    • UK_LCFS_RECOMPOSED_PARENTS["transport_consumption"] nets and adds back only petrol and diesel (:287-296).
    • So transport can sit below bus + petrol + diesel, and "no household's … transport total is below its levelled parts" holds only if bus doesn't count. The end-to-end test checks only transport >= road_fuel.
  2. "Consumption moves from £979.0bn to £971.4bn (−0.8%)" isn't this PR's effect.

    • The only divisions the steps rewrite are housing (180.1 → 173.5) and transport (173.0 → 181.5), which net +£1.9bn.
    • The other −£9.5bn is re-run drift; the receipts concede "differ by the re-run as well".
    • Round 1's VAT-coverage knock-on (0.67 → 0.665) rests on that −0.8%.
  3. Exclusion reasons mix bases.

  4. Question: is the bound row's design-weight gap only the 0.4% scope gap?

    • ons.household_road_fuel_expenditure binds the published calendar-2025 value (35.099bn), not uprated. The engine projects the levelled 2024 frame forward with its own growth.
    • ONS's own 2024-to-2025 move is −5.2% (37.025 → 35.099bn). Any difference between that and the engine's fuel growth adds to the 0.37% scope gap, and the calibration closes the combined gap by moving weight toward fuel buyers.
    • The binding notes size only the scope part. It's worth reading the row at design weights in R1 and recording the total.
  5. "The three compile-parity receipts are unchanged" holds for the re-pin commit only. At PR level, the incumbent-2025 and production-2023 receipts each gain one ledger_only row (1,257 → 1,258 and 924 → 925), from 149fb61b5, and the changelog repeats the claim unscoped.

  6. Reproducibility: run_stage_dev.sh, the Group B config, the M1–M4 measurements and the masked re-solve aren't committed. The outputs live under data/ukds/acceptance/… and the plan outside the repo. docs/evidence/uk-a16-v20/scripts/ is the precedent for committing them.

  7. Questions and nits.

    • What does LCFS p537 cover? If it's all of 04.5, then where derive_energy_from_lcfs splits the whole of p537 into electricity and gas (:1948), oil and solid fuel are subtracted with the drawn energy. Then the housing remainder doesn't hold them, despite the comment at :284.
    • The diagnostics can abort the build: _ons_annual_value raises unless exactly one row matches (with no price-basis or seasonal-adjustment filter), and desnz_cars_share_by_fuel raises for a year outside 2022–2024.
    • The 07.2.2 target selector doesn't pin the price_basis, consumption_concept or seasonal_adjustment dimensions that chronicle#323 added, and has no row-count guard, unlike the vendored selections.
    • Northern Ireland's bus factor on England's index picks up England's January 2025 fare-cap rise (191.1 → 201.8).
    • Receipt arithmetic: "33.2%" is 16,362/48,529 = 33.7% (label which is weighted). "66% more per household" is 1.625 on the receipts' own numbers. And 84.5 → 78.9% appears for both the UK: port the remaining uk-data fixes tracked in #1095, engine items included #1121 head and the 2026-09-30 build; is that a copy error?
    • docs/uk-chronicle-feed-repin.md:124 has no blank line before the new paragraph.

Interaction with #1198 and #1145

Checked and fine:

  • The re-pin: every +4/−4 vendored file changes only the four feed-identity fields, and chronicle_feed.json shows 352,549 rows.
  • Ratios: 0.88 and 0.76 (2024), 0.90 and 0.81 (2025), and £36.887bn = 37.025 × (1 − 0.00372).
  • Mirrors: gate digests recompute to the mirrored values, and only stage_contract_sha256 moves in uk_spine.json.
  • Fixture: lcfs_household.csv is synthetic.
  • obr.vat: nothing else depends on it being bound, and its retire condition is checkable against policyengine-uk#2187.
  • Round 1's Should-2 and Should-3 and the two nits are fixed.

juaristi22 and others added 3 commits October 11, 2026 20:55
María's independent review of #1167 at 3523109 found the diary columns a
quarter past the calendar-2024 basis the PR claims, a gate that could not see
it, and gate and register texts describing rows that no longer bind.

Stage behaviour:
- The diary spend columns (twelve divisions and bus fares) are carried on the
  engine's CPI index from the April-to-March 2023-24 diary year to calendar
  2024: uprate_donor_columns gains a fiscal_year_bridge and a per-column
  from_window. The step is 1.0186, not the full year's 1.025. Income
  predictors keep the full-year step to the FRS 2024-25 incomes.
- consumption_basis reads each diary column's declared window and the bridge
  weights, not an assumed period type.
- transport_consumption is recomposed around the priced bus fares as well as
  the levelled petrol and diesel, so it holds every levelled part.
- The housing and transport totals keep their committed support bounds, so the
  terminal support gate holds the emitted totals. The recomposed_totals check
  guards the declaration and the receipt; its sums are identities.
- The division capture and the litres audit record why they have no figures
  when a published row is missing for the year; they no longer raise. The
  litres audit falls back to the OBR cars share. A stale vendored resource
  still fails.
- The Consumer Trends target selectors pin the price basis, concept and
  seasonal adjustment chronicle#323 added.

Texts restated to what the stage and the calibration now do:
- gates.json: the energy level is calendar 2024; no bound row or
  release-blocking check holds calibrated domestic energy, the VAT base or
  fuel duty since D1 to D3, and the notes say so; what road_fuel_level and
  recomposed_totals can and cannot catch.
- The signed exclusions for obr.fuel_duties and COICOP 04.5 no longer say the
  rows that replaced them bind.
- The four measure-exclusion reasons give design-to-final moves and the
  masked-arm comparison separately, and say the four rows were masked together
  on the 2026-09-30 problem.
- The bound road-fuel row's notes give the engine-growth part of its
  design-weight gap: about +2.3% on policyengine-uk 2.122.2, scope included.
- The stage notes record the fuel flag's known residual, the Northern Ireland
  bus index proxy, and that the housing remainder holds only part of the
  diary's other fuels.
- The three exclusions that tracked #1113, which the PR closes, now track the
  follow-up issue #1207, and the two energy rows retire on its reconciliation.

Regenerated: source_stages.json, the coverage manifest, the gate digests, the
LCFS support bounds, the national references and membership, and the H2
fixture (only lcfs_consumption's contract hash moves).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…1113)

The review asked that the scripts behind the receipts live in the repository,
as docs/evidence/uk-a16-v20 does.

- scripts: the M2/M3 desk measurements, the M4 masked re-solve, and the
  stage-only run with its per-tree runner.
- outputs: aggregates only. The household dumps and solved weight vectors stay
  out, and sanitize_outputs.py drops the stage's donor_floor receipt, which
  records exact donor minima.
- The stage runs include main's stage on the same spine. A re-run redraws most
  households, so that run, not the spine's stored columns, is what a change is
  compared with.
- M1's volume figures have no saved script; the README says so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Corrects what the adversarial review found wrong in the receipts: the -0.8%
  on consumption was re-run drift against the stored spine, the diary columns
  were not on calendar 2024, the housing remainder holds only part of the
  diary's other fuels, and the masked re-solve masked the four rows together
  on the 2026-09-30 problem.
- Adds the round's measurements on the head-r3 spine against main's stage:
  consumption -0.3% (GBP 969.6bn to 966.8bn), the ten diary divisions -0.7%,
  housing GBP 172.1bn and transport GBP 182.4bn. All seven lcfs gates pass.
- Adds the bound road-fuel row's desk reading at design weights: about +2.3%
  on policyengine-uk 2.122.2.
- Records the decisions the review asked to see written down, and the
  follow-up issue #1207 that takes the open items.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@juaristi22

juaristi22 commented Oct 11, 2026 •

Copy link
Copy Markdown
Collaborator Author

The independent adversarial review above is addressed at 3f5743165, three commits on top of 352310965, so the delta reviews on its own. The branch is not rebased. Item numbers are the review's.

Should fix

  1. The diary columns are now at calendar-2024 prices, and the gate checks it. The review is right: a full year's CPI step (1.025) carried the April-to-March 2023-24 diary to about October 2024. The twelve divisions and bus fares now declare the diary-year window (from_window: fiscal_year, with the operation's fiscal_year_bridge). The engine's CPI index is calendar-year, so the diary year sits at three quarters of 2023 and one of 2024, and the step to calendar 2024 is 1.0186. consumption_basis reads each column's declared window and the bridge weights; a column on a full step fails it. Income predictors keep the full year's step, to the FRS 2024-25 incomes. On the head-r3 spine the ten diary divisions move −0.7% against main's stage.
  2. "Closes UK domestic electricity and gas: record why ONS 04.5.1/04.5.2 is preferred over DESNZ × QEP, reconcile the two, and declare the price basis PE-UK should project from #1113" no longer orphans the exclusions. The open items are one follow-up issue, UK consumption-tax levels: open items after #1167 #1207, under the two deferral registers: the calibration-target items under WS-C deferred adjudications and the reconciliation layer #736 and the spine items under WS-E spine deferrals: known bugs and signed inconsistencies awaiting their increment #796. The three exclusions now track UK consumption-tax levels: open items after #1167 #1207. The two energy rows retire on its reconciliation of ONS spending with DESNZ volume at QEP prices, and the cars row on its sizing of business use of cars. The PR still closes UK domestic electricity and gas: record why ONS 04.5.1/04.5.2 is preferred over DESNZ × QEP, reconcile the two, and declare the price basis PE-UK should project from #1113.
  3. The stale texts are restated, and the decision behind them is recorded.
    • gates.json: the energy_rake level is calendar 2024. The energy_rake and uk_aggregate_admin notes now say that since D1–D3 no bound row and no release-blocking check holds calibrated domestic energy, the VAT base or fuel duty; the stage gates hold the levels at prior weights, the bound ONS row holds road-fuel spend, and consumption_drift reports the rest.
    • The signed exclusions for obr.fuel_duties and COICOP 04.5 no longer say the rows that replaced them bind.
    • The contract's unmapped declarations, energy_pricing.py, the stage docstring and the stage notes are corrected too.
    • A release-blocking limit on the drift is a possible follow-up once R1 has measured it. It is not added here without that measurement.
  4. The two totals keep frame-level bounds. Housing and transport are back in lcfs_consumption_support_bounds.json (−20,000 to 300,000 and 0 to 200,000), so the terminal uk_support gate holds the emitted totals; on the head-r3 spine both totals are inside them. The recomposed_totals and road_fuel_level notes now say what each can and cannot catch.
  5. The fuel flag does not hold electric households that the redraw then fuels. The flag is a draw at the car stock's non-zero-emission share, keyed on the household identity. It is the stage's own designation of who buys fuel: an unflagged vehicle owner is the zero-emission household and has no fuel, before and after this PR. The third of flagged households at zero before the redraw was the two-week diary, not electric cars. What stands from the point is that the designation is random with respect to income and all-or-nothing per household. The stage notes now record that as a known residual.

Minor

  1. Transport is recomposed around the priced bus fares as well as the levelled fuel. No household's transport total is below its petrol, diesel and bus fares on the head-r3 spine, and the end-to-end test checks all three.
  2. The −0.8% was re-run drift. Main's stage re-run on the same spine reads £969.6bn against the spine's stored £979.0bn. Against that base the PR read +0.2% (£971.4bn) before this round, and reads −0.3% (£966.8bn) with the diary bridge. The receipts and the PR body now compare with the base run. Round 1's VAT-coverage note rested on the −0.8%, so it should be re-derived on R1.
  3. The exclusion reasons separate their bases. Each gives the design-to-final move and the masked-arm comparison apart, says the four rows were masked together, and places the re-solve on the 2026-09-30 problem (1,090 rows).
  4. The bound row's design-weight gap is more than the scope offset. ONS's own move from 2024 to 2025 is −5.2%; policyengine-uk 2.122.2 carries petrol and diesel −2.7%. With the scope offset the row reads about +2.3% on that version, so the calibration moves weight away from fuel buyers. The binding notes and receipts give that desk figure, and R1 records the measured one.
  5. The parity claim is scoped to the re-pin. The new road-fuel row adds one ledger-only row to each national receipt; the changelog, the re-pin doc and the receipts say so.
  6. The scripts are committed under docs/evidence/uk-consumption-taxes-1113/, with their aggregate outputs. Household dumps, weight vectors and the stage's exact donor minima stay out. M1's volume figures have no saved script; the README says so.
  7. Questions and nits.
    • p537 is all fuel, light and power: £60.1bn on the donor, against £54.9bn for the electricity and gas derived from it. For 2,978 of the 4,202 diaries the derivation splits all of p537, so their other fuels leave with the drawn energy. For the 1,224 that report a combined payment, the rest (£5.2bn) stays in the housing remainder. The comment, notes and receipts claimed the remainder held all the other fuels; they are corrected.
    • Diagnostics no longer abort: the division capture records why it has no figures, and the litres audit falls back to the OBR cars share. Their row lookups pin the price basis and seasonal adjustment. A vendored resource that lags the pin still fails.
    • The target selectors pin price_basis, consumption_concept and seasonal_adjustment.
    • Northern Ireland's bus factor is left on England's index; the stage notes name it a declared proxy that carries the English fare-cap rise.
    • Arithmetic: 33.7% of flagged rows, 33.2% of their weight; 63% more furnishings per household at B3 (61% now). The 84.5% to 78.9% is not a copy error: UK: port the remaining uk-data fixes tracked in #1095, engine items included #1121's head reads 84.54% and 78.87% from its files, the 2026-09-30 build 84.52% and 78.93%.
    • The re-pin doc has its blank line.

Interaction with #1198 and #1145. Agreed, and the PR body now carries the merge-order steps.

One new observation. The housing total now contains the energy columns, and a small number of households carry extreme electricity and gas values. Values of that size predate this PR, and nothing bounds the energy columns themselves. #1207 carries it, with the furnishings over-draw and the other open items.

Verification at 3f5743165: 1,487 tests pass across the LCFS stage, stage-health, donor-uprating, energy and bus pricing, source-stage, coverage, gate, country-spec, feed, register, contract and microcosm-data contract suites, with test_h2_uk_spine_parity and test_uk_graph. The licensed round-trip of the support bounds passes too. load_country_spec("uk") loads, the coverage manifest --check passes and the mirrored gate digests are current. The H2 fixture is regenerated; only lcfs_consumption's contract hash moves. All seven lcfs gates pass on the head-r3 spine.

@juaristi22

Copy link
Copy Markdown
Collaborator Author

Re-review (Claude Code) at 3f5743165

Static review of 352310965..3f5743165 against the previous review and the round-2 reply. I ran nothing. The CI run for this head was still queued when I looked.

Verdict. Every item from the previous review is fixed or answered convincingly, and the diary bridge is right: 1.55763 / (0.75 · 1.51964 + 0.25 · 1.55763) = 1.018633 on the engine's calendar-year CPI. Two disclosure-control items went to the author separately. The rest below is minor.

Previous items

# Status Note
1 Diary basis Fixed The 12 divisions and bus declare from_window: fiscal_year with the 0.75/0.25 bridge. consumption_basis fails a missing window, a missing column or wrong weights (stage_health.py:2702-2725). Income predictors keep the full step, which is consistent.
2 #1207 Fixed #1207 is open, the three exclusions track it, and the retire conditions are reworded.
3 Stale text Mostly fixed gates.json 333/888, the signed exclusions, energy_pricing.py and the stage notes are fixed. Leftovers are in item 7 below.
4 Support bounds Fixed Housing and transport carry main's bounds again, and uk_support applies them.
5 EV flag Answered Accepted: the recipient has no fuel type, so the flag is the only zero-emission identity. One residual isn't recorded: the rate is the car-stock ZEV share applied per household. Battery-only households are a smaller share of vehicle-owning households, because many EV households also run a combustion car, so the flag likely over-designates no-fuel households.
6 Bus in transport Fixed UK_LCFS_RECOMPOSED_PARENTS nets and restores bus, and the end-to-end test checks fuel + bus.
7 Consumption figure Fixed Base 969.58, then 971.44 (+0.19%) before this round and 966.76 (−0.29%) now. The ten divisions move −0.68%, and the bridge alone is −0.62%.
8 Exclusion bases Fixed The figures match m4_report.json.
9 Bound-row gap Fixed pe-uk 2.122.2 carries both fuels at −2.691% for 2025: 36.887 × 0.97309 = 35.894 against 35.099, which is +2.26%. So the calibration moves weight away from fuel buyers.
10 Parity scope Fixed
11 Scripts Fixed receipts.json hashes match. M1 has no script, as the README says.
12 Nits Mostly fixed The p537 split matches the derivation masks, and the selectors pin all three dimensions. Minor item 1 below has a new fail-open in the diagnostics.

New

  • Two disclosure-control items have gone to the author separately.
  • Hardening: the sanitiser docs/evidence/uk-consumption-taxes-1113/scripts/sanitize_outputs.py:28 drops only donor_floor. stage_dev_run.py:145 also copies support_clip entries, and those carry donor_min and donor_max. The block is empty today only because the named columns don't exist. Add both keys to DROPPED, or drop the block.

Minor

  1. The diagnostics now fail open on bad data, not only on a missing year. DiagnosticUnavailableError also covers ambiguous or duplicate rows (lcfs_consumption.py:616, :753), so a data-integrity error is recorded as "unavailable". And a resource vendored at the pin but missing the year quietly drops the litres audit to the uniform OBR share; only the receipt notes it.
  2. The recomposed totals carry an uprating residue.
    • Parents: take the CPI bridge (×1.0186).
    • The parts netted from them: stay at their own factors. Drawn energy isn't uprated. Petrol and diesel take the litre-based spending proxies (diesel ×0.898).
    • Residue: the remainders keep about 0.019 × drawn energy (roughly £1bn in housing) and the fuel factor gaps (roughly £1.7bn in transport) on the committed totals. So the totals aren't strictly on one basis.
    • Fix: rescale each drawn part by parent factor ÷ part factor before netting, or record the residue.
  3. UK consumption-tax levels: open items after #1167 #1207's R1 acceptance contradicts its own desk figure. It expects levelled road fuel to move under 2% from design to final, but the bound row reads +2.3% at design, so fitting it moves road fuel about 2.2%.
  4. Nits.
    • consumption_basis checks the declared window and weights, but not that the receipt's factor equals after / (0.75 · before + 0.25 · following), though both are recorded.
    • Stale text remains:
      • the uncarried_spend docstring (lcfs_consumption.py:1305-1307, "stay inside the housing remainder");
      • binding notes saying 04.5 is "bound per fuel" and that obr.fuel_duties_cars is "Bound at FY2024-25";
      • donor_uprating.py:3.
    • Per-division movement: the bridge is a uniform −0.62%, but divisions move from −1.4% to +2.2% from re-run noise. That matters when reading division_capture.
    • Local paths: the scripts and output spine fields carry local absolute paths.

No tests were weakened and no counts were edited to match. The count changes follow the code.

@juaristi22
juaristi22 merged commit d648c97 into main Oct 11, 2026
10 checks passed
juaristi22 added a commit that referenced this pull request Oct 12, 2026
Taken in #1145 at María's direction, #1167 having merged.

- Hardening: the evidence sanitiser drops donor_min and donor_max beside
  donor_floor and keeps only the spine file's name; the committed outputs
  and scripts carry no machine-local paths (the scripts read the inputs
  root from PE).
- Minor 1: an ambiguous or duplicate vendored row is a data fault and
  fails closed as a plain ValueError; only a missing year is recorded as a
  report-only diagnostic being unavailable.
- Minor 2: the recomposition receipt records each parent's uprating
  residue, the parent's factor against each netted part's, weighted; not
  corrected.
- Nits: the consumption-basis gate holds each bridged column's recorded
  factor to after / base; the uncarried_spend and donor_uprating docstrings
  and the obr.fuel_duties_cars and 04.5 binding notes say what the rows
  are now (measured, not bound); the evidence README says the per-division
  movement between runs is redraw noise around the uniform bridge.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
juaristi22 added a commit that referenced this pull request Oct 12, 2026
Main re-pinned the Chronicle feed to 1ee7dfe (352,549 rows) and merged
#1167, #1087 and #1099 among 87 commits. On the merged tree: the PRIS
resource is re-vendored from the new pin (44 rows unchanged), the target
references, membership and both national compile-parity receipts are
regenerated against it (1,245 active references: main's 1,232 plus the 13
SPI property amounts), the release input coverage manifest and the H2
fixture are re-derived, the gate-battery and certification part digests
move with the merged gates.json, and the count pins follow. The commits
below this one keep the surfaces they were measured with.

Co-Authored-By: Claude Fable 5.1 <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

2 participants