Skip to content

Add HMRC benefits in kind statistics, July 2026, and keep one OBR EFO number per line and year - #399

Merged
juaristi22 merged 4 commits into
mainfrom
hmrc-benefits-in-kind-316
Oct 31, 2026
Merged

juaristi22 merged 4 commits into
mainfrom
hmrc-benefits-in-kind-316

Conversation

@juaristi22

@juaristi22 juaristi22 commented Oct 30, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Two changes, in two commits; a third records the workbook's R2 location, and a fourth answers the first review:

  1. hmrc-benefits-in-kind-july-2026 (544 facts), for Company car tax +1 point (2030-31): needs a company car benefit in policyengine-uk and company cars in Microcosm uk-autumn-budget-dashboard-2026#316. Budget 2026 raises company car tax by one percentage point in every band from 2030-31 (Table 6.1). policyengine-uk 2.165.0 (Model the company car and car fuel benefits, with Class 1A NICs policyengine-uk#2750) models the company car and car fuel benefits and their Class 1A NICs. Microcosm draws company car CO2 and fuel at HMRC's Table 2A and 2C margins and measures its calibrated file against these tables, binding none of them. Its publisher facts have to come from Chronicle, so this package lands before Microcosm re-pins its feed.
  2. OBR EFO March 2026: drop the years the October 2026 vintage also publishes (PolicyEngine/microcosm#1095) #397: the March 2026 EFO packages drop the 427 facts whose line and year the October 2026 packages also publish, under any line id. They keep the 74 that October lacks (73, and chronicle#396's VED outturn), and the bundle's 363 semantic duplicate keys between the two vintages go.

Closes #397.

1. HMRC benefits in kind statistics, July 2026

Source: Benefits in kind statistics: July 2026, published 15 July 2026 under the Open Government Licence v3.0. The Tables 1 to 4 workbook is pinned by SHA-256 in db/data/hmrc/benefits_in_kind_july_2026/manifest.yaml. packages/hmrc/benefits_in_kind_july_2026/build_package.py writes source_package.yaml from the workbook, and --check compares instead.

What it carries (121 record sets)

  • Table 1, 2011-12 to 2024-25: car, fuel, and car-and-fuel benefit recipients, taxable value, and Income Tax and Class 1A NIC liabilities. It also carries HMRC's average taxable value per recipient for car and fuel benefit.
  • Table 2A, 2011-12 to 2024-25: car benefit recipients by CO2 emission band. Each year carries the bands it prints. The bands changed in 2020-21: to 2019-20 the table prints 51-75, 76-94, 165-174 and 175+ g/km; from 2020-21, five-gram bands from 51-54 to 90-94, then 165-169 and 170+. Every band carries company_car_co2_emissions bounds, so bands from the two schemes never share a key.
  • Table 2C, 2011-12 to 2024-25: car benefit recipients by fuel type ('Diesel (including hybrids)', 'Petrol (including hybrids)', 'Fully Electric').
  • Table 2E, 2011-12 to 2024-25: the average CO2 emissions of company cars, in g/km (41 g/km in 2024-25).
  • Tables 3A and 3B, 2024-25: car and car fuel benefit recipients, taxable value and average taxable value by range of total income.
  • Table 4, 2024-25: car benefit recipients, taxable value and average taxable value by region. Northern Ireland, Scotland and Wales sit beside the English regions, and HMRC's 'Unknown' row gets the statistical_scope id hmrc:recipient_region_unknown.

Not ported:

  • Tables 2B and 2D, which restate Tables 2A and 2C as whole-percent shares of each year's total; the counts are the finer series.
  • The 'All' rows of Tables 3A, 3B and 4 (see below).
  • Table 5 (all Class 1A benefits), which is a separate workbook.

Record-set ids follow hmrc.benefits_in_kind_july_2026.<table>.<measure>.ty<year>, and a fact's source_record_id appends .<row>.<measure>. For example, 2024-25 car benefit recipients are hmrc.benefits_in_kind_july_2026.table1.recipients.ty2024.car.recipients (920,000).

HMRC's semantics

  • Markers emit no fact, never a zero. '<0.5' in Table 2A means fewer than 500 recipients, '<2.5' in Tables 2C and 3B fewer than 2,500, and '[note 3]' in Tables 2A and 2C marks cars emitting 0 g/km, excluded while they carried a zero-rate charge (2011-12 to 2014-15). The generator asserts every marker cell, and each affected measure's notes name its marked rows. Table 3B's three bands with '<2.5' recipients keep their taxable value and average, each with the marker as a guard cell.
  • Rounding is each sheet's own, quoted in each measure's notes:
    • Table 1 to the nearest 10 (10,000 recipients, GBP 10 million, GBP 10 for the averages);
    • Table 2A whole thousands and Table 2E whole g/km, with no stated rule;
    • Table 2C to the nearest 5 thousand;
    • Tables 3A and 3B to the nearest 5 below 10 and the nearest 10 above;
    • Table 4's recipients to the nearest 5 thousand and its amounts to the nearest 10.
  • 2024-25 is provisional (note 1): it comes from an initial extract of the Expenses and Benefits data. Every 2024-25 record set says so on its period_coverage notes. The 2024-25 liabilities also say HMRC bases them on the Survey of Personal Incomes projected forward a year (commentary section 1; background quality report section 3.5).
  • Payrolled cars have been under-reported since April 2016 (commentary section 8). Every record set from 2016-17 carries that caveat on its period_coverage notes, along with what each year could report: no RTI reporting of payrolled cars in 2016-17, voluntary reporting in 2017-18, and compulsory but incomplete reporting from 2018-19. Earlier years note the informal payrolling that kept a few employers' P11Ds out (section 8.1), and 2011-12 to 2014-15 note that electric cars are absent.
  • Table 3A's total need not match Table 1. Tables 3A, 3B and 4 are grossed up from the SPI cases matched to the P11D and RTI records, then scaled to the full data's taxable value. HMRC prints that 'the total number of recipients will not necessarily match the figure in Table 1', and each of their record sets quotes it. Their 'All' rows print the same recipients and taxable value as Table 1's 2024-25 cells, so they are not ported again; the generator asserts that equality. Their 'All' averages would share Table 1's keys, so they are not ported either. Table 4's 'All' average, GBP 3,350 against Table 1's GBP 3,330, shows its grossed recipients differ from Table 1's before rounding.
  • Averages are ported as printed, not derived. HMRC computes them before rounding. For example, Table 1's 2024-25 car benefit average is GBP 3,330, while GBP 3,070 million over 920 thousand recipients gives GBP 3,337.

Provenance

  • Recipients, taxable values and averages in Table 1, and Tables 2A, 2C and 2E, come from employers' P11D returns and, from 2017-18, the RTI records of payrolled cars: administrative.
  • HMRC estimates the Income Tax and NIC liabilities from the SPI cases matched to those records, taking the difference between the liabilities with and without the benefit: model_output.
  • Tables 3A, 3B and 4 are survey_aggregate, with survey instrument 'Survey of Personal Incomes matched to employers' P11D and Real Time Information company car records'. hmrc-isa-statistics-2026 classes its SPI-matched tables the same way.
  • Every fact is an observation, at country K02000001 except Table 4's regions.

The notes cite the release's commentary and HMRC's background quality report. Both were checked against the cited sections.

2. One number per OBR EFO line and year (#397)

The rule (María, 30 October 2026): when two vintages publish a number for the same line and year, Chronicle keeps the later one. The March 2026 packages therefore drop every record set whose line and year the October 2026 package of the same kind publishes. A line and year counts as October's under any line id. That removes 427 facts: receipts 239, expenditure 141, economy 30, aggregates 17. They keep 74:

  • every line's 2024-25 outturn (calendar 2024 for the economy lines);
  • the 2025-26 forecasts of the lines October starts in 2026-27: the seven duties sheet 3.10 breaks betting and gaming duties into, and the aggregates' public sector current receipts, total managed expenditure and net borrowing.

Sheet 3.10's own 2025-26 total goes: October prints it as table 4.1's 'Betting and gaming taxes', with the same concept and accrued basis under another line id (Vahid's first review).

How:

Merge order

Before merge

  • R2: done. chronicle publish-raw uploaded the workbook to r2://ledger-raw/raw/uk/hmrc/hmrc-benefits-in-kind-july-2026/2026/a0ee5617…/2026_Tables_1_to_4.ods and wrote storage.r2 into the manifest (9ca635f). chronicle verify-raw matches the remote SHA-256 and size (42,963 bytes), and the package's facts now pass the consumer contract.

  • The merged-bundle test (test_build_bundle_writes_merged_consumer_contract) runs in CI only. Its pins are updated from measured builds:

    • Facts: 1,662,831 → 1,662,948 (+544 BIK, −427 March EFO).
    • Semantic duplicate keys: 2,687 → 2,324.
    • Geographies: 102,617 → 102,618 (HMRC's unknown recipient region).
    • Packages: 441 → 442. The hmrc source goes to 37,216 and obr to 4,539; source tables go to 384.
    • Country keys: K02000001 41,080, K03000001 23,558, K04000001 1,180.
    • Entities: person 598,988, government 6,702, institutional sector 8,877.
    • Periods: the BIK tax years, and the March decrements for fiscal 2025 to 2030 and calendar 2025 to 2029.

    The EFO deltas come from partial bundles of the nine EFO packages, built on main and on this branch: 4,818 → 4,391 facts and 363 → 0 duplicate keys, with no period, geography or entity key removed. The BIK counts come from the package's facts, run through consumer_fact_rows and build_bundle_coverage with the R2 URI publish-raw will write. Its geography names match Chronicle's register, so it adds no name warnings.

  • Chronicle judge reviews, per AGENTS.md.

Tests

  • The new tests/test_source_hmrc_benefits_in_kind_july_2026.py (14 tests) re-reads every value from the workbook with pandas, located by the publisher's labels. It also checks markers, notes, provenance, counts by table and year, and the generator's reproduction of the committed package.
  • Every one of the 544 facts was also compared with a separate standard-library parse of the ODS. Every numeric cell in the seven ported sheets is either a fact or one of the deliberately unported Total and 'All' rows.
  • chronicle#397: tests/test_source_obr_efo_october_2026.py gains a test that no March fact shares a line and year with October, by record-set id and by concept, unit, period, geography, entity and constraint set, and that each line keeps one year. The March-line test now also covers the two retained templates.
  • Tests updated for the kept years: test_issue_381_uk_excise_facts.py, test_issue_357_microcosm_1095_ports.py, test_chronicle_source_package.py (economy and aggregates), test_scale_value.py and test_chronicle_obr_council_tax_decomposition.py. The scale-value test swaps the dropped income tax FY2025-26 pin for NI employee FY2024-25, which still discriminates; no expenditure line besides Scottish council tax does. The council tax test checks March's 2024-25 identity and October's 2025-26 to 2031-32.
  • Locally, the eight test files this PR touches pass (179 tests), the three generators reproduce their committed packages, and chronicle validate-package hmrc-benefits-in-kind-july-2026 is valid with no warnings. After the review fix, the three EFO test files it touches pass again (57 tests), and after the rebase onto UK facts for chronicle#395: Northern Ireland domestic rates, Welsh bus journeys, the Childcare Offer for Wales, VED by payer and CPI gas weights #396 the eleven test files the branch touches pass (120 tests), UK facts for chronicle#395: Northern Ireland domestic rates, Welsh bus journeys, the Childcare Offer for Wales, VED by payer and CPI gas weights #396's test_source_uk_395_facts.py included. The full suite runs in CI.

🤖 Generated with Claude Code

@vahid-ahmadi

Copy link
Copy Markdown

Review of 9ca635f (draft, so a comment rather than a formal review). First round; no earlier comments or threads. I reviewed b555d8e. 9ca635f only adds the workbook's R2 location to the manifest, and the BIK --check and test file still pass on it.

The benefits in kind package is right: all 544 facts match the pinned workbook, and the pinned file is HMRC's current one. The #397 drop is exact for 425 of 426 cases, and the pins reconcile. One should, about a March line-year that October also publishes under a different line id, and one nit.

Findings

1. should: one kept March fact still has an October number for the same line and year.
March keeps obr.efo_2026_03.receipts.betting_and_gaming_duties_accrued.fy2025: sheet 3.10's total, concept obr.betting_and_gaming_duties, accounting_basis: accrual, UK, FY2025, a forecast of £3,797.2m. October publishes obr.efo_2026_10.receipts.other_hmrc_taxes.betting_and_gaming_taxes.fy2025: table 4.1's 'Betting and gaming taxes', the same concept, basis, geography, entity and year, as outturn of £3,892.0m (scripts/build_obr_efo_october_2026.py:885, docs/data-sources/uk-obr-efo-october-2026.md:78-80). docs/data-sources/uk-excise-facts.md:41-43 says sheet 3.10's total equals sheet 3.1's 'Betting and gaming taxes', so these are the same line in the two vintages.

  • The assertions differ, so this isn't a semantic duplicate. But the bundle still has two numbers for this line and year, which uk-obr-efo-october-2026.md:161 ("a consumer never has two numbers to choose between") says can't happen.
  • The seven duties under it are fine. October has no 2025-26 breakdown for them (table 4.10 starts in 2026-27).
  • test_march_keeps_only_the_line_years_october_does_not_publish (tests/test_source_obr_efo_october_2026.py:174-178) matches on record-set id after a prefix swap, so it can't see a line October carries under another id. Matching on concept, period, geography, entity and constraint set would catch it. I ran that check over all 74 kept facts, and this is the only hit.
  • Please drop it or document why it stays. If it goes: March keeps 73 (74 with UK facts for chronicle#395: Northern Ireland domestic rates, Welsh bus journeys, the Childcare Offer for Wales, VED by payer and CPI gas weights #396), MARCH_KEEPS["receipts"] becomes (40, {2024: 33, 2025: 7}), facts 1,661,872, obr 4,538, the fiscal_year:2025 decrement 58, K02000001 41,040, person 598,507. The header (packages/obr/efo_receipts_march_2026/source_package.yaml:12-14), uk-excise-facts.md:32-35, the October doc (:36-38) and the changelog would need to say "the seven duties" rather than "betting and gaming duties and the seven duties".

2. nit: a few places still describe the pre-#397 picture. scripts/build_obr_efo_october_2026.py:16 and :1652 and the docstring at tests/test_source_obr_efo_october_2026.py:125 say "a consumer changes vintage by package". docs/data-sources/uk-obr-efo-october-2026.md:5 and changelog.d/obr-efo-october-2026.added.md:1 say "beside the March 2026 vintage". This is still true of the line ids, but a line now saying the later years are October's only would match :157-165.

Benefits in kind package

  • Hash. I fetched 2026_Tables_1_to_4.ods from the manifest URL today. It is byte-identical to the pin (a0ee5617…, 42,963 bytes), and it is the only Tables 1 to 4 file on the publication page.
  • Values. I read the ODS with my own standard-library parser (zip plus XML), not Chronicle's cells reader or pandas, and mapped rows by label. All 544 facts match on table, measure, year, row and value. None are missing, extra or different. The units scale correctly: thousands to persons, £ million to GBP, and averages and g/km as printed.
  • Markers. There are 21 marker cells: '[note 3]' ×8, '<0.5' ×6 and '<2.5' ×7. None emits a fact. Table 3B's three '<2.5' bands keep their taxable value and average.
  • Totals. Table 2A's bands sum to within ±5 thousand of the Total row in every year (whole thousands against a total rounded to 10). Table 1's 'all' rows are car plus fuel, to within rounding. 'All' rows of 3A, 3B and 4 are not ported, as the header says.
  • Class 1A. NIC liability divided by taxable value is 13.7% to 13.9% for car benefit in every year except 2022-23 (14.4%, the year's blended rate). That fits employer Class 1A and supports the concept name.
  • Overlap. No other package uses a benefit_in_kind or Class 1A concept. The OBR receipts lines mention Class 1A only inside the NICs footnote ("NIC2"), so nothing overlaps.

#397: what drops and what stays

I built the nine EFO packages at main (53815b2) and at this head, and compared the facts.

  • Dropped: 426, all source_projection. By package: receipts 238, expenditure 141, economy 30, aggregates 17. 363 of them share a semantic key with an October fact. The other 63 are March 2025-26 forecasts beside October 2025-26 outturn on the same line. Every dropped fact has exactly one October fact with the same concept, basis, period, geography and entity, so nothing went that October lacks.
  • Kept: 74. These are identical to main's (ids, keys and values). The October packages are unchanged (4,317 facts, identical). There are no semantic duplicates across the nine (363 → 0).
  • Pins. These match the drop and the BIK package: +544 −426 facts; hmrc +544, obr −426; person +544 −337, government −47, institutional sector −42; K02000001 +505 −342, K03000001 −48, K04000001 −24 (and 12 N92000002, which isn't pinned); the BIK tax-year counts; and the period decrements.
  • Consumers. In this repo, the March sids are read only by the tests this PR updates and by the October build. GitHub code search finds Microcosm's live code reading obr.efo_2026_10.* (uk_runtime/hmrc_uprating.py). The only efo_2026_03 hit is a committed evidence file, docs/evidence/uk-cgt-889/calibration-comparison.json (CGT FY2025, one of the dropped facts). It is a record, not code. policyengine-uk-data has no hits. Code search isn't exhaustive, and Microcosm may select by concept and package rather than by id.
  • What the drop removes. After this, March's forecasts for FY2025-26 to 2030-31 and calendar 2025 to 2029 are no longer in the bundle. They stay in the packages at 53815b2 and in any feed pinned before this PR, and the March workbooks stay pinned under db/data/obr. So comparing the March and October forecasts now takes a pre-OBR EFO March 2026: drop the years the October 2026 vintage also publishes (PolicyEngine/microcosm#1095) #397 commit.

Verdict

The BIK half is ready. On #397, item 1 needs a decision either way. Then it waits on the Microcosm re-pin order in the body and a green bundle job.

What I ran

Fresh clone at b555d8e (BIK --check and its tests re-run on 9ca635f) and a fresh venv, both deleted afterwards. I ran one process at a time.

  • build_package.py --check (BIK) and build_obr_efo_october_2026.py --check: clean. I re-ran build_uk_excise_packages.py and build_uk_1095_port_packages.py, and the tree stayed clean.
  • chronicle validate-package on the BIK package and the four March packages: valid, with no errors or warnings.
  • ruff 0.14.10 (locked) check over CI's paths: passed.
  • Pytest:
    • the six test files this PR touches, apart from the bundle and source-package tests: 81 passed;
    • test_chronicle_bundle.py and test_chronicle_source_package.py, filtered to bundle, OBR, HMRC and EFO, with the merged build excluded: 42 passed;
    • the alias, source-row, drift and boundary tests: 1,154 passed.

Not verified

  • The merged-bundle test and its absolute pins. I reconciled the deltas from partial builds. CI's Chronicle bundle and surface jobs were still pending when I posted.
  • The bundle warning counts (geography names, groupby labels).
  • Table 5 and the background quality report's section references.

🤖 Generated with Claude Code

juaristi22 added a commit that referenced this pull request Oct 30, 2026
Vahid's review of #399: March kept
obr.efo_2026_03.receipts.betting_and_gaming_duties_accrued.fy2025, but October
publishes the same line and year (concept obr.betting_and_gaming_duties, accrued, UK,
FY2025) as table 4.1's 'Betting and gaming taxes', under another line id. The
generators now match March and October on concept, unit, constraints, domain,
geography, entity and period, not on line id, so that record set goes too. The
October build keeps the line's March record set as its template, in its place, so
October's facts are unchanged. The test compares facts the same way, and March keeps
73 facts.

Where the build, the October packages' headers, a test docstring, the October doc
and the #393 changelog fragment still described the two vintages side by side, they
now say the years both publish are October's only.

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

Copy link
Copy Markdown
Collaborator Author

Thanks, Vahid. Both are fixed in 3e1e7e0.

1. Sheet 3.10's 2025-26 total: dropped. October's table 4.1 'Betting and gaming taxes' (obr.efo_2026_10.receipts.other_hmrc_taxes.betting_and_gaming_taxes.fy2025) is the same line and year as March's betting_and_gaming_duties_accrued.fy2025, under another line id: same concept, accrued basis, geography, entity and year. Under the later-vintage rule it goes.

  • build_uk_excise_packages.py and build_uk_1095_port_packages.py now match March record sets with October's on concept, unit, constraints, domain, geography, entity and period, not on line id. Re-running them removes exactly that record set.
  • That line has no March year left, so build_obr_efo_october_2026.py keeps its March record set in LINES_LEFT_IN_OCTOBER_ONLY, inserted before remote_gaming_duty, where it was. --check passes and October's facts are unchanged; the October packages change only in their header comments (item 2).
  • test_march_keeps_only_the_line_years_october_does_not_publish now also compares facts on concept, unit, period, geography, entity and universe constraint set. Rerun over the kept March facts, that comparison finds nothing.
  • The counts are the ones you worked out:
    • March keeps 73 facts, and MARCH_KEEPS["receipts"] is (40, {2024: 33, 2025: 7});
    • the bundle has 1,661,872 facts, with obr at 4,538;
    • the fiscal_year:2025 decrement is 58;
    • K02000001 is 41,040 and person 598,507.
  • The receipts header, uk-excise-facts.md, the October doc and the changelog now say "the seven duties", and say the total is October's table 4.1 row.

2. Pre-#397 wording: updated. These now say the years both vintages publish are October's only:

  • the build's docstring and march_header, and so the four October package headers;
  • the test docstring;
  • the October doc's opening paragraph;
  • changelog.d/obr-efo-october-2026.added.md.

Tests: test_source_obr_efo_october_2026.py, test_issue_381_uk_excise_facts.py and test_issue_357_microcosm_1095_ports.py pass (57), and ruff check is clean. The merged-bundle pins are left to CI.

🤖 Generated with Claude Code

@vahid-ahmadi

Copy link
Copy Markdown

Review of 3e1e7e0, round 2

Thanks, María. Item 1 is resolved. Item 2 is resolved apart from one sentence.

Status of round 1

1. should (kept March fact overlapping October table 4.1): resolved.

  • I built the nine EFO packages at 9ca635f and at 3e1e7e0 and compared every fact field, not just the headers. Exactly one fact goes: obr.efo_2026_03.receipts.betting_and_gaming_duties_accrued.fy2025 (£3,797.2m). The other three March packages and all five October packages are identical, values included.
  • My own overlap check, over the 73 kept March facts against every October fact, keys on concept, unit, constraints, filters, geography, entity and period. It finds nothing. A looser key (concept, geography, entity and period only) also finds nothing.
  • The new matching in both builders is sound. Unit and constraints are in the key, so a March line can't drop against an October line in another unit or on another basis. Outside the policy package, every filter here is also a constraint, so the basis always reaches the key. domain takes four values and is in the builders' key but not the test's. So a line that differed only in domain would be kept by the builder but caught by the test. The excise assertion (bool(kept) != (line_id in OBR_LINES_IN_OCTOBER_ONLY)) is a good guard.
  • The 1095 port's sheet 3.7 facts are unchanged, and re-running both builders leaves the tree clean.
  • The pins match the numbers from round 1: 1,661,872 facts, obr 4,538, fiscal_year:2025 decrement 58, K02000001 41,040, person 598,507, and changelog counts of 427 and 239.

2. nit (pre-#397 wording): mostly resolved. The build docstring, march_header, the four October headers, the test docstring and the October doc now read correctly. One leftover: changelog.d/obr-efo-october-2026.added.md:1 still ends "so a consumer changes vintage by package". Its new opening clause makes that only half true. Dropping the clause, or ending the sentence at "under obr.efo_2026_10", would fix it.

No new findings.

Verdict

Ready as far as items 1 and 2 go, apart from the one changelog sentence. It still waits on the Microcosm re-pin order in the body and a green bundle job.

What I ran

Fresh clone at 3e1e7e0 and a fresh uv venv, both deleted afterwards. I ran one process at a time.

  • build_obr_efo_october_2026.py --check and the benefits in kind build_package.py --check: clean. I re-ran build_uk_excise_packages.py and build_uk_1095_port_packages.py, and the tree stayed clean.
  • Pytest: test_source_obr_efo_october_2026.py, test_issue_381_uk_excise_facts.py, test_issue_357_microcosm_1095_ports.py and test_source_hmrc_benefits_in_kind_july_2026.py gave 71 passed. test_chronicle_bundle.py without the merged build gave 26 passed.
  • chronicle validate-package on the March receipts package and the four October packages: valid, with no errors or warnings.
  • ruff 0.14.10 check over CI's paths: passed.

Not verified: the merged-bundle test and its absolute pins. CI's Chronicle bundle and surface jobs were still pending when I posted.

🤖 Generated with Claude Code

juaristi22 and others added 4 commits October 30, 2026 17:11
For PolicyEngine/uk-autumn-budget-dashboard-2026#316: Budget 2026 raises company car
tax by one percentage point in every band from 2030-31, and policyengine-uk 2.165.0
(PolicyEngine/policyengine-uk#2750) models the company car and car fuel benefits.

hmrc-benefits-in-kind-july-2026 carries 544 facts from HMRC's Tables 1 to 4 workbook
(15 July 2026): Table 1's recipients, taxable value, average taxable value, and
Income Tax and Class 1A NIC liabilities, 2011-12 to 2024-25; Tables 2A, 2C and 2E's
car benefit recipients by CO2 band and by fuel type and the average CO2 emissions of
company cars over the same years; and Tables 3A, 3B and 4's 2024-25 recipients,
taxable value and average taxable value by range of total income and by region.

Markers emit no fact, never a zero; each sheet's rounding is quoted; 2024-25 is
provisional and payrolled cars are under-reported from 2016-17, on every record
set's notes. HMRC's averages are ported as printed, since the rounded cells do not
reproduce them. Tables 3A, 3B and 4 are grossed from the SPI cases matched to the
P11D and RTI records (survey_aggregate); their 'All' rows print Table 1's totals and
are not ported again.

Re-pin the merged bundle: +544 facts, one source table and one geography (HMRC's
unknown recipient region). The merged-bundle test needs `chronicle publish-raw` for
the new artifact first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#397: where two vintages publish a number for the same line
and year, Chronicle keeps the later one. The March 2026 receipts, expenditure,
economy and aggregates packages drop the 426 record sets whose line and year the
October 2026 packages also publish (238, 141, 30 and 17). They keep the 74 October
lacks: each line's 2024-25 outturn (calendar 2024 for the economy), and the 2025-26
forecasts of the lines October starts in 2026-27.

build_uk_excise_packages.py and build_uk_1095_port_packages.py leave out the years
the October receipts package publishes, and the hand-written parts lose the same
record sets; the 74 facts kept are unchanged. The high-value council tax surcharge
has no 2024-25 value, so build_obr_efo_october_2026.py keeps its March record set as
the template for its October line, and the October packages stay byte-identical.

The same rule covers vehicle excise duties (chronicle#395), so the excise script's
outturn-only special case for them goes; March keeps VED's 2024-25 outturn.

Re-pin the merged bundle (-426 facts; semantic duplicate keys 2,687 -> 2,324), and
update the tests, package headers and docs that described the March years.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`chronicle publish-raw` uploaded 2026_Tables_1_to_4.ods to
r2://ledger-raw/raw/uk/hmrc/hmrc-benefits-in-kind-july-2026/2026/<sha256>/ and wrote
storage.r2 into the manifest. `chronicle verify-raw` finds the remote object's
SHA-256 and size (42,963 bytes) equal to the manifest's, so the package's facts now
carry raw_r2_uri and pass the consumer contract.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Vahid's review of #399: March kept
obr.efo_2026_03.receipts.betting_and_gaming_duties_accrued.fy2025, but October
publishes the same line and year (concept obr.betting_and_gaming_duties, accrued, UK,
FY2025) as table 4.1's 'Betting and gaming taxes', under another line id. The
generators now match March and October on concept, unit, constraints, domain,
geography, entity and period, not on line id, so that record set goes too. The
October build keeps the line's March record set as its template, in its place, so
October's facts are unchanged. The test compares facts the same way. March keeps 74 facts:
73, and chronicle#395's VED outturn.

Where the build, the October packages' headers, a test docstring, the October doc
and the #393 changelog fragment still described the two vintages side by side, they
now say the years both publish are October's only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@juaristi22
juaristi22 force-pushed the hmrc-benefits-in-kind-316 branch from 3e1e7e0 to ac9d7bb Compare October 30, 2026 17:34
@juaristi22
juaristi22 merged commit 08962f1 into main Oct 31, 2026
3 checks passed
juaristi22 added a commit to PolicyEngine/microcosm that referenced this pull request Oct 31, 2026
…benefits in kind facts

The UK Chronicle feed moves to 08962f1, the merge of
PolicyEngine/chronicle#399, on top of 7d5d627, the merge of
PolicyEngine/chronicle#396. It has 1,512,256 rows: 53815b2's 1,511,063,
plus #396's 1,076 UK facts from PolicyEngine/chronicle#395 and #399's 544
benefits in kind facts, less 427 March EFO record sets. The schema digest
is unchanged. The merge commit's tree is the pull request head's
(ac9d7bb), so the artifact was built from that head's bundle and carries
the merge commit's identity.

- Vendored resources:
  - a new hmrc_benefits_in_kind_facts.json (96 rows in six selections),
    registered in the country package: the 2024-25 rows the company cars
    stage reads;
  - the 35 other vendored resources keep their rows under the new feed
    identity;
  - the DESNZ constituency resource keeps its 2,526 values, under keys
    re-derived from the new identity.
- obr.winter_fuel_allowance loses its pin to March's table. #399 keeps
  one OBR EFO number per line and year (PolicyEngine/chronicle#397), so
  FY2025-26 is October's outturn, 0.02% above the March projection.
- The local surface was regenerated from a cut of the artifact by the
  200bf6d rules. The references and the compile-parity receipt are
  byte-identical, the membership moves its feed label and the 1,263
  DESNZ keys, and the census restates the pin.
- The coverage manifest is regenerated. It also catches up with the
  company_cars stage.
- The H2 fixture is regenerated. The fixture tool runs company_cars in
  its legacy plan and audits its fuel type as a string column.

The national references, membership and compile-parity receipt follow in
the next commit. The 19 targets that window FY2024-25 and FY2025-26 into
calendar 2025 cannot compile on this feed: their window takes both years
from one series, and no EFO edition now carries both.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

OBR EFO March 2026: drop the years the October 2026 vintage also publishes (PolicyEngine/microcosm#1095)

2 participants