Repository navigation
UK: company cars from the FRS job table (dashboard #316) - #1430
Conversation
2.165.0 (PolicyEngine/policyengine-uk#2750) models the company car and car fuel benefits in kind, and the Class 1A NICs on them, from nine person inputs that default to no car. The lock moves from #1421's 2.164.2 so the company_cars stage can write them (PolicyEngine/uk-autumn-budget-dashboard-2026#316). The uprating and bonus-sacrifice pins keep every value. Concept coverage gains the nine inputs, and the liquid-assets reason and its test name the two GBP stocks among them. The coverage manifest and the H2 spine fixture are regenerated with the stage. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The first piece of the company car work for PolicyEngine/uk-autumn-budget-dashboard-2026#316, not yet declared in the manifest. policyengine-uk 2.165.0 taxes the company car and car fuel benefits from nine person inputs that default to no car. - uk_runtime/company_cars.py writes six of them for each employee the FRS job record gives a company car (the root stage's carriers, which SPI-channel rows inherit from their twins): - fuel from the FRS code (a hybrid is an alternative fuel, a diesel meets RDE2 as declared), and an unknown fuel at Table 2C's shares; - list price within the respondent's band from WAS round-8 company cars' prices grown by the engine's CPI index, the open and unknown bands by fuel class, and a uniform draw where a cell has too few donor households; - CO2 at Table 2A's shares among cars that emit it, hybrids first into 1 to 50 g/km, which takes the declared electric range; - private fuel and capital contributions from the job record. The engine's defaults stay for availability, private-use payments and the 2020 registration flag, which no survey records. - On SPI-channel rows, whose pay is the tape's PAY + EPB + TAXTERM, a car is kept only where EPB is positive, and the engine's car and fuel benefits come out of employment_income, capped at EPB: fuel goes first where it alone exceeds EPB, and a car that still does not fit has its price scaled down (its contributions dropped), or is dropped below the declared minimum price (María's ruling of 30 October 2026). - uk_runtime/hmrc_company_car_statistics.py types HMRC's Benefits in kind statistics (July 2026): the Table 2A and 2C margins the stage draws to and the Table 1 totals it is measured against. The vendored resource waits for the Chronicle package; until then the loader names that. - Engine-free tests cover the declaration checks, the donor, the draws' bounds and margins, identity keying and the carve-out arithmetic; the engine tests check that policyengine-uk 2.165.0 reads the inputs, that SPI pay plus its benefits is the tape's total, and that the 2030-31 rise reaches the holders alone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The root stage now writes four carriers the company_cars stage reads to set policyengine-uk 2.165.0's company car inputs (PolicyEngine/uk-autumn-budget-dashboard-2026#316): frs_company_car_price_band (CARVAL 1 to 9, or 10 for "Don't know" or unanswered), frs_company_car_fuel_code (FUELTYP 1 to 5, or 6 likewise), frs_company_car_private_fuel (EXPBEN03) and frs_company_car_contribution (CARAMT where CARCON is 1, one total to date). A person's company car is the one on their EXPBEN01 job with the lowest JOBTYPE, so a person has at most one, as in the engine. The survey asks CARVAL and CARCON of a company van too; they are taken as the car's. SPI-channel rows inherit their FRS twin's carriers, as they do every root column. The carriers are no engine inputs: they are declared in both manifests, typed in the graph's root cells, and dropped at the release boundary, and the H2 fixture tool's synthetic job tab gives two employees a car (the fixture is regenerated with the stage). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The receipt's checks block counts the rows breaking an invariant any population keeps, for the stage-health gate to require all zero: a non-finite or negative value, a holder without a list price, an input written for someone without a car, an unknown fuel type, a car that emits CO2 without a figure, an electric car with one, private fuel on an electric car, contributions above the list price, an electric range outside 1 to 50 g/km, and an SPI benefit above its EPB. FRS holders without pay are reported, not failed: the benefit is taxable whatever the pay. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
For PolicyEngine/uk-autumn-budget-dashboard-2026#316, before the stage is declared: - gates.json: uk_stage_company_cars_invariants (stage_health, transferred, release-blocking, no population fact check, so the synthetic smoke build blocks on it too) fails on any invariant break the receipt counts and on SPI-channel pay plus its carved benefits off the tape's total by more than maximum_pay_identity_error_gbp. stage_health gains the company_cars check; the spine gate scope, the battery's parameter keys, the microcosm-data contract mirrors and every digest the edit moves follow (the battery's policy, manifest and fingerprint, the spine and release-cut part digests). - The export surface allows the six person inputs the stage writes, in both allow-lists in the same order. - The source manifest's operation kinds and the sources schema gain the stage's six kinds, each a closed branch. - The stage reads its carriers' names from frs_spine. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
For PolicyEngine/uk-autumn-budget-dashboard-2026#316: - uk/spec/sources.yaml, and source_stages.json (its typed projection), declare company_cars as the last stage, after isa_subscriptions, whose income cells read employment_income: nothing then reads the SPI-channel pay the stage rewrites. It reads the vendored HMRC benefits-in-kind facts and the WAS round-8 person tab, and has eight operations (the facts check, the donor, fuel, list prices, CO2, the SPI carve-out, the mass receipt and the reviewed fence). It writes six non-negative person inputs and rewrites employment_income. - Graph and build wiring: the stage's WAS person source, an open read surface (one engine materialization over the frame), its typed cells, and a reader-isolation boundary, since it rewrites a cell the open-surface readers before it bind; the kernel binds its module and helpers and its fixture implementation; the spine build gets its implementation, its WAS person input and its identity-keyed seeds. - The stage's output constants follow the manifest (the six inputs and the rewritten pay). - Fixtures and tests: the H2 fixture tool's WAS persons carry company vehicles and the household weight, and its stage count is 51; the roster tests follow, and isa_subscriptions is no longer the last stage in its notes and test. - docs/uk-company-cars-316.md and the changelog fragment. The graph tests need the vendored facts resource, which waits for the Chronicle package: until it is vendored they fail on the missing hmrc_benefits_in_kind_facts.json, and the H2 fixture and coverage manifest are regenerated with it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
HMRC's employment income counts taxable benefits in kind, which policyengine-uk 2.165.0 keeps out of employment_income. SPI-channel pay is the tape's PAY + EPB + TAXTERM, from which the company_cars stage now carves the company car and car fuel benefits, so the rows on HMRC's employment income would lose them (María's ruling of 30 October 2026, PolicyEngine/uk-autumn-budget-dashboard-2026#316). - A named term, employment_income_with_benefits_in_kind (employment_income + benefits_in_kind), and its annual measure on the annual income simulation. - hmrc.spi.employment_income.amount_by_total_income_band and the local hmrc.employment_income.amount read it: on SPI-channel rows that is the tape's total as before the carve-out; on FRS rows, pay plus the car benefits. Their notes say so. The two count rows keep filtering on employment_income_annual, which only asks whether a person has pay. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…pany_cars The local hmrc.employment_income.amount row now reads pay plus benefits_in_kind, so the stub engine the local metric tests run on declares it (zero for everyone, so no expected figure moves). The private-school attendance stage is followed by isa_subscriptions and now company_cars, neither of which writes its flags. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A list price band now draws from the WAS round-8 donor cars when at least 3 households carry them, the floor María set for every UK output on 29 October 2026 and CD171's absolute floor, instead of 10. The stage still refuses a declared minimum below 3. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The loader reads the 2024-25 rows of hmrc-benefits-in-kind-july-2026 (PolicyEngine/chronicle#399) by their source record ids, <record set>.<row>.<measure>, in persons and pounds as Chronicle carries them: Table 1's car and fuel recipients, taxable value, income tax and Class 1A liabilities, Table 2A's twenty CO2 bands, Table 2C's three fuels, and Tables 3A and 4 by income range and region. Chronicle emits no fact for a cell HMRC marks as suppressed, so a cell the stage needs and the feed lacks is refused, never read as zero, and a resource whose Table 2A or 2C total, or electric count, sits from Table 1 beyond the sheets' rounding is refused too. The band and fuel ids are Chronicle's row ids (co2_0 ... co2_170_plus; diesel_including_hybrids, petrol_including_hybrids, fully_electric), and the stage's verify_vendored_fact_resource declaration names the twelve 2024-25 record sets. The test support builds a synthetic vendored payload and types it with the same loader. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Review of 21e3623 (draft, so an early look as a comment). First round; there are no earlier comments. The stage writes what policyengine-uk 2.165.0 expects. I checked the nine inputs at the 2.165.0 tag. The six written ones have the right units, periods and enum names (GBP stocks uprated by CPI, g/km, miles, Findings1. should: on a frozen fleet, expect the static 2030-31 score well under £310m. The gap comes from the size of the base, not the EV share.
2. should: the SPI pay-identity check in the release-blocking gate can't fail. 3. should: one CI failure is not on the body's expected-red list. 4. should: the lock step pulls in more than the body lists.
So the wealth, LISA, consumption and services draws can move for households with other investment income. Suggest adding these to Results' "gate and fit movement" list for the pair build. 2.165.0 also adds Class 1A to 5. nit: unknown-fuel draws never produce a hybrid. Checked and fine: the three defaults (availability 1, no private-use payments, the 2020 flag) only matter in 2020-22 or slightly raise each car's taxable value for part-year holders; the WAS donor (cars not vans, household weight, floor of 3); CARVAL/FUELTYP mapping and the open band; CO2 bands within Table 2A's bounds; the SPI carve-out arithmetic (fuel first, exact price scaling, £5,000 floor); the re-pointed SPI targets, since VerdictSound design. Items 2 and 3 are small code fixes. Item 1 is about setting expectations before the pair build, and item 4 is about what that build should report. Not ready until the #399 vendoring, the regenerations and the build, as the body says. What I ranFresh clone at 21e3623 and
Not verified: the engine tests (no UK engine locally, no build or Modal), any FRS or WAS figure, and the score itself. Item 1's figures are rough arithmetic from published HMRC and OBR numbers. 🤖 Generated with Claude Code |
The annual measures receipt lists every measure the test computes, and the test never computed employment_income_with_benefits_in_kind_annual, so engine-uk failed on the set comparison. With nobody holding a company car, the measure is the year's pay. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ns within pay - The stage prices its output again on the engine. On every SPI-channel row, pay plus the engine's car and fuel benefits must be the pay the row brought in. The receipt records the largest miss rounded up to the penny, and uk_stage_company_cars_invariants allows 5p for the engine's single precision. The old check compared the carve-out with itself, so it could not fail. - The carve-out's budget also keeps the pay the row's pension deductions need. salary_sacrifice bounds employee contributions and salary sacrifice by pay and bonus sacrifice by half of it, and a later pay rewrite must hold those bounds. - The receipt records the weighted total list price of electric cars and of the others: the base a one-point rise acts on. - Every weighted figure in the receipt is withheld when a cell it rests on has fewer distinct source households than the floor. Shares that sum to one are withheld together. Invariant counts are banded. - docs/uk-company-cars-316.md says what to expect of the 2030-31 score, and that an unknown fuel never draws a hybrid. - The gate battery and certification digests are re-pinned for the gates.json change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…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>
…ed facts - The spine graph test counts 51 manifest stages. - The acceptance receipt's pending transformations append company_cars after isa_subscriptions, as the production plan runs it. - The facts loader drops its pre-vendoring guard, as the ISA loader reads its resource, and its test now loads hmrc_benefits_in_kind_facts.json against the feed pin and checks Table 1 and 2C's 2024-25 values. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PolicyEngine/chronicle#397 keeps one OBR EFO number per line and year, the latest edition's, so the March 2026 tables carry FY2024-25 and the October 2026 tables FY2025-26. Each edition's record sets are their own series, so calendar_year_window found no series with both years. A new selector key, source_table_by_opening_year, maps each opening year to the table that carries it, as source_measure_id_by_opening_year does for measures: - a fact matches only the table its own year declares; - the record set drops out of the series identity, so the mapped tables form the window's one series; - a list-valued source_concept still names several series, each read across the tables; - the selector cannot also declare source_table or the measure map; - calendar_year_window_scaled_by_ratio reads its window the same way. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
María's ruling of 31 October: the 19 OBR targets that window FY2024-25 and FY2025-26 into calendar 2025 take FY2024-25 from the March 2026 EFO and FY2025-26 from the October 2026 EFO, each year's latest edition after PolicyEngine/chronicle#397. Their selectors declare source_table_by_opening_year, and each note says so. - Fifteen of the 19 move by under 1%: income tax -0.5% and UC +0.7%. The four larger moves are domestic rates +3.2%, Pension Credit +2.3%, Jobseeker's Allowance -2.5% and SDLT -1.2%. - Three report-only diagnostics pinned March's FY2025-26 forecasts, which the feed no longer carries: the OBR's capital gains tax receipts, State Pension spending and total NICs. They pin October's outturns (observation, observed_only), with their notes restated. The NICs total now sits 2.1% above the class rows at the window. - The test feed fixture's captured March rows give way to the feed's current ones: October's FY2025-26 income tax, incapacity benefits and capital gains tax, and March's FY2024-25 income tax in its v4 form. The national calibration tests' synthetic window facts carry each year's table. - Regenerated from the 08962f1 national cut: the national references and membership, and the national compile-parity receipt. The receipt's OBR rationales name the two editions, and Winter Fuel, which binds FY2025-26 alone, has a rationale of its own. - The membership also catches up with policyengine-uk 2.164.1's October-EFO indices, which #1421 locked after #1420 had generated it: the eight SPI Table 3.10 self-employment bands move -0.55% and the thirteen dividend bands under 0.001%. The receipt already had these values. - docs/uk-income-anchors-280.md describes the map, the re-pin runbook gains the 08962f1 entry, and the changelog fragment is complete. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks, Vahid. All five items are addressed. Item 2 led to one more fix, and to a disclosure pass on the stage's receipt. 1. The 2030-31 expectation and the list price base.
2. The pay identity check couldn't fail.
3. The engine-uk failure. 4. The 2.164.3 and 2.164.4 predictor moves. They are in Results' gate and fit movement list:
5. Hybrids. The doc now says that an unknown fuel never draws a hybrid, since Table 2C counts hybrids inside petrol and diesel, and that the effect is small. What I ran:
The chronicle#399 re-pin and the BiK vendoring are pushed alongside. So is the fix for the 19 OBR windows that #397 split across the March and October EFOs: they take FY2024-25 from March and FY2025-26 from October (María's ruling of 31 October). The body's Status says what each moved. 🤖 Generated with Claude Code |
Independent review of #1430 at
|
María plans to merge #1422 first, so #1430 builds on it (uk-1095-next at 0a69ef4). - The lock is #1422's policyengine-uk 2.165.1, with its stamps, pins and version mentions. - #1422's three OBR council tax nation rows declare source_table_by_opening_year, as the other windowed OBR rows do on the 08962f1 feed. The council tax, SDLT and UC notes #1422 restated keep the edition sentence. - UK_ENGINE_DEFAULT_INPUTS signs only the three company car inputs the stage leaves at the engine's defaults, with a reason that states the full-year approximation. The six the stage writes are no longer signed. - Both sides' gates are in the contract mirrors, and the gate battery and certification digests are re-pinned. - Both sides' calendar-year window tests are kept. - The generated surfaces keep one side here: the national references, membership and compile-parity receipt, the coverage manifest, the H2 fixture and the concept coverage. The commits that follow regenerate them on the merged tree. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The review is issuecomment-6545997069 on #1430. María's rulings of 31 October 2026: carve upstream, fuel first, and measure the EPB shortfall before correcting it. - Items 2 and 10: company_cars runs right after hmrc_spi_income_spine, so every later stage reads SPI-channel pay without the car benefits. That covers salary_sacrifice and its bounds, the take-up draws' earnings rules, the gainful self-employment flag and the CGT stages, and the identity tool's salary-sacrifice carrier holds. The carve no longer keeps room for pension deductions; salary_sacrifice bounds them on the carved pay. - Item 11: only tape rows (person_is_spi_income_recipient) carve. The SPI channel's undrawn copies and dependants keep their twin's pay and car as FRS rows do, and the pay identity is checked on the tape rows. - Item 7: where the car and fuel benefits do not fit inside EPB together, the fuel goes first, so a car that fits without it is always kept. An unknown-fuel holder who reports private fuel draws petrol or diesel. - Item 3: the receipt records the weighted holders and list price the tape rows lose for want of EPB. - Item 4: the receipt records the list price the scaling takes off and that of the cars it drops. The docs say the electric range and RDE2 assumptions set a scaled car's price, so they move the 2030-31 base. - Item 5: the receipt states the CO2 shares of the cars the output holds beside the draw's. - Item 6: the docs say the receipt's figures are at the stage's weights, and the results measure the base on the calibrated file. - Item 1: obr.ni_employer binds ni_class_1_employer. OBR's line is table 4.4 row 18, Class 1 only; Class 1A is row 21. Three notes no longer say that no household carries Class 1A. - Item 8: the employment income count rows filter on pay plus the car benefits, as the amounts read. - Item 9: band 1's uniform draw starts at the declared minimum list price, GBP 5,000. - Item 12: company_cars gains a coverage-manifest family. - Item 13: - the annual simulation makes a car available for the share of the year the pay covers; - the fact fence's count is compared with the vendored rows; - CARVAL's bounds and FUELTYP's codes are defined once in frs_spine; - the stage's helper modules gain the engine adapter, spi_income and stochastic_assignment; - the stage notes and graph comments are restated. - Cross-PR: the guard test counts every stage's declared outputs. The policyengine-uk 2.165.0 fragment gives way to #1422's 2.165.1. The generated surfaces are regenerated on 08962f1 in the next commit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#1422's head is now 43c3da7 (its review commits 7a95f37, 42913ab and c0dd6ea, and their regeneration on 53815b2). - The three OBR council tax nation rows are now calendar_year_window_scaled_by_ratio with obr.council_tax's MHCLG operands. They keep source_table_by_opening_year beside them, and their restated notes keep the edition sentence. - The engine-defaults guard is #1422's: it counts every stage's declared outputs and rewrites. One assertion is added, that it sees the company car inputs. - Both sides' gates are in the contract mirrors, and the gate battery and certification digests are re-pinned. - The generated surfaces keep this branch's side, to be regenerated on 08962f1 next. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Run on the merged tree, one job at a time, from the 08962f1 cuts: - The vendored resources: re-vendored on the full artifact. All 36 are byte-identical to the merge. - The national references and membership: 1,409 active, 30 signed out. Relative to this branch's last regeneration, only #1422's changes move: - the three OBR council tax nation rows resolve, scaled to main residences, with their edition maps; - obr.council_tax moves -3.2% (main residences); - the DfE funded childcare rows take their 2026-01 observation period; - the SLC devolved total splits into Scotland, Wales and Northern Ireland; - the TFC top-up, the NI bus receipts and Scottish Child Payment are restated. - The national compile-parity receipt follows. - The local census, references, membership and compile-parity receipt are byte-identical to the merge. - The coverage manifest, the H2 fixture (company_cars after the SPI income stage) and the policyengine-uk 2.165.1 concept coverage are regenerated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The national references, membership and compile-parity receipt are unchanged. - The local compile-parity receipt takes #1422's restated rationale for its 903 ONS small-area rows; no value moves. - The coverage manifest and the H2 fixture follow the round's stage notes and receipts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#1422's bd9839b regenerates its H2 spine parity fixture on #1422's code. This branch keeps its own, regenerated after merging 5b693b7 on code that adds the company_cars stage. It already carries the SPI receipt wording bd9839b brings, and it records this branch's own hmrc_spi_income_spine contract digest. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#1422's 821e342 answers its third review: restamp failures raise and name the row, the reference period is one constant (UK_TARGET_REFERENCE_PERIOD), the spine gates read the assembled boundary from graph_evidence, and the stage-health count bounds and the floor-of-ten guard are tighter. It merges without conflicts, and company_cars stays in the transferred phase, after frs_brma. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The company cars invariant counts an electric range set on a car outside 1 to 50 g/km (`electric_range_outside_1_to_50_g_km`). The gate notes and the stage-health docstring said "an electric range outside 1 to 50 g/km", which reads as a range measured in g/km. The gate digests are re-pinned. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks for the review. Every item is addressed, on top of #1422's head, since #1422 merges first. María ruled on three of them on 31 October: the carve moves upstream (items 2 and 10), the fuel goes first (item 7), and the EPB shortfall is measured before anything corrects it (item 3). Fix before the build
What the Results will measure
Stage behaviour
Coverage and texts
Cross-PR with #1422
What I ran, on the 08962f1 feed, on the merged tree with #1422's head 821e342:
Before #1422's third review I also re-vendored the resources (byte-identical), regenerated the coverage manifest and the concept coverage, and ran the bus targets file and the national compile-parity regeneration on the full artifact, with its release gate. All passed. After the third review, the references and receipts those two runs check regenerate byte-identical from the feed's cuts, so I didn't repeat those 27 GB runs. 🤖 Generated with Claude Code |
#1422's 0da33d0 answers its fourth review: periods are read under their declared type, a period's year needs four digits, the authored observation period reads the selector's period type, the assembled boundary moves to graph.py, and the floor-of-ten guard reads more spellings of ten. It merges without conflicts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#1422's 4af602d answers its fifth review: an observation period is read and moved as its reference's match policy reads it, a spelling read two ways refuses to move, the compile-parity reasons are one constant the receipt test replays, and the floor guard reads floor words in more names and spellings. It merges without conflicts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Second review of #1430 at
|
| # | Status |
|---|---|
1. obr.ni_employer |
Fixed. It binds ni_class_1_employer, which reproduces the old measure exactly. Correction: my round-1 note said the solve would move weight. That was wrong: the row is held out of the objective until 2027-01-16, so the fix corrects a diagnostic. Leftovers are in 6 below. |
| 2. Identity tool | Fixed. The carve runs right after the SPI stage. A probe chain (company_cars, then salary_sacrifice, then the tool's pre-sacrifice frame) holds. But see 2. |
| 3. EPB shortfall | Fixed. The receipt measures the lost holders and list price. |
| 4. Price scaling | Partly. The arithmetic and the receipt fields are right. But docs/uk-company-cars-316.md:46-47 and :108-109 still say the diesel supplement and the frozen electric share move only the baseline, which contradicts :86-94. |
| 5. CO2 after the carve | Fixed (co2.drawn and co2.output). |
| 6. Pre-calibration weights | Fixed in the texts. |
| 7. Fuel first | Fixed, and private fuel can't draw electric. The unknown-fuel shares are in 7 below. |
| 8. Count rows | Fixed. All four HMRC employment rows read the benefit-in-kind term; #1428's new row doesn't (3). |
| 9. Band 1's floor | Fixed. |
| 10. Upstream pay rules | Partly. Bonus sacrifice, UC take-up, Carer's Allowance and the CGT stages see carved pay. Exception: the UC gainful self-employment flag is set inside the SPI stage on pre-carve pay (uk_runtime/spi_income.py:3015-3047), and nothing refreshes it. A side-trade profit of £5,000, with pre-carve pay of £6,000 and carved pay of £1,000, should be flagged and isn't. The reply says this flag sees carved pay. |
| 11. Tape rows only | Fixed. |
| 12. Coverage | Fixed. |
| 13. Small items | Fixed. The availability share matches ITEPA ss. 143(3) and 152(1). |
| Cross-PR with #1422 | Fixed. The nation rows carry the edition map, three defaults stay signed, and the guard counts every stage's outputs. A forward merge with #1422's current head (4af602d81) is clean, and its tests pass. |
Also new
5. The calibrated-file report the doc promises doesn't exist.
- The promise:
docs/uk-company-cars-316.md:140-142says the national reports the calibrated file against HMRC Tables 1, 3A and 4. So do the BIK facts' purpose text and the gate note (uk/gates.json:1059, "measured, not gated"). - What exists: the only reader of
HMRCCompanyCarStatisticsis the stage itself, before calibration. - Fix: add the diagnostic, or say the comparison is a manual step.
6. The obr.ni_employer exclusion still describes ni_employer (uk/calibration_measure_exclusions.json:445-449).
- The texts: its reason calls
ni_employerthe secondary Class 1 charge. Its condition says to rebind "when a locked policyengine-uk nets the allowance out of ni_employer". - The risk: with the row now on
ni_class_1_employer, meeting that condition (pe-uk#2459) would leave the bound measure gross. Following the register would then rebind about £7.4bn high. - Also:
tests/engine_free/uk/test_uk_national_insurance_targets.py:19-21and the body's fit-movement note are stale, and no test pins the new binding.
7. Smaller:
- Unknown-fuel shares: the unknown-fuel draw no longer meets Table 2C (
uk_runtime/company_cars.py:658-672). Fuelled holders now exclude electric, while unfuelled ones keep the unconditional shares. So the electric share among unknown-fuel holders is (1 − f) × Table 2C's, where f is the share reporting private fuel. - Income proxy: the CGT and ISA income proxy (
uk_runtime/cgt_imputation.py:191-208) sumsemployment_incomealone. With the carve upstream, tape-row holders' car benefit drops out of it, though the engine's total income includesbenefits_in_kind. obr.fuel_duties: it is the only OBR window row without the edition map (uk/uk_population_targets.json:2214-2240). It is signed out today, but it would refuse if brought back.- Council tax nation rows: their parity reason omits the main-residence scaling. This comes from UK: take up #1095's actionable items on policyengine-uk 2.165.1 #1422 and is carried in this PR's regenerated receipt.
8. Stale texts:
uk/gates.json:1059anduk_runtime/stage_health.py:5107and:5136still say "any SPI-channel row … less any salary sacrifice";uk_runtime/salary_sacrifice.py:746-752;docs/uk-isa-subscriptions-1415.md:5, though ISA is now last;- three docs count 19 rows with
source_table_by_opening_year, where the merged tree has 22; - the engine test texts still say "SPI-channel".
9. Test gaps: no test does any of these:
- runs
salary_sacrificeon the stage's output; - checks the stage's fit-weight evidence;
- pins the
ni_class_1_employerbinding; - compares
co2.drawnwithco2.output.
Checked and found true
- The scaled-price arithmetic matches policyengine-uk 2.165.1. The electric range rule in
40968ff53matches ITEPA s. 139 and the engine. - Draws stay keyed and uniquely salted, so moving the stage changes no other stage's draws.
- The regenerated surfaces match their generators wherever that can be checked without the feed: the coverage manifest, local census, contract digests, parity reasons,
uv.lockand concept coverage. - The nine merge resolutions drop no side.
- The 2.1% gap the parity reason cites is consistent with October's tables.
Disclosure-control items went to the author separately.
🤖 Generated with Claude Code
- Drop the stage's empty fit-weight records, which the release-cut weights audit read as a fitting stage that recorded none (as #1428 does for ISA), with a test on the stage's evidence. - Cap the tape rows' carve at EPB within the pay the employee pension contribution leaves, so salary_sacrifice never meets a contribution above pay. The review's two probes are tests, one of them running salary_sacrifice on the stage's output, and the invariants count a contribution above the carved pay. - Take a carved row's UC gainful self-employment again on its carved pay, by the root's rule; the flag is a declared output and rewrite. - Draw the unknown fuels to Table 2C together: the holders without private fuel draw electricity at Table 2C's share over the share the private-fuel holders leave. - Say the calibrated-file comparison with Tables 1, 3A and 4 is a manual step (doc, stage note, fact fence, BIK purpose text, gate note). - Restate the obr.ni_employer exclusion for the ni_class_1_employer binding, with a test pinning both; give obr.fuel_duties the edition map; give the council tax nation rows a parity reason that names the main-residence scaling. - Restate the stale texts: the gate note and stage health, the salary_sacrifice comment, the ISA doc, the docs that counted 19 mapped rows, the engine tests and the doc's baseline-only sentences. Gate digests re-pinned. Generated surfaces follow in the next commit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On the 08962f1 cuts: the national compile-parity receipt gives the three council tax nation rows their main-residence reason; the membership's obr.fuel_duties entry matches the two tables its new edition map names (it stays signed out); the coverage manifest and the H2 fixture carry the company_cars declaration that writes the UC gainful flag. The national references and the local receipt regenerate byte-identical. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The engine reads employment_status as an EmploymentStatus name, whose full-time employee member is FT_EMPLOYED. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks for the second review. Every item is addressed, on top of main now that #1422 is in (11c1f4e). Merging main brings no UK file. Fix before the build
Merging with #1428 (3 and 4) These fall to whichever PR lands second. If #1430 lands first, #1428 needs to:
Round-1 items that were only partly fixed
Also new
What I ran, on the 08962f1 feed, on main (11c1f4e) merged with these fixes (head 144d738):
CI runs the full engine-free suite on this head. Locally, the full suite passed on the previous merge with #1422's final head: 14,487 tests, and only the three checks that can't pass on this machine fail. 🤖 Generated with Claude Code |
Third review of #1430 at
|
| # | Status |
|---|---|
| 1. Fit-weight records | Fixed. The property and its import are gone. The new evidence test (packages/microcosm-build/tests/engine_free/uk/test_uk_company_cars.py:825) fails if the declaration comes back. With ISA's declaration also removed, a probe audit passes. ISA is the rest (Verdict). |
2. Carve and salary_sacrifice |
Fixed. The budget is EPB within pay less the employee contribution (uk_runtime/company_cars.py:985). Both of my probes now run: PAY 0, EPB £3,000 and a £150 contribution keeps its car, with £2,850 carved and £150 of pay left. £20,000 of pay with a £20,000 contribution drops the car and keeps the pay. Salary and bonus sacrifice and the private-fuel variants run on the output. The new invariant can't fire on legitimate input. See 3 for the cost. |
| 3. #1428's employment row | Right but incomplete (Merging). |
| 4. Merge steps | Right but incomplete (Merging). |
| Round-1 item 4 | Fixed. The doc's two sentences agree with the scaling. |
| Round-1 item 10 | Fixed in code. A carved row takes the root rule on its carved pay, using the SPI stage's exact inputs, and my £5,000 side-trade case is flagged. Untested at the stage (1). |
| 5. Calibrated-file report | Fixed. It's a manual step, and every text says so. |
6. obr.ni_employer |
Fixed as asked. The new binding test fails on revert. Residual texts are in 4. |
| 7. Smaller | Fixed. The unknown fuels meet Table 2C together wherever the private-fuel holders leave room (a 200,000-row probe). obr.fuel_duties carries the map, and the nation reasons name the main-residence scaling. The side effect is in 2. |
| 8. Stale texts | Fixed, except the module docstring (5). |
| 9. Test gaps | Fixed. Each new test fails under its mutation. |
Checked and found true
- The carve: on every path the benefit is at most the budget, which is at most pay less the contribution. The SPI cap is an exact minimum on the same rows, with no stage between them. The gate blocks on every violation key.
- The UC refresh: it applies the SPI stage's rule and only turns flags on, only on carved rows. Its declaration matches across the sources, the graph, the coverage manifest, the H2 fixture and the export allow-list. Benefits in kind aren't UC earnings in 2.165, so carved pay is the right comparator.
- The fuel draw: the shares sum to 1. Where the private-fuel holders leave too little room, every unfuelled car draws electricity and petrol and diesel keep Table 2C's ratio. f is weighted, and the salts are unchanged.
- Regenerated surfaces: the nation parity reasons equal the generator's constant, and the replay passes for the national and local receipts. The fuel duties membership entry, the coverage manifest and the H2 fixture are consistent.
- Merges: the remerge-diffs of
14dc81547,23aadd8f3and4d4c1a6e6are empty, and main brings no UK file. A trial merge with main atdfa5fe399is clean, and main's one later commit (Harden the money run-entry walk and cover its gaps #1441) touches no UK file. - CI on
144d73836: every job passes. The touched files pass locally too: the stage, salary sacrifice, stage health, receipt counts, the UK and generic ledger targets, compile parity, the data contract, the NI targets and the graph.
Disclosure-control items went to the author separately.
🤖 Generated with Claude Code
- Draw the lost holders' fuel at the holders' electric share: the tape rows that lose their car for want of EPB passed their own private-fuel share to the unknown-fuel draw (unknown_fuel_electric_share; the draw takes the share as a parameter). - Rename the unknown-fuel rule to table_2c_shares_together_..., in the schema and both manifests, so a declaration for the old rule is refused; a test says so. - Measure what the employee contribution's room costs: the carve runs again inside EPB within pay alone, and the receipt counts the cars the room scales further or drops and the list price they give up. - Test the stage-level UC refresh: a carved tape row given a side trade between its carved and drawn pay is flagged and counted (the review's no-op mutation now fails it). - Pin every HMRC employment income row, amount or count, national or local, to the benefit-in-kind term, so a new row reading pay alone fails (the merge with microcosm#1428's Table 3.10 row). - Restate the module docstring, the ISA doc's stage position (true with later stages), the salary_sacrifice comment (its conversion doesn't refresh the UC flag) and the ni_employer exclusion's under-21 sentence (the 2.165.1 lock applies the zero rates). Generated surfaces follow in the next commit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rule The source manifest's hash moves with the unknown-fuel rule's new id, and the H2 fixture's copy of the company_cars declaration and its contract digest follow. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks for the third review. #1428 lands after this PR and picks this code up, and builds run from there (María's call). So the merge-time items stay with #1428, and everything that tightens this PR on its own is fixed here. Left for #1428
Fixed here
What I ran, on head de41aba:
Nothing here touches a target or the feed, so the references, memberships and parity receipts stand as last regenerated. CI runs the full engine-free suite on this head. 🤖 Generated with Claude Code |
Fourth review of #1430 at
|
| # | Status |
|---|---|
| 1. Stage-level UC refresh | Fixed. The new test gives a carved row a side trade between its two pays, and asserts the flip and the receipt count. It fails when the refresh is made a no-op. |
| 2. Lost holders' fuel | Fixed. The lost rows take the holders' electric share, and the new test reproduces the probe. |
| 3. Contribution room | Measured, with the choice of route left to a ruling after the pair build. One nit, not blocking: cars_the_employee_contribution_scales_or_drops also counts a car that keeps its price and loses only its private fuel to the room, while the name and the doc say "scales further or drops". |
4. obr.ni_employer texts |
Fixed. The under-21 sentence is in the past tense, and policyengine-uk#2459 has the note. |
| 5. Texts | Fixed. The module docstring, the renamed rule (a test refuses the old name) and salary_sacrifice's comment are updated. |
| Merge steps | Moved to #1428, which lands after this PR. The new test makes a forgotten switch of the Table 3.10 employment row fail loudly. The ISA doc line now holds either way. |
What #1428 needs after this merges
- Switch the Table 3.10 employment count row, and its note, to
employment_income_with_benefits_in_kind_annual. Update its pinning test and the Table 3.10 cross-grain reason to match. - Re-vendor Table 3.7 on 08962f1, and regenerate its membership and parity receipt.
- Set the stage count to 52 in
test_uk_graph.pyandtest_country_spec.py. - Regenerate the gate-battery pins in
contract.pyandtest_contract.py. - Regenerate the coverage manifest and the H2 fixture.
Disclosure-control items went to the author separately.
🤖 Generated with Claude Code
Main brings #1422 (policyengine-uk 2.165.1 and the small-cell floor of three) and #1430 (the company_cars stage and the Chronicle feed re-pin to 08962f1). Following #1430's merge order for this PR: - Both PRs add a stage in the same places, so the stage lists, kernels, transforms and coverage contracts keep both: company_cars after the SPI income stage, trading_allowance_receipts still last. The stage count is 52 in the spec tests, the graph test and the fixture tool. - The UC take-up test keeps this PR's gap passing with #1422's source households and floor: band 0's pool withholds its flag (<3), band 2's, on four sources, says the count exceeds every entitled unit. - The Table 3.10 employment count reads employment_income_with_benefits_in_kind_annual, the term every HMRC employment income row reads since #1430, with its notes, cross-grain reason, test and changelog. - The support floor's adjudication keeps #1422's floor of three and this PR's extension to the employment count. - Counts add both sides: 460 population targets and 398 in the registry scope, 45 held measure names, 185 mapped and 213 unmapped declarations, 1,418 active references and 37 signed exclusions. Regenerated on Chronicle 08962f1 (the artifact María supplied, digests verified): the Table 3.7 savings rows (feed identity only; the values are unchanged), the national references and membership and the national compile-parity receipt from the national cut, then the coverage manifest, the H2 fixture on policyengine-uk 2.165.1, and the gate-battery pins and certification part digests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… 2.165.1 The bundle moves from the national cut 0a4c8e2c (a8e25870c, 2.164.2) to f6e33b93, built from PolicyEngine/microcosm#1428 at 329a99e71: the independent review's fixes, recalibrated trading allowance claimant rates, and main with PolicyEngine/microcosm#1422 and PolicyEngine/microcosm#1430 merged. It is certified on its own build, policyengine-uk 2.165.1. The local-area cut stays 6528c865 (Max's call between it and c9df4b86), re-audited for 2.165.1: no dropped column, and fourteen inputs added after 2.162.12 take their defaults, now including the nine company car inputs, so local-area results carry no company cars. The lock, release lock, TROs and the household calculator snapshots are regenerated on 2.165.1, and the bundle checks pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#1422 landed (11c1f4e), followed by #1430's company cars and #1432's default load. Only pinned and generated files conflicted. The gate battery and spine-part digests are re-pinned on the merged gates spec (five move), and the release input coverage manifest and the charter-H2 fixture are regenerated; the fixture checks byte-identical. policyengine-uk stays at 2.165.1, the lock main and #1428 share, and the feed pin moves to main's 08962f1. The H2 parity test and 86 related engine-free files pass (5,682 tests, 53 skipped). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
What and why
Budget 2026 raises every company car appropriate percentage by one point in 2030-31: Table 6.1 row 86, +£310m in 2030-31 and +£345m in 2031-32 (OBR split: income tax 226.2, NICs 92.4). The dashboard can't score it (PolicyEngine/uk-autumn-budget-dashboard-2026#316), because neither the engine nor the data had company cars.
company_cars, that fills six of those inputs, on UK: take up #1095's actionable items on policyengine-uk 2.165.1 #1422's policyengine-uk 2.165.1 lock.Without the stage, a file on 2.165.0 still scores the rise at £0.
Status
uk/hmrc_benefits_in_kind_facts.jsonvendors the 96 2024-25 rows the stage reads, and is registered incountry_package.json.source_table_by_opening_year, takes FY2024-25 from March's table and FY2025-26 from October's (María's ruling of 31 October).obr.capital_gains_tax,obr.state_pensionand theobr.nitotal. They now pin October's outturns. None of them is in the fit.obr.winter_fuel_allowancereads October's FY2025-26 (+0.02%).obr.fuel_duties: 23 rows in all.Merge order
UK: take up #1095's actionable items on policyengine-uk 2.165.1 #1422 is in. Main (11c1f4e) is merged here.
What this PR reconciles. UK: take up #1095's actionable items on policyengine-uk 2.165.1 #1422 stayed on Chronicle 53815b2 (María's call), so the feed pin and the table maps arrive with this PR. This PR also regenerates every shared surface on 08962f1.
SPI donor savings rebased to SPI's 2023-24 bands (savings follow-up to #1418) #1428 lands after this PR and picks this code up, and builds run from there (María's call). On merging main, SPI donor savings rebased to SPI's 2023-24 bands (savings follow-up to #1418) #1428:
hmrc.spi.employment_income.count_by_self_employment_income_bandrow, its test and its Table 3.10 cross-grain reason toemployment_income_with_benefits_in_kind_annual, which a test here now requires of every HMRC employment income row;Its
b9bcd709cremoves ISA's empty fit-weight records, which the weights audit needs.Company car inputs.
UK_ENGINE_DEFAULT_INPUTSsigns only the three inputs the stage leaves at the engine's default: availability, private-use payments and the 6 April 2020 registration flag. Its reason states the full-year approximation. UK: take up #1095's actionable items on policyengine-uk 2.165.1 #1422's guard test counts every stage's declared outputs and rewrites.Changes
policyengine-uk 2.165.1, UK: take up #1095's actionable items on policyengine-uk 2.165.1 #1422's lock. 2.165.0 (Model the company car and car fuel benefits, with Class 1A NICs policyengine-uk#2750) brings the company car inputs. 2.164.3 also counts
other_investment_incomein market and HBAI income, which the spine fills (UK: data fixes for the Budget 2026 household measures #1418), so HBAI-based outputs move too.The FRS job tab's company car answers. The root stage carries four columns per person from their car job with the lowest JOBTYPE (one car per person, as in the engine):
They are dropped at the release boundary.
The
company_carsstage runs right after the SPI income stage (hmrc_spi_income_spine; María's ruling of 31 October). Every later stage reads the tape rows' pay without the car benefits, as the engine'semployment_incomeexcludes them:salary_sacrificeand its bounds, the take-up draws' earnings rules and the CGT and ISA stages' income proxy. Every draw is keyed on identity. The stage fits no model and declares no fit-weight records.person_is_spi_income_recipient) keeps its twin's only where its drawn EPB is positive. The channel's undrawn copies and dependants keep their twin's pay and car, as FRS rows do.The tape rows' pay. It is the tape's PAY + EPB + TAXTERM, so the engine's car and fuel benefits come out of
employment_income, capped at EPB (María's ruling of 30 October) within the pay the row's employee pension contribution leaves. The SPI income stage capped that contribution at the drawn pay, andsalary_sacrificerefuses one above the pay that remains; a row whose contribution takes all its pay loses its car. The receipt measures what the room costs beside a cap of EPB within pay alone: the cars it scales further or drops, and the list price they give up.The stage then has the engine price its output again: on every tape row, pay plus the engine's benefits must equal the pay the row brought in. A carved row's UC gainful self-employment, which the SPI income stage set on the drawn pay, takes the root's rule again on the carved pay. Employer contributions stay as the SPI income stage set them on the drawn pay, as on every tape row that draws EPB.
Targets.
hmrc.spi.employment_income.amount_by_total_income_bandand the localhmrc.employment_income.amountreademployment_income_with_benefits_in_kind_annual, a new named term for employment income plusbenefits_in_kind, which HMRC's employment income counts.obr.ni_employerbindsni_class_1_employer. OBR's line is Class 1 employer NICs (table 4.4 row 18), and Class 1A sits in row 21.The facts.
hmrc_company_car_statistics.pyreads Full eCPS input coverage: 158 required columns, 8 evidence-backed exclusions #399's 2024-25 rows by their source record ids, in persons and pounds:Gates.
uk_stage_company_cars_invariantsis release-blocking and runs on the synthetic smoke too. It fails on:Not bound. No HMRC company car fact is a calibration target: the stage draws to Tables 2A and 2C, so binding them would be circular (María's ruling of 28 October on the ISA facts). Comparing the calibrated file with Tables 1, 3A and 4 is a manual step for now.
Results
PENDING the pair build, which follows the merge. Every figure is checked for disclosure before it goes in.
list_price.weighted_total_gbp) is at its pre-calibration weights. Two published comparators frame it: HMRC's 2024-25 taxable value implies roughly £27bn to £37bn, and the OBR's NICs line about £60bn.holders.spi_dropped_without_epb; María's ruling of 31 October: measure first), and the list price the carve-out scales away or drops, including what the employee contribution's room takes.COMPANY_CAR_TAX_RISE_2030against Table 6.1 row 86 (+£310m). Expect it well under £310m, from the size of the base (docs/uk-company-cars-316.md, "What to expect of the 2030-31 score"). The file holds the build year's fleet: fuel and CO2 are frozen, prices grow by CPI, and the weights are not projected. A file that matches Table 1 exactly should still fall about 30 to 45% short.obr.income_tax.obr.ni_employernow measures Class 1 alone, but it is held out of the objective as a diagnostic until 2027-01-16, so the fit doesn't move with it;other_investment_incometomarket_income,household_market_incomeandhbai_household_net_income, and 2.164.4 tocapital_income. Those feed stage predictors, so the draws below can move for households with other investment income:was_wealth, which materializeshousehold_net_incomeandcapital_income;was_lisa(household_net_income);lcfs_consumptionandetb_services(hbai_household_net_income).Tests
test_uk_company_cars.py, engine-free and engine. It covers the reviews' cases:salary_sacrificerun on the stage's output;test_uk_company_cars_stage_health.py;test_uk_frs_spine.py;test_uk_measure_simulation.py;ni_class_1_employerbinding and its exclusion intest_uk_national_insurance_targets.py;test_uk_population_targets.py.tools/run_integration_tests.shwas not run locally; CI'sintegration-ukruns it.Disclosure
The FRS and WAS are UK Data Service data (SN 9563, SN 7215). The stage's receipt carries weighted aggregates, rounded counts and support bands only. The HMRC figures quoted here are published.
Not in this PR
🤖 Generated with Claude Code