Skip to content

Model Marriage Allowance as the statutory s.55B transfer and Step 6 reduction - #1963

Draft
MaxGhenis wants to merge 38 commits into
mainfrom
marriage-allowance-election
Draft

MaxGhenis wants to merge 38 commits into
mainfrom
marriage-allowance-election

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Sep 30, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #1947.

Builds on #1949 (merged), which deducts each person's own tax on earnings in UC.

What the law says and what changes

ITA 2007 ss. 55A-55E (legislation.gov.uk, read 2026-09-30; verbatim text in the issue):

Before After (= law)
Transferable amount min(partner's unused allowance, 10% of PA), rounded up to £10 10% of the s. 35(1) allowance, rounded up to £10 (s. 55B(4)(b), (5)): £1,260 in 2026-27. 2015-16 is fixed at £1,060 whatever the allowance (s. 55B(4)(a), a new fixed_amount parameter)
Transferor allowance unchanged allowance cut by the transferable amount (s. 55B(6))
Recipient taxable non-savings income cut by the amount Step 6 tax reduction of the basic rate, or Scottish basic rate, × the amount (s. 55A(2), s. 55B(1), (3)), capped at the Step 5 tax left after other reductions (s. 29(2)-(3), s. 27(2))
Eligibility tax band of the recipient (NONE/STARTER/BASIC/INTERMEDIATE) recipient not liable above the listed basic rates (s. 55B(2)(b), (ba)); transferor the same once their allowance is cut (s. 55C(1)(c), (ca)) and entitled to an allowance (s. 55C(1)(b)); no married couple's allowance claimed by either (s. 55B(2)(d))
Who claims anyone married with a partner who has unused allowance, both partners if both are below the allowance the couple elects only if it lowers their combined income tax, in whichever direction lowers it more. One election per couple: s. 55E limits each individual to one election and one reduction, and does not bar elections both ways (which could lower some couples' tax further); HMRC's guidance describes a single transfer from the lower earner
Take-up the recipient's would_claim_marriage_allowance the couple's election uses the gaining spouse's would_claim_marriage_allowance: one draw per couple, and the same draw that decided take-up before (the dataset draws it per person at 50%)

The rate test reads the model's own liabilities. Two points follow from the statute's wording:

  • Dividends: the dividend nil rate does not help. s. 55B(2)(ba) tests the dividend upper rate "if section 13A (dividend nil rate) were omitted".
  • Savings: the savings nil rate does help. s. 55B(2)(b) lists it, and nothing like (ba) applies to savings. (Compare s. 12B(8)(b)(ii), which does look through s. 12A.)

Scottish savings and dividends are banded on UK rates, so an intermediate-rate Scottish taxpayer with savings stays eligible. The old band test made them ineligible.

Variables

New, all Person, YEAR:

  • marriage_allowance_transferable_amount: s. 55B(4)-(5).
  • is_marriage_allowance_spouse: married and the claimant or partner (Replace generic child and adult flags with each programme's legal definitions #1896's is_claimant_or_partner), only where the benefit unit has two. Children are never spouses.
  • makes_marriage_allowance_election: the couple's choice; users can set it as an input.
  • marriage_allowance_relinquished: s. 55B(6).
  • marriage_allowance_appropriate_percentage: s. 55B(3).
  • marriage_allowance_tax_reduction_limit: s. 29(2)-(3).
  • marriage_allowance_tax_reduction: s. 55B(1). Added to income_tax_subtractions.

Changed:

  • marriage_allowance is now the amount received from the spouse's election, and only the gaining spouse has it. It no longer comes off taxable income: it was removed from earned_taxable_income_exclusions.
  • personal_allowance subtracts marriage_allowance_relinquished.
  • meets_marriage_allowance_income_conditions is the rate test above.

Removed:

  • partners_unused_personal_allowance: it is not part of the law and nothing else used it.
  • the eligible_bands parameter.

The CPS expanded-MA contrib reform now overrides meets_marriage_allowance_income_conditions and marriage_allowance_transferable_amount. Its mechanics follow the statute, so a higher-rate recipient saves the basic rate × the amount, not their marginal rate. It also lifts the income condition only when remove_income_condition is set; before, raising only ma_rate lifted it too.

How the election is computed

The election depends on the couple's tax with and without it, and the tax depends on the election. So makes_marriage_allowance_election runs two policyengine-core branches, the pattern PE-US uses for itemisation:

  1. No election in force.
  2. Every spouse's own allowance cut by the transferable amount, with no reductions granted.

For each spouse, the saving from electing is the partner's reduction (capped at their Step 5 tax left) minus the elector's extra tax. It is rounded to pennies. The couple elects if the saving is positive and the conditions hold. The spouse with the larger saving elects.

The inputs to income tax that don't depend on the election (employment income, adjusted net income, the other allowances, savings, dividend and property income, the Child Benefit charge, other tax credits) are resolved first and set as inputs in both branches. So is the property allowance route (uses_property_allowance, ITTOIA 2005 s. 783BL), which is settled before the election. Holding them fixed does three things:

  • the result doesn't depend on which variable a caller asks for first;
  • a choice made upstream in branches of its own (a reform's split of adjusted net income, a labour supply response, the property allowance route) isn't remade for the counterfactual;
  • on policyengine-core ≥3.33, where set_input on a branch drops values derived from the input it replaces, the branches don't recompute them.

Branches share the simulation's cached arrays until they write (core#556) and are dropped after use.

Tie-break: when both directions save the same, the spouse with the lower adjusted net income elects, then the elder, then the first listed.

Property allowance route: main now chooses each landlord's property allowance route (ITTOIA 2005 ss. 783BE-783BL) by comparing tax with and without the allowance. This PR makes that comparison weigh the benefit unit's income tax, not the landlord's alone. A route can change whether a spouse elects, and so the other spouse's tax. Example (2026): a spouse on £12,000 with £800 of lettings and £200 of finance costs, married to a basic-rate earner on £30,000. Full relief plus the election costs the couple £3,372. Electing out of the allowance for the finance-cost reduction costs the landlord £6 instead of £138, but stops the election, so the couple pays £3,492. The independent review found this; it is now a YAML case and an explicit example in both couple-tax properties, which fail with the old own-tax rule.

Pinned election: set as an input, an election is taken to be one the other spouse is entitled to gain from; the conditions are checked only when the model makes the choice. Only a spouse can give allowance up.

Universal Credit

uc_income_tax_on_earnings (#1949; on main it now calls policyengine_uk/utils/uc_income_tax.py) sets the Step 6 reductions in income_tax_subtractions against the tax on earnings first, except the property finance-cost reduction, which goes against tax on property income. This PR adds the Marriage Allowance reduction to that list, so the recipient's reduction comes off the tax on their earnings. That matches how HMRC delivers it: gov.uk's tax code letters say code M means "you've received a transfer of 10% of your partner's Personal Allowance". So a pensioner's State Pension no longer changes the earning partner's UC earned income once the couple has elected. The strict xfail test_state_pension_does_not_change_partners_marriage_allowance now passes.

Known interaction (intended): the election weighs the couple's income tax before the winter fuel payment charge, not their UC. At the point where electing stops saving tax, the couple stops electing. The earner's tax then rises by up to £252, and their UC rises by up to 55% of that. test_couple_stops_electing_when_it_no_longer_saves_tax pins an example: 2026, State Pension £12,500 → £12,600, UC £5,509.26 → £5,547.86. A choice rule that also weighed UC would behave differently; that is a separate modelling decision. The same holds for the winter fuel payment charge, which turns on UC and Pension Credit: test_electing_can_start_the_winter_fuel_payment_charge pins a 2026 couple aged 70 and 45 whose election saves £114 before the charge, ends their £1 of UC and so starts a £200 charge (income tax including it: £7,002.26 → £7,088.26).

Departures in the issue, each now a YAML test

Issue item Test (marriage_allowance.yaml) Old model Law = new model
Worked example (30,000 / 12,000) "Issue example" 3,372 / 0 3,234 / 138
1. Transferor with £15,000 / £12,000 of interest "Issue departure 1" (×2) recipient 3,486 / 3,372 3,234
2. Recipient with £12,570 earnings, £5,000 dividends "Issue departure 2" 483.75 231.75
3. Scottish intermediate-rate recipient "Issue departure 3" saves 264.60 saves 252 (3,199.07)
4. Rounding of the partial unused amount every case: the amount is always 1,260
5. Double counting when both are below the allowance "Issue departure 5" 1,260 each 0
6. Married couple's allowance not checked "Issue departure 6" MA given no election

Also covered:

  • the transferor just under / at the allowance (elect / not);
  • a higher-rate recipient;
  • take-up follows the gaining spouse;
  • direction regardless of age order;
  • the cap at Step 5 tax (86);
  • other tax reductions first (186);
  • a tie (the lower-income spouse elects, then the elder);
  • children in the unit;
  • the dividend nil-rate vs savings nil-rate straddles;
  • Scottish savings banded on UK rates;
  • the pensioner case from the UC test;
  • 2015-16 (£1,060) and 2019-20 (£1,250).

meets_marriage_allowance_income_conditions.yaml is rewritten on incomes, not tax-band inputs. It includes s. 55C(1)(c) with the cut allowance (50,000 earnings: eligible with the full allowance, ineligible once it falls to 11,310).

Invariants and property tests

test_marriage_allowance_properties.py (Hypothesis). It generates couples and singles with every taxable income type, in England, Wales and Scotland, with children and occasional married couple's allowance claims, for 2019, 2026 and 2027. "Income tax" in these is tax before the winter fuel payment charge, which is what the election weighs:

  1. The couple's income tax is never higher with the model's election than with none. When the couple elects, it is at least a penny lower.
  2. Differential against brute force: the model's couple tax equals the minimum over {no election, first spouse elects, second spouse elects}, each run with the election fixed and filtered by the statutory conditions computed from those runs.
  3. With the election fixed, the gaining partner's Marriage Allowance, reduction, allowance and income tax do not change when the elector's income of any type rises. The elector's allowance is exactly the uncut allowance less the transferable amount.
  4. Accounting:
    • only spouses elect, at most one per couple;
    • the amount given up equals the amount received in each couple, and is 0 or the transferable amount;
    • the reduction is at most the appropriate percentage × the amount and the Step 5 limit;
    • the gainer's tax falls by exactly the reduction;
    • the elector never pays less.
  5. The couple's income tax, with the model's election, never falls when either spouse gains income.
  6. No one outside a married couple elects, gives up or receives anything.
  7. The income conditions match the tax bands, including with pension relief and Gift Aid (test_income_conditions_match_the_tax_bands).

UC properties now run with Marriage Allowance claimed:

Before, these switched Marriage Allowance off "until this is fixed". Where a property compares two runs, each couple's election is held at its first-run choice, because the election legitimately responds to income.

Mutation check (YAML): ten mutants, each restoring one old behaviour (the unused-allowance cap, relief off taxable income instead of Step 6, no transferor cut, band-based eligibility, double counting, rounding of a partial amount, and four more), were all killed at 418a0b9b3 (evidence kept with the review files). They were not re-run after the 20 Oct merge; the mutated code paths are unchanged except the rate test, whose new behaviour the cases below pin.

Impact on the certified pair

Real Microsimulation runs on enhanced_frs_2024_25.h5 from policyengine-uk-data 1.58.1 (sha256 056e80fd8e91…, the dataset in policyengine.py's certified UK bundle). Base: main 76cfc9e79. Head: this PR at 10e6b338c (d75ab7ac3 changes only a test fixture). Four runs: base and head, each also with no one claiming (would_claim_marriage_allowance false for everyone; head no-claim at d75ab7ac3), 2025-26 to 2030-31. All trees clean. These figures predate the 28 Oct merge below; the re-run on the merged head is pending.

£m a year, head minus base, weighted. Households, benefit units and people are weighted counts; the dataset records behind them follow in brackets.

Year Income tax Household net income Universal Credit Pension Credit Housing Benefit Council Tax Reduction Government balance
2025-26 -37.6 +37.4 -0.2 -0.0 -0.0 +0.0 -37.4
2026-27 -45.4 +45.1 -0.3 -0.0 -0.0 -0.0 -45.1
2027-28 -75.0 +74.8 -0.3 +0.1 -0.0 +0.0 -74.8
2028-29 -75.4 +75.1 -0.3 +0.0 -0.0 -0.0 -75.1
2029-30 -73.5 +73.0 -0.3 -0.1 -0.0 +0.0 -73.0
2030-31 -68.1 +67.5 -0.3 -0.1 -0.1 -0.0 -67.5
Year Marriage Allowance cost, main Marriage Allowance cost, this PR Couples electing Reductions granted (£m) Gaining spouses with the full reduction People with Marriage Allowance on main
2025-26 334.4 372.0 1.71m (3,480 records) 426.4 1.67m (3,284 records) of 1.71m (3,480 records) 2.47m (5,179 records)
2026-27 308.9 354.3 1.61m (3,355 records) 397.7 1.56m (3,119 records) of 1.61m (3,355 records) 2.18m (4,789 records)
2027-28 273.1 348.1 1.54m (3,239 records) 380.6 1.48m (2,977 records) of 1.54m (3,239 records) 1.99m (4,538 records)
2028-29 271.0 346.4 1.59m (3,159 records) 380.1 1.47m (2,924 records) of 1.59m (3,159 records) 1.90m (4,452 records)
2029-30 268.4 341.9 1.56m (3,059 records) 375.8 1.44m (2,856 records) of 1.56m (3,059 records) 1.87m (4,286 records)
2030-31 266.4 334.5 1.51m (2,945 records) 370.5 1.40m (2,795 records) of 1.51m (2,945 records) 1.78m (4,121 records)

"Marriage Allowance cost" is income tax with no one claiming (would_claim_marriage_allowance false for everyone) minus income tax as modelled, in the same model.

Year People whose income tax changes People whose tax moves between partners, couple unchanged Households gaining > £1 Households losing > £1 Benefit units whose UC changes
2025-26 1.01m (1,680 records) (0 records) 0.40m (571 records) 0.19m (240 records) 18.37k (22 records)
2026-27 0.87m (1,473 records) (2 records) 0.41m (513 records) 0.14m (233 records) 13.56k (21 records)
2027-28 0.82m (1,410 records) (0 records) 0.43m (485 records) 0.10m (232 records) 12.95k (20 records)
2028-29 0.86m (1,380 records) 0.00m (4 records) 0.43m (469 records) 0.14m (213 records) 14.09k (24 records)
2029-30 0.96m (1,293 records) (0 records) 0.39m (420 records) 0.13m (215 records) 11.66k (26 records)
2030-31 0.87m (1,203 records) (0 records) 0.40m (416 records) 0.10m (196 records) 10.68k (20 records)

Decomposition, 2026-27 (income tax change, head minus base; couple counts are weighted couples, and the records behind them are spouses, two per couple):

  • Electing spouses' tax: +43.4m; gaining spouses' tax: -89.0m.
  • Couples that elect now but had no Marriage Allowance on main: 0.21m (518 spouse records), tax -27.1m.
  • Couples with Marriage Allowance on main that do not elect now: 0.63m (2,548 spouse records), tax +0.2m. On main, 0.29m (838 records) people had it on both partners (the double counting).
  • Couples with Marriage Allowance on main and an election now: 1.40m (6,192 spouse records), tax -18.5m.
  • Scottish gaining spouses: 0.18m (397 records), tax -7.9m.

Poverty rates, percentage points, head minus base:

Year BHC all BHC children BHC working age BHC pensioners AHC all AHC children AHC working age AHC pensioners
2025-26 – – – – – – – –
2026-27 +0.000 +0.000 +0.000 +0.000 +0.000 +0.000 +0.000 +0.000
2027-28 +0.000 +0.000 +0.000 +0.000 +0.000 +0.000 +0.000 +0.000
2028-29 +0.000 +0.000 +0.000 +0.000 +0.000 +0.000 +0.000 +0.000
2029-30 – – – – – – – –
2030-31 +0.000 +0.000 +0.000 +0.000 +0.000 +0.000 +0.000 +0.000

2026-27 levels (base → head): BHC all 14.94% → 14.94%; BHC children 20.35% → 20.35%; BHC working age 13.51% → 13.51%; BHC pensioners 13.11% → 13.11%; AHC all 20.37% → 20.37%; AHC children 29.26% → 29.26%; AHC working age 19.44% → 19.44%; AHC pensioners 12.26% → 12.26%.

Household net income by decile of base household income, 2026-27:

Decile Change (£m) Mean change (£ per household) Households changed
1 +0.5 +0.14 6.99k (55 records)
2 +0.5 +0.15 32.25k (140 records)
3 +1.3 +0.43 93.44k (199 records)
4 +1.8 +0.61 132.82k (160 records)
5 +2.5 +0.85 43.75k (100 records)
6 +17.8 +5.39 105.66k (104 records)
7 +4.7 +1.54 44.97k (80 records)
8 +0.7 +0.24 6.82k (15 records)
9 +15.2 +5.19 127.66k (14 records)
10 +0.0 +0.00 0.30k (4 records)

Runtime and memory (one process per run, same dataset bytes):

Run Load (s) 2025 2026 2027 2028 2029 2030 Total (s) RSS after each year (GB) Peak RSS (GB) Branches left
base 7.9 2.1 1.9 2.1 2.1 2.0 2.2 24.3 2.804, 3.019, 3.181, 3.425, 3.708, 3.957 3.957 none
head 11.0 2.6 2.4 2.4 2.5 2.5 4.1 36.0 2.99, 3.224, 3.624, 3.911, 3.957, 4.306 4.306 none
base_noma 9.1 2.3 2.1 2.7 2.3 2.5 2.3 29.2 2.687, 2.837, 3.061, 3.286, 2.439, 2.38 3.286 none
head_noma 6.5 1.5 1.7 1.5 1.5 1.6 1.5 18.6 2.96, 3.213, 3.478, 3.776, 3.92, 4.303 4.303 none

Poverty changes for 2025-26 and 2029-30 rest on fewer than three households and are suppressed (–); every other year is unchanged to 0.001pp. Cells resting on fewer than three dataset records are shown as counts only.

What moves:

  • Income tax falls £37.6m in 2025-26, £45.4m in 2026-27 and £68-75m a year from 2027-28. The Marriage Allowance costs £354m in 2026-27 against £309m on main, measured each way as income tax with no one claiming minus income tax as modelled.
  • More couples gain the reduction. 0.21m couples elect now that had no Marriage Allowance on main, which gave it only where the transferor had unused allowance; they pay £27.1m less.
  • Double counting goes: on main 0.29m people had Marriage Allowance on both partners.
  • Tax moves between partners. Electing spouses pay £43.4m more and gaining spouses £89.0m less in 2026-27, as s.55B(6) cuts the transferor's allowance.
  • UC changes for 11-18k benefit units (20-26 records), net −£0.2m to −£0.3m a year, through the tax on earnings that UC deducts, which the reduction lowers.
  • Runtime: the election's branches add about 0.3-1.9 s a year to a full run, and peak memory is 4.3 GB against 4.0 GB.

The 3 Oct figures this body used to carry were on a different dataset (uk-data private copy e433e532…) and an older main; they are replaced.

Not modelled, and limits inherited from the model

  • Not modelled:

    • residence (s. 55B(2)(c), s. 55C(1)(d), (2)-(4)): the model assumes UK residents;
    • death during the year (s. 55B(7));
    • backdating and continuity of elections (s. 55D);
    • marriages for part of the year;
    • spouses outside the benefit unit (s. 55C(1)(a) needs no cohabitation, but the absent spouse is rarely in the survey).
  • The election is judged on the model's own income tax, so it inherits these pre-existing departures, each filed as a follow-up (all rechecked on 2026-10-12 main):

    • Allowance order: allowances go against savings before dividends rather than in the order that most reduces tax (s. 25(2)).
    • 2015-16 basic rate limit is £32,000 in the parameter; Finance Act 2014 s. 2 set it at £31,785.

    Main has since fixed six limits this PR first listed:

    • savings and dividends now band above property income;
    • a Scottish taxpayer's personal savings allowance is set on UK bands;
    • Gift Aid extends the rate limits instead of coming off income (Give Gift Aid relief by raising the rate limits, not by deducting the donation #2271);
    • the 2016-17 allowance is £11,000, so that year's transferable amount is £1,100;
    • the savings starting-rate band tapers on non-savings taxable income (35f992c6a). The model no longer elects for a transferor with £12,500 of earnings and £5,500 of interest married to a £30,000 earner: forcing the election costs the couple £124 (£3,610 against £3,486, checked at fae3baaea);
    • the dividend rate thresholds equal the basic rate limit from 2016-17 (checked at fae3baaea).

    The remaining two were rechecked on 12 Oct main, not since.

  • One election per couple. s. 55E limits each individual to one election and one reduction but does not bar elections both ways. Both ways lowers tax where giving up allowance costs less than the basic rate. For example, two spouses each with £20,000 of dividends (2026) pay £1,489.95 with no election, £1,373.40 with the model's single election, and £1,256.85 with both electing. The model allows one election per couple, the single transfer from the lower earner that HMRC's guidance describes.

  • Double taxation relief should come after the Marriage Allowance (s. 27(6)), but other_tax_credits lumps it with other reductions. Total liability is unaffected; only the split of the reduction is.

  • SPI dataset: policyengine-uk-data's SPI build supplies marriage_allowance for MAIND == 1. SPI: keep Marriage Allowance on recipients (MAIND) and give transferors their cut allowance policyengine-uk-data#502 remains a draft intended to keep that recipient input and supply the transferor allowance cut. It depends on this PR and the separate batched data release (d833); this continuation does not establish approval, data landing or consumer adoption.

The 20 Oct merge of main

Merging main 76cfc9e79 conflicted only in the import block of reforms/cps/marriage_tax_reforms.py (both sides kept). Main had since reworked the income tax pipeline, and one change broke this PR's rate test. Adjusted net income now deducts the pension relief that allowances also deduct, and Gift Aid now extends the rate limits instead of coming off income (#2271). The rate test took allowances off adjusted net income. So it deducted pension relief twice, which let a higher-rate pension saver pass s.55B(2)(b), and it took the gift off income below dividends instead of extending the basic rate limit, which failed a donor with dividends under s.55B(2)(ba).

10e6b338c makes the rate test read non_savings_taxable_income, the base that savings and dividend tax use, and extend the non-savings and dividend limits by the grossed-up gift (ITA 2007 s.414(2)(b)), as earned_income_tax does. The election's branches also hold net_income, so they don't rebuild it from the held adjusted net income. Main's uc_income_tax_on_earnings now lives in utils/uc_income_tax.py, and it already subtracts every Step 6 reduction in income_tax_subtractions from the tax on earnings first, so this PR's own UC edit fell away in the merge. The commit also removes the #1947 xfail entry from .github/test_outcome_allowlist.toml, which the regression checker requires once the xfail is gone.

Tests after the merge:

  • YAML at 829fccae4 (the merge, before the fix): 530 passed. The new rate-test cases fail there (4 failed, 3 passed) and pass at 10e6b338c.
  • Marriage Allowance YAML at 10e6b338c: 64 passed.
  • Fail on main (76cfc9e79, only test files copied): the Marriage Allowance YAML gives 10 failed and 4 passed; the de-xfailed test_state_pension_does_not_change_partners_marriage_allowance fails (UC earned income £18,033.60 against £18,171.60 in law).
  • The independent review re-ran the five changed YAML files at the head (97 passed) and the head's YAML on the base (18 failed, 23 passed). It also checked query-order equality and baseline isolation under a £15,000 personal allowance reform.

Overlaps

Axiom

axiom: TheAxiomFoundation/rulespec-uk#362 + TheAxiomFoundation/rulespec-uk#437 queued

  • rulespec-uk#362 (open, pe-parity): s.55C, extended on 2026-09-30 for this PR with s.55C(1)(ca) and s.55B(2)(ba) and seven companion cases.
  • rulespec-uk#437 (open, pe-parity): the s.55B(2)(ba) dividend nil-rate look-through for the s.55B re-encode.
  • The s.55B re-encode itself is queued in rulespec-uk bulk/worklist.yaml (batch PE-UK-CHILD-DEFS, queued by rulespec-uk#354, closed 2026-09-30). It already requires the fixed transferable amount, the s.55B(6) cut and the s.55B(1) reduction.
  • Not verified: that each issue is dispatch-ready as written, or any encoded parity. Nothing is encoded yet.

rulespec-uk's s.55B module still caps the transfer at the partner's unused allowance: it encodes the same departure PolicyEngine had. The worklist entry and #362 both called the PolicyEngine side "a separate follow-up"; this is it.

Review

  • Two earlier GPT-6.1 Sol reviews (of dafe6301c on 12 Oct, then of 99182e396) were cut off before they reported. The first found the property-route interaction above and six smaller points, all addressed.
  • GPT-6.1 Sol reviewed 10e6b338c on 21 Oct: REQUEST_CHANGES on one P2, a test-fixture defect, not a model defect. The property fixture left is_claimant_or_partner to inference, which reads a married couple aged 18 and 100 as parent and child. d75ab7ac3 sets the roles and pins that couple as an example; it fails without the roles and passes with them. The review found the statutory mechanics, the election engineering and the merge fix sound. It labelled two rules as modelling choices: one election per couple, and an election that weighs income tax only, not UC.
  • The delta review of d75ab7ac3 (21 Oct) was served by Opus 5.5, not Sol (the Codex lanes were closed): APPROVE, with five P3s on this body. They are fixed here: uk-data#502 is a draft, the Axiom line cites Remove benunit- and household-level saved random numbers #437 and Validate against HMRC 'ready runner' #354, decomposition counts are spouse records, property 7 is listed, and the head no-claim run is pinned.
  • GPT-6.1 Sol reviewed the 28 Oct merge at 8117a4462: REQUEST_CHANGES on three P2s and two P3s, all in tests and this body (see "The 28 October review" below). It found the merge itself sound: the before-charge objective is labelled, the private branches hold the inputs the election does not change, and the guard, fiscal partition and workflow equal main's. 21977b274 and fae3baaea address all five.
  • GPT-6.1 Sol reviewed fae3baaea on 28 Oct: APPROVE, all five fixed, with two P3s. One, a sub-penny gap in the conditions property, is fixed in 5cedfaf9f. The other, on this body's CI paragraph, is corrected in "CI and memory" below.
  • 69c919f60 frees the memory the election's branches held (see "CI and memory"). It needs its own review before this PR leaves draft.

Reproducer: policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/income/uc_earnings_deductions.yaml, policyengine_uk/tests/policy/baseline/gov/hmrc/income_tax/allowances/marriage_allowance.yaml, policyengine_uk/tests/policy/baseline/gov/hmrc/income_tax/allowances/meets_marriage_allowance_income_conditions.yaml, policyengine_uk/tests/policy/baseline/gov/hmrc/income_tax/property_income_band_base.yaml::Case 7, Marriage Allowance leaves the band base alone (#1947)., policyengine_uk/tests/policy/reforms/cps/marriage_tax_reforms.yaml::Expanded MA — rate-only expansion keeps the higher-rate bar, policyengine_uk/tests/test_marriage_allowance_election_branches.py::test_results_do_not_depend_on_query_order

22 October continuation

The continuation at 00bf62a81008d3439ae8a43e1c4cf32412ab4ff8 preserves the original PR ancestry and the pinned main integration. Private election counterfactuals discard an inherited final-income-tax input while the real simulation retains explicit inputs. Fiscal declarations now document the CGT-to-income-tax eligibility read. BASIC and NEW State Pension calculations promote both operands to float64 before division to preserve finite tiny-cap inputs. The Pension Credit Class 2 regression explicitly isolates a married couple with neither partner claiming Marriage Allowance. The original one-election-per-couple and income-tax-only objective remain.

Validation actually ran 807 selected Python IDs in the last continuation: 806 passed and one credential-dependent test skipped. Eleven actual affected State Pension YAML calls passed. The unmodified strict checker exited 1 solely for the unchanged Darwin/glibc malloc_trim skip; no local strict pass is claimed. Formatting changed no Python AST, and runtime stat/link witnesses remained unchanged.

The accounting ledger combines those 807 IDs with 7,434 qualified retained outcomes, covering 8,241 unique IDs (8,137 passed and 104 skipped). Retained per-node evidence keeps its original source/runtime/impact qualifications. The original full and earlier bounded runs timed out; their raw outcomes remain preserved. These overlapping batches are not added or represented as whole-head execution.

Independent code and source review of committed head 00bf62a81008d3439ae8a43e1c4cf32412ab4ff8 completed with no open findings or material coverage gaps. The fix verifier reported CLEAN; C1, C2 and C3 remain recorded as resolved. The source review validated 30 distinct cached official originals (83 hash records); it did not rerun model tests or establish encoded parity.

The historical population, mutation and author-review figures above retain their dated pins. This continuation did not rerun the private population comparison, establish encoded parity, or certify deployed/owner acceptance. Axiom follow-up, rulespec-uk #362/worklist debt, data #502/d833 release and consumer rebind/adoption, and #2545/#2553 coordination remain open. Actual resulting-head Linux CI, READY, post-ready Fix and the correct Vahid review request are later gates.

CI investigation update — 23 October 2026

Commit c4cfc9489becb74c616e1f988fef4377793a8b46 adds a bounded accounting-guard failure message that includes only trusted public identifiers and privacy-suppressed aggregate counts. The assertion, tax calculations, guard rules and tolerances remain unchanged. Both source and wheel test jobs hit their existing one-hour limit on the preceding head; the shared matrix now uses eight shards with the same four workers, strict outcome audit and time limit. This adds runner capacity; it is not a demonstrated runtime or compute saving.

The meaningful regression reproduced the original failure message exposing a synthetic private-field sentinel. The other new API cases failed before the helper existed. After implementation, all 123 selected Python cases and the strict selected-outcome checker passed. These cases include the 17 earlier RED/control cases; counts are not added. A formatter-only proof preserves the tested Python semantics. Independent code and privacy/source review of this committed update completed with no open findings or material gaps, and C4/C5 verification is CLEAN.

Focused regression checks:

  • policyengine_uk/tests/aggregate_impact/test_aggregate_impact_measure.py::test_certified_guard_failure_message_excludes_private_fields
  • policyengine_uk/tests/code_health/test_ci_test_shards.py::test_configured_source_and_wheel_shards_account_for_every_node

The original runtime stat/link guard stopped on generated NumPy caches. Its failed result is preserved. An independent, bounded source/header/code/installed-RECORD check qualified exactly 82 new bytecode caches, seven cache directories and seven parent-directory metadata changes; it did not establish the creator PID or whole-runtime byte identity. No tests were replayed to dismiss that guard.

The certified per-household accounting failure on 00bf62a8 remains causally unresolved. The public synthetic cases did not reproduce it. This diagnostic update does not fix or waive that invariant, and this PR remains draft until actual resulting-head Linux CI and the accounting gate pass. No full suite, certified/private dataset or encoded-parity run was performed locally for this update. All earlier policy, source, Axiom and adoption qualifications remain.

The 28 October merge of main

1cd797715 merges main 41fa5ddb5 into c4cfc9489. Five files conflicted:

  • .github/workflows/test-suite.yaml, docs/engineering/skills/testing.md: main's six shards. The 23 Oct eight-shard change is dropped: main has re-sharded since, and the workflow is not this PR's to change. The 23 Oct shard-accounting test reads the shard count from the workflow, so it stays.
  • policyengine_uk/data/fiscal_partition.yaml: main's file. The 22 Oct declaration that income tax reads capital gains tax is dropped (see "The accounting failure" below).
  • uses_property_allowance.py: main now weighs each landlord's route on income tax including the winter fuel payment charge, with ceiling bounds on the charge. The merge keeps that and, as this PR did before, sums both routes' tax over the benefit unit, because the route can move the spouse's tax through the election.
  • test_property_income_properties.py: main's pensioner property test, asserting the benefit unit's tax instead of the landlord's own, and this PR's couple test.

The election compares tax before the winter fuel payment charge. On main, income_tax now adds the charge (winter_fuel_payment_charge, ITEPA 2003 s.681I), and the charge reads Pension Credit and UC. Left comparing income_tax, the election's branches would compute benefits for every couple. They compare income_tax_before_winter_fuel_payment_charge instead:

  • The charge falls on a person whose own total income (ITA 2007 s.23 Step 1) is over £35,000, and the election does not change total income.
  • So the charge can differ between the two positions only through the benefits that the charge and the payment read.
  • The election already does not weigh effects on benefits (see the UC interaction above).

This is a modelling choice, labelled as one. test_final_tax_input_does_not_change_the_election_or_capital_gains_tax now runs for both income_tax and the before-charge tax.

The accounting failure. On 00bf62a8 and c4cfc948, CI failed test_every_tax_component_is_counted_exactly_once: perturbing income_tax was reported to move capital_gains_tax one for one in 89 households.

  • I reproduced it on a subset of the certified dataset holding those households: 89 of 89.
  • Recording capital_gains_tax at every guard step showed it moved in no household at any step.
  • With main's partition (no declared read), the guard passes on the same subset.
  • The flag came from the guard's reader tolerance, which then scaled with the head's level. Main has since scaled that tolerance by the perturbation instead (76108fafe), and guard.py here is main's.

The 22 Oct private-branch delete_arrays change, the float64 State Pension change, the Pension Credit Class 2 fixture and the 23 Oct diagnostics in measure.py are kept.

Main's new household presumption. 613326be1 presumes a member 20 or more years younger than the claimant is their child, at any age. The YAML case "Pensioner transferor with State Pension" is a couple aged 70 and 45, so it now supplies is_claimant_or_partner for both (8117a4462). Without the roles, the case fails on the merge (no election); with them, it passes.

Tests at 8117a4462:

  • policyengine-core test on tests/policy/baseline/gov/hmrc/income_tax and tests/policy/reforms/cps: 460 passed.
  • The Marriage Allowance, rate-test, property band base, UC earnings deductions and Pension Credit earnings disregard YAML files: 202 passed. At 1cd797715, before the role fix, the 70/45 case was the one failure.
  • Python: test_marriage_allowance_properties.py, test_marriage_allowance_election_branches.py, test_property_income_properties.py, test_uc_state_pension_properties.py, test_state_pension_components.py, code_health/test_ci_test_shards.py and aggregate_impact/test_aggregate_impact_measure.py, with -n 3: 186 passed in 13 min 27 s. The run started at 1cd797715; 8117a4462 changes only a YAML case and a comment.

Impact. The certified-pair figures above are at 10e6b338c on main 76cfc9e79. The re-run on the merged head is held by the host's Budget-day reservation until 29 Oct 07:00Z, and this body will carry it before the PR leaves draft.

The 28 October review

GPT-6.1 Sol's findings at 8117a4462, and what 21977b274 does about each. Only tests, one comment and this body change; the model code is as at 8117a4462. fae3baaea then fixes one more test, which CI found, and 5cedfaf9f the round-4 review's P3 on the conditions property (see "CI and memory").

  • P2: main's CPS switch tests (Stop the statutory Marriage Allowance rate from switching on the CPS expanded Marriage Allowance #2553) asserted the old mechanics. This PR gives the Marriage Allowance as a Step 6 reduction at the basic rate (ITA 2007 s. 55B(1), (3)), so the higher-rate earner the expanded reform lets in gains 20%, not 40%. In test_marriage_allowance_cps_switch.py:
    • With remove_income_condition, the £60,000 earner's tax is 11,432 − 1,260 × 20% = £11,180, not £10,928.
    • A CPS rate alone keeps the income condition (only remove_income_condition lifts it), so the higher-rate earner gets nothing.
    • The removed eligible_bands parameter is no longer read; the conditions test names the electing bands itself.
    • The old test that the allowance never falls as the statutory rate rises was false. A couple on £20,000 and £12,000 transfers £1,260 at 10% but stops electing at 100%: giving up all £12,570 would cost the elector £2,400 for a reduction capped at the partner's £1,486 of tax. The test now checks that the transferable amount never falls and that the allowance is all of it or none; test_a_couple_can_stop_electing_as_the_statutory_rate_rises pins that couple as intended.
  • P2: the Pension Credit earnings oracle ignored the transferor's cut allowance. test_pension_credit_earnings_disregard_properties.py computed the notional tax deduction on a full £12,570 allowance. It now uses the allowance after marriage_allowance_relinquished, as the model does (s. 55B(6)). The reviewer's example, a Scottish couple each with £15,000 of self-employment income, does not elect: their taxable profit is above the £2,306 starter band either way, so the £1,260 given up costs 20%, the same as the reduction, and electing saves nothing. The test pins a couple that does elect: an English couple aged 67 with £12,000 and £15,000 of self-employment income in 2024. Deductions are £138 and £234; the old oracle expected £0 for the transferor.
  • P2: the statutory paternity pay property assumed an unchanged election. A recipient whose pay rises from £12,000 to £12,570 can stop electing, and their own tax then falls from £138 to £0 while the couple's rises from £2,172 to £2,286. The property now requires the recipient's tax not to fall only while the election is the same, and test_a_couple_can_stop_electing_as_statutory_paternity_pay_rises pins that couple.
  • P3: the Marriage Allowance properties still asserted that the election minimises income tax including the winter fuel payment charge. They now use tax before the charge, which is what the election weighs. test_electing_can_start_the_winter_fuel_payment_charge pins the reviewer's couple: electing saves £114 before the charge, ends £1 of UC and starts a £200 charge.
  • P3: this body listed two limits main has since fixed. The savings starting-rate and dividend upper-rate limits are moved to the list of fixed limits, each rechecked at fae3baaea.

A further finding while fixing the first P2: tax_band places taxable income exactly at a band's lower limit in that band, though none of it is charged at that rate (s. 10). At £50,270 of earnings (£37,700 of taxable income, all at the basic rate) it reports HIGHER, and the model correctly lets the earner gain. test_taxable_income_at_the_basic_rate_limit_still_meets_the_conditions pins £50,270 and £50,271. At 21977b274 the conditions property judged bands a penny lower; the round-4 review found that this leaves income under a penny over the limit unchecked, and 5cedfaf9f replaces it (see "CI and memory").

Tests:

  • At 21977b274, the four changed test files (test_marriage_allowance_cps_switch.py, test_marriage_allowance_properties.py, test_pension_credit_earnings_disregard_properties.py, test_statutory_paternity_pay_income_tax_properties.py), with -n 3: 71 passed in 12 min 15 s.
  • At 21977b274, the other 15 Python test files that compute the Marriage Allowance (benefit cap, dated reform routes, deterministic variables, Pension Credit Class 2, reform representations, trading losses and the UC property files): all passed. That run also held the pre-fix versions of three of the changed files; its 15 failures were all in the old CPS switch file, which the 71 above replace (324 tests, 1 h 7 min).
  • At fae3baaea, test_aggregate_impact_childcare_provenance.py: 42 passed. The rest of policyengine_uk/tests/aggregate_impact except the certified-dataset test (which runs in its own CI job), with -n 3: 321 passed.
  • No YAML or model code changed since 8117a4462, whose YAML results above stand. Ruff format and lint pass on the changed files.

CI and memory

Runners lost to memory. Every head since the 28 Oct merge lost test shards to memory within seven minutes: GitHub reported "The runner has received a shutdown signal" as an error, and each shard's last once-a-minute reading was 14.6 to 15.8 GB used of 16 GB.

  • 8117a4462 (run 40754493119, release run 40754493529): test shards 3, 4 and 5 and release-test shards 4 and 5. Shard 4 recorded passes before it stopped. Test shards 1, 2 and 6 and release-test shards 3 and 6 ran to the end. Release-test shard 2 finished its tests (1,722 passed; four failed: three CPS switch cases and the provenance test below) before the run was cancelled, and release-test shard 1 was cancelled before it finished.
  • fae3baaea (run 40771609348): test shards 3 and 5. Shards 1, 4 and 6 and the YAML tests passed; shard 2 was cancelled when 5cedfaf9f was pushed.
  • 5cedfaf9f (run 40781132542, release run 40781132665): test shards 3 and 5 and release-test shards 3 and 4.

Main's runs pass close to that limit. On two passing runs on 28 Oct (the merge queue's run 40775419493 and savings-income-uprating-obr's run 40771218539), shard 3 read 15.6 and 15.4 GB used in its third minute. When 5cedfaf9f's shard 3 stopped, one worker was running microsimulation/test_reform_impacts.py, which calculates a reform and its baseline on the full dataset.

Correction. Earlier versions of this section said main and two other PRs' runs lost shards the same way. They did not. Run 40393666784 is PR #2661's, not main's. The runs on fix/2541-ca-earnings-pence and obr-october-2026-efo-2711 lost no runner: a search of their logs for the message had matched the workflow's own comment, which quotes it.

The cause: the election's branches outlived the election. policyengine-core records each new branch in every calculation then under way (get_branch adds it to each open frame's created_branches), and those frames keep it until they finish. So the two branches behind each election lasted until the calculation that asked for the election (household net income, say) ended, though tax_position had already removed them from simulation.branches. They held their copies of the simulation's arrays and their own results. 69c919f60 clears their storage when tax_position drops them (release in makes_marriage_allowance_election.py). It clears the storage directly: delete_arrays and drop_computed_arrays would record a change in each of those frames, though nothing reads the branch again.

Measured with tracemalloc on a sample of the certified dataset (every 26th household: 2,033 households, 4,346 people), calculating 2026 household net income in a fresh simulation:

Code Peak Held afterwards
main 41fa5ddb5 48.4 MB 30.7 MB
fae3baaea (the model code of 5cedfaf9f) 77.1 MB 30.8 MB
69c919f60 52.0 MB 30.8 MB

"Held afterwards" is after gc.collect(). A gc.collect() after each branch was dropped, without clearing it, left the peak at 77.2 MB, because the branches were still referenced, not garbage. On that sample, household net income, income tax, the election, the Marriage Allowance, UC, Pension Credit and council tax reduction for 2025, 2026 and 2029 are bit-for-bit the same at fae3baaea and 69c919f60. test_the_election_releases_its_branches checks that the election's two branches hold nothing once it is made. It fails at 5cedfaf9f and passes at 69c919f60.

CI on 69c919f60 (run 40784891433, release run 40784891458) is running. Four minutes in, past the point where shards 3 and 5 had stopped on every earlier head, all shards were still running. This body will carry the result.

Other failures on 8117a4462. The finished shards' failures were the CPS switch cases fixed above, and one more in shard 2:

  • test_partial_setter_failure_restores_only_touched_input (test_aggregate_impact_childcare_provenance.py, unchanged from main) hit Python's recursion limit. The test replaced child.set_input with a plain closure. Simulation.clone copies instance attributes and rebinds only bound methods, so each branch the election made from the child kept the closure and wrote its inputs to the child. The branch never held its election, and the election's formula branched again until the stack ran out. No model code puts a closure on a simulation's set_input. fae3baaea binds the test's setter to the child, so clone rebinds it to each branch; only the child's own write fails, as the test intends. It fails at 21977b274 and passes at fae3baaea.

The round-4 P3 on the conditions property. At fae3baaea, the conditions property in test_marriage_allowance_cps_switch.py judged tax_band a penny lower. A London earner on £50,270.005 has under half a penny of taxable income over the basic rate limit, but the penny-lower run reported BASIC, so a £0.01 tolerance in the income conditions could have passed. 5cedfaf9f compares each person's taxable income (earnings less personal allowance) with the basic rate limit itself (£37,700 in 2025-26, or £31,092, where the Scottish higher rate starts), and requires that everyone at or under it meets the conditions. It pins the £50,270.005 earner as an example and in test_taxable_income_at_the_basic_rate_limit_still_meets_the_conditions. A copy of the model with a £0.01 tolerance fails both tests.

Tests:

  • At 69c919f60, test_marriage_allowance_election_branches.py -k "releases or no_branch": 2 passed. With 5cedfaf9f's model code and the new test file, test_the_election_releases_its_branches fails (the branches still hold their populations' values) and the other passes.
  • At 5cedfaf9f's tests with 69c919f60's model code, the 22 Python test files that compute the Marriage Allowance and test_aggregate_impact_childcare_provenance.py, with -n 3, and the 12 YAML files that test it: running; this body will carry the result.
  • Ruff format passes on the two changed files.

🤖 Generated with Claude Code

MaxGhenis and others added 7 commits September 30, 2026 16:25
The transferor's election cuts their own personal allowance by the fixed
transferable amount (s.55B(4)-(6)); the gaining party gets a Step 6 tax
reduction at the basic or Scottish basic rate, capped at their Step 5 tax
(s.55A(2), s.55B(1), (3), s.29(2)). The couple elects only when the
conditions in s.55B(2) and s.55C(1)(b)-(c) hold, no married couple's
allowance is claimed, it lowers their combined income tax, and the elder
spouse's take-up draw is true.

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

YAML cases from ITA 2007 ss.55A-55E, hand-computed, including the issue's
worked example and each departure it lists. The State Pension and
earnings-deduction UC properties now run with Marriage Allowance claimed,
holding each couple's election at its first-run choice; the strict xfail on
the partner's Marriage Allowance now passes, and a new case pins the point
where the couple stops electing.

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

The couple's tax never rises with the model's election, which matches the
cheapest allowed option found by running each election fixed; the gaining
partner's reduction does not depend on the elector's income; transfers
balance and reductions stay within s.55B(1)/(3) and s.29(2); couple tax never
falls with more income.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The dataset draws would_claim_marriage_allowance independently per person at
50%. Reading the elder spouse's draw reshuffled take-up across about 0.65m
couples on the Enhanced FRS for no legal reason. The gaining spouse's draw is
the one that decided take-up before, so the same couples claim. Also add the
changelog fragment.

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

s.55B(2)(ba) asks whether dividends would reach the dividend upper rate,
which starts at the basic rate limit (s.13); the dividends scale's own
threshold lags it before 2026-27. State the married premise in the UC
property tests, and correct the comments on why the couples do not elect.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The tie case no longer asserts income tax, which depended on the model's
fixed allowance order rather than s.25(2). The married couple's allowance
case uses spouses old enough to claim it. The ordering case cites s.27(2)
and s.29(2)-(3); the limit notes s.27(6) for double taxation relief; the
single election per couple is described as a modelling choice, since s.55E
does not bar elections both ways.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e rate test

s.16 puts dividends above property income as well as earnings, so a landlord
whose dividends cross the basic rate limit is liable at the dividend upper
rate (s.55B(2)(ba)). Note that a spouse outside the benefit unit is not
modelled.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Resolve the election-independent income tax inputs before branching, so a
reform that branches inside adjusted_net_income gives the same result
whichever variable is asked for first, and free the branch arrays
afterwards. Only a spouse can give allowance up, and spouses must be old
enough to marry (16, the Scottish minimum). On a tie the lower-income spouse
elects. The CPS expanded reform lifts the income condition only when
remove_income_condition is set. Refresh stale comments.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis added a commit to PolicyEngine/policyengine-uk-data that referenced this pull request Oct 1, 2026
…riage Allowance

The locked policyengine-uk predates PolicyEngine/policyengine-uk#1963. A lock
bump changes the dataset build, so it belongs in its own release. The test
runs as soon as the installed model defines marriage_allowance_relinquished.

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

Copy link
Copy Markdown
Collaborator Author

Heads-up from #1949, which this stacks on: its head moved from d0fc22f to 514132f with one added commit and no history rewrite. The commit changes two lines in policyengine_uk/tests/test_uc_state_pension_properties.py (#MA_ISSUE → #1947 in the docstring and in the strict xfail's reason) and one sentence in the UC docs page. No formula changed. A rebase onto 514132f should conflict only where this PR removes that xfail marker. Further review fixes to #1949, if any, will also arrive as added commits.

@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

Update from #1949: its head is now c98c53c, a merge of #1943's new head ce85220, which has main 7b9fc37 merged in. No history rewrite and no formula change. Rebasing or merging onto c98c53c brings in current main, so this PR's impact base moves too. The one conflict #1949 had was the UC docs means-test sentence.

MaxGhenis and others added 2 commits October 3, 2026 17:21
…ce-election

Take #1949's uc_income_tax_on_earnings, which now sets every Step 6
reduction (income_tax_subtractions, which this branch extends with the
Marriage Allowance reduction) against the tax on earnings first. Identify
spouses as the married claimant and partner (#1896), dropping the
two-eldest rule and the minimum-age parameter. Keep #1896's Marriage
Allowance and CPS tests, adapted to the transfer model; carry the pinned
elections into #1949's married couple's allowance and property-income
properties. Drop the branch-array clearing now that core shares branch
arrays until they are written.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Hold each couple's Marriage Allowance election at the base run's choice in
the UC capital-income properties too, in place of switching Marriage
Allowance off.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis and others added 8 commits October 11, 2026 12:32
Conflicts:
- income_tax_subtractions.yaml: keep main's property_finance_cost_relief
  and this branch's marriage_allowance_tax_reduction.
- test_uc_earnings_deductions_properties.py: keep this branch's election
  holding for invariants 1-3; take main's pre-#1942 formula (main removed
  uc_mif_capped_earned_income) and its start-up period for invariant 4.
- test_uc_income_from_capital_properties.py, test_uc_state_pension_properties.py:
  keep this branch's election holding in place of main's
  marriage_allowance=False switches; take main's comments on the
  claimant-or-partner presumption.

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

Main added property_finance_cost_relief as a Step 6 reduction limited to
the tax left after every other reduction, so it came after the Marriage
Allowance reduction. The election then valued the gaining spouse's
reduction against tax the finance-cost reduction would have cleared
anyway: a landlord whose finance costs wipe out their tax still "saved"
252, so the couple elected and paid the elector's extra tax for nothing.

Each Step 6 reduction is limited to the tax left after those before it
(ITA 2007 s. 29(2)-(3)); the order changes how the total is split but not
the total (s. 27(2)). Take the finance-cost reduction first and the
Marriage Allowance reduction last, so the reduction is exactly the tax the
election saves the gaining spouse. YAML cases pin both the no-election and
the partial cases; the property tests now generate finance costs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The tests switched Marriage Allowance off because the old model booked it
as the transferor's unused allowance (#1947). With the statutory transfer,
an election fixes the gaining partner's reduction, so hold each couple's
election at the first run's choice instead, as the other UC property tests
now do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Companion cases B and C of TheAxiomFoundation/rulespec-uk#437: on 50,070
of earnings, 200 of dividends reaches the basic rate limit exactly and
meets the conditions; 201 would put 1 at the dividend upper rate if
s. 13A were omitted, so it does not.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Finance costs make the property allowance a choice that income tax
weighs in its own branches, which nest with the election's, so giving
them to every adult made the property tests about ten times slower.
Draw them for about one population in four, and always try a landlord
couple whose finance-cost reduction clears the landlord's tax.

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

The election and the property allowance route both weigh income tax in
policyengine-core branches, and they nest. Check that results are
bit-for-bit the same whichever output is asked for first, with and
without a dated personal allowance reform; that a reform's baseline
matches an unreformed simulation whichever runs first; and that no
branch is left after computing six years.

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

The independent review found a couple whose automatic choices raised their
tax: the landlord spouse's property allowance route was chosen on their own
tax, which forfeited the other spouse's Marriage Allowance reduction (2026:
3,492 against 3,372 with full relief and the election). The route now
compares the benefit unit's income tax; the case is a YAML test and an
explicit example in both couple-tax properties, which fail on the old rule.

2015-16's transferable amount is fixed at 1,060 (ITA 2007 s.55B(4)(a)), not
10% of whatever the allowance is, via a fixed_amount parameter; the CPS
reform respects it outside its expansion.

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

Copy link
Copy Markdown
Collaborator Author

The last description update dropped this PR's Reproducer: line, so the Fix evidence check failed. I've added it back. Please keep it in the description file you edit from, or narrow it to the test that reproduces the bug:

Reproducer: `policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/income/uc_earnings_deductions.yaml`, `policyengine_uk/tests/policy/baseline/gov/hmrc/income_tax/allowances/marriage_allowance.yaml`, `policyengine_uk/tests/policy/baseline/gov/hmrc/income_tax/allowances/meets_marriage_allowance_income_conditions.yaml`, `policyengine_uk/tests/policy/baseline/gov/hmrc/income_tax/property_income_band_base.yaml::Case 7, Marriage Allowance leaves the band base alone (#1947).`, `policyengine_uk/tests/policy/reforms/cps/marriage_tax_reforms.yaml::Expanded MA — rate-only expansion keeps the higher-rate bar`, `policyengine_uk/tests/test_marriage_allowance_election_branches.py::test_results_do_not_depend_on_query_order`

🤖 Generated with Claude Code

@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

Coordination note: #2545 (fixing #2544, where Step 6 reductions offset the Step 7 charges) changes the lists this PR reads. Whichever lands second rebases.

  • gov.hmrc.income_tax.income_tax_subtractions becomes step_6_tax_reductions, with the same contents. income_tax_additions is split into step_5_tax_components and step_7_additional_tax.
  • New income_tax_reductions = min(Step 6 list, Step 5 tax); income_tax = max(0, income_tax_pre_charges − income_tax_reductions) + income_tax_charges.
  • capped_mcad is capped at max(0, income_tax_pre_charges − other_tax_credits).

If #2545 lands first, this PR's rebase:

  1. adds marriage_allowance_tax_reduction to step_6_tax_reductions.yaml; income_tax_subtractions.yaml is deleted there, so a modify/delete conflict is expected;
  2. changes income_tax.income_tax_subtractions to income_tax.step_6_tax_reductions in marriage_allowance_tax_reduction_limit.py and property_finance_cost_relief.py.

If this PR lands first, #2545 carries marriage_allowance_tax_reduction into the new list.

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

# Conflicts:
#	policyengine_uk/reforms/cps/marriage_tax_reforms.py
Main's Gift Aid and adjusted net income changes (#2253 and the s. 58 rework)
moved the pension relief into adjusted net income while allowances still
deduct it, and made Gift Aid extend the rate limits instead of coming off
income. The rate test took allowances off adjusted net income, so after the
merge it deducted pension relief twice (a higher-rate pension saver passed
s. 55B(2)(b)) and took the gross gift off income below dividends instead of
extending the basic rate limit (a donor with dividends failed s. 55B(2)(ba)).

It now reads non_savings_taxable_income, the base savings and dividend tax
use, and extends the non-savings and dividend limits by the grossed-up gift
(ITA 2007 s. 414(2)(b)), as earned_income_tax does. The election also holds
net_income in its branches, so they do not rebuild it from the held adjusted
net income through net_income_from_adjusted_net_income.

Also drops the stale #1947 xfail allowlist entry (the xfail went with the
fix) and the stale Marriage Allowance sentence in non_savings_taxable_income.

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

github-actions Bot commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Aggregate impact on certified data: pass

Base 2475bf58a8 against head f753141386, both on enhanced_frs_2024_25.h5 (policyengine-uk-data 1.58.1, sha256 056e80fd8e91), fiscal years 2025-26 to 2031-32. Each year is a full microsimulation run on each side.

Checks: 0 broken by this change, 1 failing on both sides, 0 fixed. Moves flagged for review: 2 (thresholds in report_spec.yaml; a flag does not fail the job).

Workflow run and full report

Notes

  • In the years a figure from fewer than 10 records is suppressed (spending.disabled_students_allowance), every total containing it, and the decile incomes, are rebuilt from their other components, so no published total includes it. Those totals are marked †. A change is withheld where the two sides leave out different figures and can't be lined up.

Moves flagged for review

Measure Year Base Head Change
Relative poverty, after housing costs: pensioners 2028-29 1,344k 1,384k +39.8k
Relative poverty, after housing costs: pensioners 2028-29 11.9% 12.2% +0.35pp

Receipts by tax head

Measure 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Income tax -£37.7m -£46.8m -£74.7m -£74.3m -£73.0m -£67.6m -£68.2m
Capital gains tax +£11,657 +£10,643 +£12,154 +£10,993 +£11,421 +£13,126 +£10,707

Spending by benefit and service

Measure 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
State Pension -£288 -£449 -£536 -£764 -£862 -£833 -£706
Pension Credit +£13 -£16,988 +£57,558 +£41,552 -£61,493 -£0.1m -£0.1m
Universal Credit -£0.2m -£0.3m -£0.3m -£0.3m -£0.3m -£0.3m -£2.1m
Housing Benefit -£3,445 -£8,804 -£12,818 -£20,721 -£36,852 -£60,034 -£66,370
Council Tax Reduction +£2,854 -£3,584 +£957 -£1 +£4,651 -£642 -£2,505
Disabled Students' Allowance suppressed suppressed suppressed suppressed suppressed suppressed suppressed

Caseloads

Measure 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Disabled Students' Allowance suppressed suppressed suppressed suppressed suppressed suppressed suppressed

Government balance, each component counted once

Measure 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Government receipts, each component once -£37.7m -£46.8m -£74.7m -£74.3m -£73.0m -£67.6m -£68.2m
Government spending -£0.2m † -£0.3m † -£0.3m † -£0.3m † -£0.4m † -£0.5m † -£2.3m †
Government balance, each receipt once -£37.5m † -£46.5m † -£74.4m † -£74.0m † -£72.6m † -£67.1m † -£65.9m †

Model totals

Measure 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
gov_tax -£37.7m -£46.8m -£74.7m -£74.3m -£73.0m -£67.6m -£68.2m
household_tax -£37.7m -£46.8m -£74.7m -£74.3m -£73.0m -£67.6m -£68.2m
pre_budget_change_household_tax -£37.7m -£46.8m -£74.7m -£74.3m -£73.0m -£67.6m -£68.2m
gov_spending -£0.2m † -£0.3m † -£0.3m † -£0.3m † -£0.4m † -£0.5m † -£2.3m †
household_benefits -£0.2m † -£0.3m † -£0.3m † -£0.3m † -£0.4m † -£0.5m † -£2.3m †
gov_balance -£37.5m † -£46.5m † -£74.4m † -£74.0m † -£72.6m † -£67.1m † -£65.9m †

Household income totals

Measure 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Household net income +£37.5m † +£46.5m † +£74.4m † +£74.0m † +£72.6m † +£67.1m † +£65.9m †
Household net income (HBAI definition) +£37.5m +£46.5m +£74.4m +£74.0m +£72.6m +£67.1m +£65.9m

Poverty rates

Measure 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Absolute poverty, after housing costs: everyone 0 0 0 0 0 -0.01pp 0
Absolute poverty, after housing costs: children 0 0 0 0 0 -0.02pp 0
Absolute poverty, after housing costs: working-age adults 0 0 0 0 0 -0.01pp 0
Relative poverty, before housing costs: everyone -0.00pp -0.00pp -0.00pp 0 0 0 0
Relative poverty, before housing costs: working-age adults 0 0 -0.00pp 0 0 0 0
Relative poverty, before housing costs: pensioners -0.01pp -0.01pp -0.00pp 0 0 0 0
Relative poverty, after housing costs: everyone -0.00pp 0 0 +0.06pp +0.00pp +0.00pp 0
Relative poverty, after housing costs: children -0.01pp 0 0 0 0 0 0
Relative poverty, after housing costs: working-age adults -0.00pp 0 0 0 0 0 0
Relative poverty, after housing costs: pensioners 0 0 0 +0.35pp +0.01pp +0.01pp 0

People in poverty

Measure 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Absolute poverty, after housing costs: everyone 0 0 0 0 0 -5.6k 0
Absolute poverty, after housing costs: children 0 0 0 0 0 -2.8k 0
Absolute poverty, after housing costs: working-age adults 0 0 0 0 0 -2.8k 0
Relative poverty, before housing costs: everyone -1.2k -1.2k -962 0 0 0 0
Relative poverty, before housing costs: working-age adults 0 0 -481 0 0 0 0
Relative poverty, before housing costs: pensioners -1.2k -1.2k -481 0 0 0 0
Relative poverty, after housing costs: everyone -3.3k 0 0 +39.8k +795 +798 0
Relative poverty, after housing costs: children -1.7k 0 0 0 0 0 0
Relative poverty, after housing costs: working-age adults -1.7k 0 0 0 0 0 0
Relative poverty, after housing costs: pensioners 0 0 0 +39.8k +795 +798 0

Inequality

Measure 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Gini coefficient, equivalised HBAI income before housing costs -0.0000 -0.0000 -0.0000 -0.0000 -0.0000 -0.0000 -0.0000

Mean household net income in each side's own deciles

Measure 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Decile 1: mean household_net_income per household +£0.16 † +£0.14 † +£4.79 † +£4.85 † +£4.89 † +£4.86 † +£4.98 †
Decile 2: mean household_net_income per household +£0.81 † +£0.15 † +£0.16 † -£0.12 † -£0.00 † +£0.22 † +£0.24 †
Decile 3: mean household_net_income per household -£0.29 † -£1.61 † +£0.23 † -£0.02 † -£0.03 † -£14 † +£0.17 †
Decile 4: mean household_net_income per household +£0.13 † +£0.80 † +£0.24 † +£0.17 † -£0.81 † +£20 † +£0.15 †
Decile 5: mean household_net_income per household -£16 † +£2.47 † +£0.68 † +£0.20 † +£1.00 † +£0.77 † +£0.21 †
Decile 6: mean household_net_income per household +£15 † +£2.93 † +£5.47 † +£1.14 † +£4.58 † +£4.48 † +£4.68 †
Decile 7: mean household_net_income per household +£1.62 † +£1.52 † +£1.49 † +£8.96 † +£3.92 † +£1.86 † +£1.83 †
Decile 8: mean household_net_income per household +£0.40 † +£0.26 † +£0.26 † +£0.26 † -£5.07 † +£0.61 † +£0.67 †
Decile 9: mean household_net_income per household +£0.69 † +£5.75 † +£10 † +£10 † +£9.87 † +£7.69 † +£7.44 †
Decile 10: mean household_net_income per household +£0.02 † +£0.00 † +£0.00 † +£0.11 † +£0.11 † +£0.11 † +£0.11 †

Standard reform costings (change in gov_balance)

Measure 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Raise basic rate by 1pp -£2.3m -£2.9m -£4.3m -£4.2m -£4.0m -£3.7m -£3.6m
Raise higher rate by 1pp -£2 -£1 -£2 +£44 +£3 -£9 -£36
Raise personal allowance by ~800GBP/year -£4.3m +£5.6m -£16.7m -£4.0m -£1.6m -£8.3m -£4.8m
Raise child benefit by 25GBP/week per additional child 0 +£5 +£26 -£2 +£3 +£2 +£4
Reduce Universal Credit taper rate to 20% -£73,071 -£87,004 -£0.1m -£0.1m -£0.1m +£13.3m -£1.3m
Raise Class 1 main employee NICs rate to 10% +£36 -£4,953 +£36 +£149 +£40 +£18 +£64
Raise VAT standard rate by 2pp +£125 -£175 +£65 -£13 -£5 +£397 -£27
Raise additional rate by 3pp +£0 -£2 -£0 +£4 0 suppressed suppressed
Raise the Universal Credit standard allowance by 10% +£4 -£5,118 +£4 -£10 +£7 -£12 +£4,300
Raise the State Pension by 10% -£6.9m -£9.1m +£12.8m +£18.8m +£28.2m +£29.4m +£29.6m

Standard reforms: change in each reform's target

Measure 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Raise basic rate by 1pp: change in income_tax -£2.4m -£2.9m -£4.3m -£4.3m -£4.0m -£3.7m -£3.7m
Raise higher rate by 1pp: change in income_tax -£0 +£49 +£1 +£19 -£2 +£14 -£4
Raise personal allowance by ~800GBP/year: change in income_tax -£4.3m +£5.7m -£16.7m -£4.0m -£1.5m -£8.2m -£4.8m
Reduce Universal Credit taper rate to 20%: change in universal_credit +£73,070 +£86,560 +£0.1m +£0.1m +£0.1m +£0.1m +£1.3m
Raise additional rate by 3pp: change in income_tax 0 0 +£0 0 0 suppressed suppressed
Raise the Universal Credit standard allowance by 10%: change in universal_credit +£0 +£5,118 0 +£0 +£0 +£0 -£4,293
Raise the State Pension by 10%: change in state_pension +£113 -£155 -£368 -£220 +£17 +£31 -£314

Household net income by decile

Mean change in household net income per household, and as a share of the decile's base income; households stay in the base run's deciles.

Base decile 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Decile 1 0 0 +£5 (+0.0%) +£5 (+0.0%) +£5 (+0.0%) +£5 (+0.0%) +£5 (+0.0%)
Decile 2 +£1 (+0.0%) 0 0 0 0 0 0
Decile 3 +£1 (+0.0%) 0 0 0 0 0 0
Decile 4 +£1 (+0.0%) +£1 (+0.0%) 0 0 0 0 0
Decile 5 +£1 (+0.0%) +£1 (+0.0%) +£1 (+0.0%) +£1 (+0.0%) +£1 (+0.0%) +£1 (+0.0%) 0
Decile 6 +£5 (+0.0%) +£5 (+0.0%) +£5 (+0.0%) +£5 (+0.0%) +£5 (+0.0%) +£4 (+0.0%) +£5 (+0.0%)
Decile 7 +£2 (+0.0%) +£2 (+0.0%) +£1 (+0.0%) +£2 (+0.0%) +£2 (+0.0%) +£2 (+0.0%) +£2 (+0.0%)
Decile 8 0 0 0 0 0 +£1 (+0.0%) +£1 (+0.0%)
Decile 9 +£1 (+0.0%) +£6 (+0.0%) fewer than 10 households change fewer than 10 households change fewer than 10 households change fewer than 10 households change fewer than 10 households change
Decile 10 fewer than 10 households change fewer than 10 households change fewer than 10 households change fewer than 10 households change fewer than 10 households change fewer than 10 households change fewer than 10 households change
all +£1 (+0.0%) +£1 (+0.0%) +£2 (+0.0%) +£2 (+0.0%) +£2 (+0.0%) +£2 (+0.0%) +£2 (+0.0%)
Year People gaining over £1 People losing over £1
2025-26 1.5% 0.8%
2026-27 1.4% 0.5%
2027-28 1.5% 0.3%
2028-29 1.5% 0.6%
2029-30 1.3% 0.5%
2030-31 1.3% 0.3%
2031-32 1.3% 0.3%

No change in any year: population.

Checks failing on both base and head (1)
  • VAT excluding private school fees is never negative (sign:vat): 2025-26: 76 units below zero, a weighted £-53,889,156 in total; 2026-27: 76 units below zero, a weighted £-55,420,387 in total; 2027-28: 76 units below zero, a weighted £-56,618,038 in total
Counted-once check

Each of 91 components was raised by £1 per unit in 2026-27, and every head of its totals was read household by household: its own head must rise by exactly £1, no other tax head may move, no other benefit may rise, and the model's own total must move by what its heads moved. A head that breaks a rule is measured again twice: with every other head held at its baseline value, and with every other head held at its value after the first £1 while the component rises again. It fails where either still shows the response. 0 failed; 0 were skipped.

  • widowed_parents_allowance: pension_credit responds only through other heads in fewer than 10 households (other heads that moved there: carers_allowance; gov_spending; not a failure)
  • widowed_parents_allowance: pension_credit responds only through other heads in fewer than 10 households (other heads that moved there: carers_allowance; household_benefits; not a failure)
Provenance
Side Commit Runtime Environment hash Partition hash Time
base main 2475bf58a8 uk 2.164.0 (files 6e91d97dc6a2), core 3.33.12, py 3.13.18 aarch64 8eafc5aaa169 1eac2ca06b91 2090.62s
head #1963 merged into main f753141386 uk 2.164.0 (files 724a8df9d354), core 3.33.12, py 3.13.18 aarch64 8eafc5aaa169 1eac2ca06b91 2807.59s

Dataset: hf://policyengine/policyengine-uk-data-private/enhanced_frs_2024_25.h5@2526edbbb687a3b54dbb11272e5389e4b62d2ac2, sha256 056e80fd8e9151d78f432a25cde863340baee81304c6216a37fb575ce8563784 (verified on both sides), policyengine-uk-data 1.58.1.

Receipts by tax head: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Final charge on cash interest in non-cash ISAs £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Lifetime ISA withdrawal charge £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Income tax £298.66bn £318.26bn £335.53bn £350.44bn £365.72bn £383.46bn £400.97bn
National Insurance (employee and self-employed) £52.37bn £54.71bn £56.48bn £58.15bn £60.22bn £62.11bn £64.44bn
National Insurance (employer) £150.59bn £156.99bn £161.74bn £166.13bn £173.24bn £178.59bn £185.29bn
Employer NI change borne by capital £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Employer NI change borne by consumers £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Capital gains tax £12.61bn £13.05bn £13.53bn £14.02bn £14.52bn £15.05bn £15.58bn
Student loan repayments £7.91bn £8.21bn £8.43bn £8.63bn £8.85bn £9.10bn £9.42bn
VAT excluding private school fees £249.37bn £255.28bn £261.81bn £267.87bn £274.12bn £280.56bn £286.81bn
Change in VAT from its baseline £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
VAT on private school fees £1.95bn £2.00bn £2.05bn £2.10bn £2.21bn £2.21bn £2.31bn
Change in private school VAT from its baseline £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Fuel duties £24.53bn £24.64bn £26.64bn £26.69bn £26.42bn £25.76bn £26.35bn
Alcohol duties £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Tobacco duties £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Vehicle excise duties £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Betting and gaming duties £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
TV licence fee £4.26bn £4.41bn £4.42bn £4.44bn £4.45bn £4.47bn £4.48bn
Council tax £49.21bn £51.88bn £54.97bn £58.29bn £61.55bn £64.92bn £68.49bn
High value council tax surcharge £0.00bn £0.00bn £0.00bn £0.48bn £0.49bn £0.50bn £0.51bn
Northern Ireland domestic rates £0.55bn £0.58bn £0.60bn £0.63bn £0.66bn £0.69bn £0.72bn
Business rates £34.68bn £38.04bn £38.88bn £39.76bn £43.07bn £43.13bn £44.57bn
Stamp duty land tax on the household's own purchases £6.90bn £7.39bn £7.81bn £8.21bn £8.62bn £9.02bn £9.42bn
Stamp duty land tax paid by companies £4.81bn £5.98bn £6.73bn £7.46bn £8.21bn £8.87bn £9.16bn
Land transaction tax (Wales) £0.07bn £0.08bn £0.09bn £0.09bn £0.10bn £0.10bn £0.11bn
Land and buildings transaction tax (Scotland) £0.31bn £0.32bn £0.34bn £0.35bn £0.37bn £0.38bn £0.40bn
Unspecified tax change borne by capital £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Unspecified tax change borne by consumers £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Wealth tax £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Wealth tax excluding the main residence £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Land value tax £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Carbon tax £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Spending by benefit and service: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Lifetime ISA bonus £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
State Pension £121.64bn £126.07bn £127.08bn £128.87bn £132.52bn £136.31bn £140.08bn
Pension Credit £6.66bn £7.04bn £7.32bn £7.46bn £7.60bn £7.77bn £7.94bn
Winter Fuel Payment £1.86bn £1.86bn £1.80bn £1.78bn £1.79bn £1.79bn £1.80bn
Pension Age Winter Heating Payment (Scotland) £0.19bn £0.20bn £0.20bn £0.21bn £0.21bn £0.22bn £0.22bn
Universal Credit £75.21bn £80.38bn £82.93bn £84.23bn £85.57bn £86.87bn £87.96bn
Housing Benefit £8.55bn £8.89bn £9.17bn £9.31bn £9.58bn £9.84bn £10.10bn
Council Tax Reduction £2.07bn £2.18bn £2.32bn £2.46bn £2.60bn £2.74bn £2.89bn
Employment and Support Allowance (income-related) £0.79bn £0.81bn £0.83bn £0.85bn £0.86bn £0.89bn £0.90bn
Employment and Support Allowance (contribution-based) £5.20bn £5.34bn £5.46bn £5.59bn £5.72bn £5.85bn £5.99bn
Jobseeker's Allowance (income-based) £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Jobseeker's Allowance (contribution-based) £0.29bn £0.30bn £0.30bn £0.31bn £0.32bn £0.32bn £0.33bn
Income Support £0.11bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Working Tax Credit £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Child Tax Credit £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Incapacity Benefit £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Personal Independence Payment £26.94bn £28.06bn £29.13bn £29.83bn £30.54bn £31.26bn £31.96bn
Disability Living Allowance £7.49bn £7.80bn £8.10bn £8.29bn £8.49bn £8.69bn £8.88bn
Attendance Allowance £8.68bn £9.04bn £9.38bn £9.61bn £9.83bn £10.07bn £10.29bn
Carer's Allowance £4.48bn £4.67bn £4.84bn £4.96bn £5.08bn £5.19bn £5.31bn
Carer Support Payment (Scotland) £0.31bn £0.32bn £0.33bn £0.34bn £0.35bn £0.36bn £0.37bn
Carer's Allowance Supplement (Scotland) £0.00bn £0.04bn £0.05bn £0.05bn £0.05bn £0.05bn £0.05bn
Severe Disablement Allowance £0.03bn £0.03bn £0.03bn £0.03bn £0.03bn £0.03bn £0.03bn
Industrial Injuries Disablement Benefit £0.50bn £0.52bn £0.53bn £0.54bn £0.55bn £0.57bn £0.58bn
Armed Forces Compensation Scheme £0.61bn £0.62bn £0.64bn £0.65bn £0.67bn £0.68bn £0.70bn
War Widow's or Widower's Pension £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Child Benefit £16.16bn £16.83bn £17.43bn £17.85bn £18.26bn £18.69bn £19.10bn
Scottish Child Payment £0.49bn £0.52bn £0.54bn £0.55bn £0.56bn £0.57bn £0.56bn
Two Child Limit Payment (Scotland, counterfactual) £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Healthy Start vouchers £0.05bn £0.05bn £0.05bn £0.05bn £0.05bn £0.05bn £0.05bn
Maternity Allowance £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Statutory Maternity Pay £3.30bn £3.39bn £3.47bn £3.55bn £3.63bn £3.72bn £3.80bn
Statutory Paternity Pay £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Statutory Sick Pay £1.12bn £1.15bn £1.18bn £1.21bn £1.24bn £1.27bn £1.29bn
Sure Start Maternity Grant £0.01bn £0.01bn £0.01bn £0.01bn £0.01bn £0.01bn £0.01bn
Bereavement Support Payment £0.67bn £0.69bn £0.70bn £0.72bn £0.73bn £0.75bn £0.77bn
Widowed Parent's Allowance £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Tax-Free Childcare £0.55bn £0.56bn £0.57bn £0.58bn £0.59bn £0.61bn £0.59bn
Extended childcare entitlement £2.99bn £3.50bn £3.60bn £3.67bn £3.77bn £3.85bn £3.94bn
Universal childcare entitlement £2.05bn £2.23bn £2.28bn £2.34bn £2.40bn £2.47bn £2.52bn
Targeted childcare entitlement £0.50bn £0.51bn £0.52bn £0.53bn £0.54bn £0.56bn £0.52bn
Childcare Offer for Wales £0.26bn £0.28bn £0.28bn £0.29bn £0.30bn £0.30bn £0.31bn
Care to Learn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Childcare Grant £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Parents' Learning Allowance £0.20bn £0.20bn £0.20bn £0.20bn £0.20bn £0.20bn £0.20bn
Adult Dependants' Grant £0.06bn £0.06bn £0.04bn £0.04bn £0.04bn £0.04bn £0.04bn
Travel Grant £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Disabled Students' Allowance suppressed suppressed suppressed suppressed suppressed suppressed suppressed
16 to 19 Bursary Fund £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
NHS spending £203.83bn £204.54bn £205.13bn £205.79bn £206.49bn £207.23bn £207.73bn
State education spending £0.02bn £0.02bn £0.02bn £0.02bn £0.02bn £0.02bn £0.02bn
Transport subsidy spending £25.05bn £25.13bn £25.21bn £25.29bn £25.37bn £25.46bn £25.53bn
Cost-of-living support payment £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Energy Bills Rebate £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Energy Price Guarantee subsidy £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Basic income £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Disability basic income £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Other public spending budget change £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Caseloads: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Lifetime ISA bonus 0k 0k 0k 0k 0k 0k 0k
State Pension 11,261k 11,158k 10,849k 10,746k 10,783k 10,822k 10,848k
Pension Credit 1,485k 1,483k 1,480k 1,461k 1,462k 1,464k 1,460k
Winter Fuel Payment 7,842k 7,793k 7,494k 7,407k 7,432k 7,459k 7,477k
Pension Age Winter Heating Payment (Scotland) 803k 803k 803k 800k 803k 806k 808k
Universal Credit 6,334k 6,341k 6,366k 6,347k 6,345k 6,333k 6,298k
Housing Benefit 1,249k 1,247k 1,250k 1,234k 1,238k 1,240k 1,239k
Council Tax Reduction 3,175k 3,198k 3,209k 3,229k 3,244k 3,257k 3,264k
Employment and Support Allowance (income-related) 71k 71k 71k 71k 71k 72k 72k
Employment and Support Allowance (contribution-based) 719k 721k 723k 726k 728k 731k 732k
Jobseeker's Allowance (income-based) 0k 0k 0k 0k 0k 0k 0k
Jobseeker's Allowance (contribution-based) 67k 67k 67k 68k 68k 68k 68k
Income Support 25k 0k 0k 0k 0k 0k 0k
Working Tax Credit 0k 0k 0k 0k 0k 0k 0k
Child Tax Credit 0k 0k 0k 0k 0k 0k 0k
Incapacity Benefit 0k 0k 0k 0k 0k 0k 0k
Personal Independence Payment 3,566k 3,579k 3,589k 3,601k 3,613k 3,626k 3,635k
Disability Living Allowance 1,291k 1,295k 1,299k 1,303k 1,308k 1,312k 1,316k
Attendance Allowance 1,694k 1,700k 1,705k 1,711k 1,716k 1,723k 1,727k
Carer's Allowance 1,034k 1,038k 1,041k 1,044k 1,048k 1,051k 1,053k
Carer Support Payment (Scotland) 71k 71k 72k 72k 72k 72k 73k
Carer's Allowance Supplement (Scotland) 0k 71k 72k 72k 72k 72k 73k
Severe Disablement Allowance 4k 4k 4k 4k 4k 4k 4k
Industrial Injuries Disablement Benefit 137k 137k 138k 138k 139k 139k 140k
Armed Forces Compensation Scheme 93k 93k 94k 94k 94k 95k 95k
War Widow's or Widower's Pension 0k 0k 0k 0k 0k 0k 0k
Child Benefit 8,102k 8,128k 8,142k 8,165k 8,191k 8,216k 8,232k
Scottish Child Payment 350k 352k 353k 357k 354k 355k 342k
Two Child Limit Payment (Scotland, counterfactual) 0k 0k 0k 0k 0k 0k 0k
Healthy Start vouchers 199k 200k 201k 201k 202k 203k 203k
Maternity Allowance 0k 0k 0k 0k 0k 0k 0k
Statutory Maternity Pay 332k 334k 335k 336k 337k 338k 339k
Statutory Paternity Pay 0k 0k 0k 0k 0k 0k 0k
Statutory Sick Pay 181k 182k 182k 183k 183k 184k 185k
Sure Start Maternity Grant 26k 26k 26k 26k 26k 27k 27k
Bereavement Support Payment 98k 98k 98k 99k 99k 99k 100k
Widowed Parent's Allowance 0k 0k 0k 0k 0k 0k 0k
Tax-Free Childcare 1,014k 1,024k 1,024k 1,027k 1,027k 1,031k 1,013k
Extended childcare entitlement 796k 801k 806k 809k 815k 819k 820k
Universal childcare entitlement 1,071k 1,074k 1,077k 1,080k 1,086k 1,096k 1,097k
Targeted childcare entitlement 165k 156k 154k 155k 155k 156k 144k
Childcare Offer for Wales 39k 39k 39k 39k 39k 39k 39k
Care to Learn 0k 0k 0k 0k 0k 0k 0k
Childcare Grant 0k 0k 0k 0k 0k 0k 0k
Parents' Learning Allowance 98k 98k 99k 99k 99k 100k 100k
Adult Dependants' Grant 18k 18k 12k 12k 12k 13k 13k
Travel Grant 0k 0k 0k 0k 0k 0k 0k
Disabled Students' Allowance suppressed suppressed suppressed suppressed suppressed suppressed suppressed
16 to 19 Bursary Fund 0k 0k 0k 0k 0k 0k 0k
Cost-of-living support payment 0k 0k 0k 0k 0k 0k 0k
Energy Bills Rebate 0k 0k 0k 0k 0k 0k 0k
Energy Price Guarantee subsidy 0k 0k 0k 0k 0k 0k 0k
Basic income 0k 0k 0k 0k 0k 0k 0k
Disability basic income 0k 0k 0k 0k 0k 0k 0k
Government balance, each component counted once: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Government receipts, each component once £898.78bn £941.81bn £980.04bn £1,013.76bn £1,052.82bn £1,088.94bn £1,129.03bn
Government spending £528.95bn † £543.83bn † £551.68bn † £558.06bn † £566.57bn † £575.31bn † £583.38bn †
Government balance, each receipt once £369.83bn † £397.98bn † £428.36bn † £455.70bn † £486.25bn † £513.63bn † £545.65bn †
Benefits in household_benefits but not in gov_spending £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Model totals: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
gov_tax £898.78bn £941.81bn £980.04bn £1,013.76bn £1,052.82bn £1,088.94bn £1,129.03bn
household_tax £496.87bn £527.55bn £554.44bn £577.67bn £603.25bn £627.58bn £654.62bn
pre_budget_change_household_tax £488.96bn £519.34bn £546.01bn £568.55bn £593.90bn £617.98bn £644.68bn
gov_spending £528.95bn † £543.83bn † £551.68bn † £558.06bn † £566.57bn † £575.31bn † £583.38bn †
household_benefits £528.95bn † £543.83bn † £551.68bn † £558.06bn † £566.57bn † £575.31bn † £583.38bn †
gov_balance £369.83bn † £397.98bn † £428.36bn † £455.70bn † £486.25bn † £513.63bn † £545.65bn †
Household income totals: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Household market income £1,651.49bn £1,713.03bn £1,764.05bn £1,812.97bn £1,862.19bn £1,918.39bn £1,985.28bn
Household net income £1,644.46bn † £1,688.74bn † £1,719.62bn † £1,750.69bn † £1,781.75bn † £1,821.14bn † £1,867.46bn †
Household net income (HBAI definition) £1,457.33bn £1,504.53bn £1,537.66bn £1,569.43bn £1,602.74bn £1,640.55bn £1,687.11bn
Population: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
People 69,224k 69,466k 69,668k 69,891k 70,128k 70,381k 70,550k
Benefit units 35,100k 35,223k 35,325k 35,438k 35,558k 35,686k 35,772k
Households 31,037k 31,145k 31,236k 31,336k 31,442k 31,555k 31,631k
Poverty rates: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Absolute poverty, before housing costs: everyone 16.0% 15.1% 15.4% 15.5% 15.6% 15.7% 15.7%
Absolute poverty, before housing costs: children 24.0% 21.1% 21.3% 21.3% 21.5% 21.8% 21.8%
Absolute poverty, before housing costs: working-age adults 13.8% 13.5% 13.9% 14.1% 14.1% 14.2% 14.2%
Absolute poverty, before housing costs: pensioners 13.7% 13.2% 13.1% 13.4% 13.5% 13.5% 13.5%
Absolute poverty, after housing costs: everyone 21.4% 20.8% 21.3% 21.5% 21.7% 21.9% 22.2%
Absolute poverty, after housing costs: children 33.0% 30.7% 31.1% 31.1% 31.4% 31.7% 32.3%
Absolute poverty, after housing costs: working-age adults 19.8% 19.6% 20.2% 20.5% 20.7% 20.8% 21.1%
Absolute poverty, after housing costs: pensioners 12.7% 12.4% 12.3% 12.5% 12.8% 12.9% 13.0%
Relative poverty, before housing costs: everyone 17.2% 16.4% 16.5% 16.4% 16.2% 16.3% 16.5%
Relative poverty, before housing costs: children 26.2% 23.5% 23.4% 23.1% 22.6% 22.8% 23.1%
Relative poverty, before housing costs: working-age adults 14.6% 14.4% 14.7% 14.7% 14.6% 14.7% 14.8%
Relative poverty, before housing costs: pensioners 15.0% 14.8% 14.1% 14.1% 14.0% 14.0% 14.2%
Relative poverty, after housing costs: everyone 21.6% 21.0% 21.1% 21.3% 21.3% 21.4% 21.8%
Relative poverty, after housing costs: children 33.3% 31.2% 30.8% 30.7% 31.0% 31.0% 31.5%
Relative poverty, after housing costs: working-age adults 19.9% 19.8% 20.1% 20.3% 20.4% 20.5% 20.7%
Relative poverty, after housing costs: pensioners 12.9% 12.5% 12.1% 12.2% 11.8% 11.9% 12.8%
People in poverty: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Absolute poverty, before housing costs: everyone 11,057k 10,466k 10,734k 10,860k 10,960k 11,054k 11,081k
Absolute poverty, before housing costs: children 3,580k 3,171k 3,210k 3,220k 3,261k 3,305k 3,317k
Absolute poverty, before housing costs: working-age adults 5,853k 5,746k 6,021k 6,123k 6,169k 6,209k 6,219k
Absolute poverty, before housing costs: pensioners 1,625k 1,550k 1,504k 1,517k 1,530k 1,539k 1,545k
Absolute poverty, after housing costs: everyone 14,847k 14,422k 14,808k 14,994k 15,242k 15,397k 15,638k
Absolute poverty, after housing costs: children 4,937k 4,609k 4,671k 4,685k 4,757k 4,811k 4,912k
Absolute poverty, after housing costs: working-age adults 8,406k 8,359k 8,732k 8,898k 9,024k 9,111k 9,240k
Absolute poverty, after housing costs: pensioners 1,504k 1,454k 1,404k 1,411k 1,460k 1,475k 1,486k
Relative poverty, before housing costs: everyone 11,895k 11,402k 11,467k 11,487k 11,373k 11,468k 11,652k
Relative poverty, before housing costs: children 3,918k 3,523k 3,519k 3,491k 3,419k 3,458k 3,516k
Relative poverty, before housing costs: working-age adults 6,202k 6,136k 6,337k 6,400k 6,366k 6,415k 6,515k
Relative poverty, before housing costs: pensioners 1,775k 1,742k 1,611k 1,597k 1,588k 1,595k 1,621k
Relative poverty, after housing costs: everyone 14,953k 14,597k 14,698k 14,865k 14,952k 15,043k 15,351k
Relative poverty, after housing costs: children 4,975k 4,677k 4,630k 4,634k 4,690k 4,712k 4,794k
Relative poverty, after housing costs: working-age adults 8,442k 8,455k 8,680k 8,847k 8,916k 8,977k 9,092k
Relative poverty, after housing costs: pensioners 1,537k 1,465k 1,388k 1,384k 1,346k 1,355k 1,464k
Inequality: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Gini coefficient, equivalised HBAI income before housing costs 0.3085 0.3059 0.3065 0.3071 0.3075 0.3081 0.3094
Mean household net income in each side's own deciles: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Decile 1: mean household_net_income per household £17,781 † £17,906 † £17,837 † £17,910 † £18,147 † £18,344 † £18,488 †
Decile 2: mean household_net_income per household £27,963 † £28,918 † £28,655 † £28,906 † £29,096 † £29,647 † £30,235 †
Decile 3: mean household_net_income per household £34,380 † £35,461 † £36,065 † £36,509 † £36,850 † £37,324 † £38,043 †
Decile 4: mean household_net_income per household £38,785 † £40,714 † £41,873 † £42,413 † £43,145 † £43,927 † £44,167 †
Decile 5: mean household_net_income per household £43,772 † £44,628 † £45,607 † £46,289 † £47,329 † £48,286 † £49,711 †
Decile 6: mean household_net_income per household £48,224 † £49,528 † £50,742 † £51,915 † £52,207 † £53,178 † £54,208 †
Decile 7: mean household_net_income per household £56,220 † £57,226 † £57,766 † £58,019 † £59,044 † £60,063 † £61,767 †
Decile 8: mean household_net_income per household £69,151 † £70,534 † £71,800 † £73,075 † £74,087 † £75,541 † £77,428 †
Decile 9: mean household_net_income per household £79,491 † £81,426 † £82,838 † £84,146 † £85,548 † £87,061 † £89,952 †
Decile 10: mean household_net_income per household £117,581 † £120,296 † £121,933 † £124,163 † £126,045 † £128,342 † £131,110 †
Standard reform costings (change in gov_balance): head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Raise basic rate by 1pp £6.47bn £6.76bn £6.67bn £6.85bn £7.06bn £7.34bn £7.55bn
Raise higher rate by 1pp £3.69bn £3.98bn £4.03bn £4.29bn £4.46bn £4.78bn £4.91bn
Raise personal allowance by ~800GBP/year £-3.82bn £-3.94bn £-4.03bn £-4.09bn £-4.18bn £-4.19bn £-2.00bn
Raise child benefit by 25GBP/week per additional child £-1.99bn £-1.75bn £-1.56bn £-1.46bn £-1.35bn £-1.23bn £-1.12bn
Reduce Universal Credit taper rate to 20% £-20.39bn £-20.96bn £-21.38bn £-21.61bn £-21.87bn £-22.16bn £-22.60bn
Raise Class 1 main employee NICs rate to 10% £11.02bn £11.42bn £11.77bn £12.07bn £12.37bn £12.75bn £13.07bn
Raise VAT standard rate by 2pp £24.89bn £25.55bn £26.14bn £26.75bn £27.39bn £28.03bn £28.66bn
Raise additional rate by 3pp £2.96bn £3.21bn £3.26bn £3.46bn £3.65bn suppressed suppressed
Raise the Universal Credit standard allowance by 10% £-3.05bn £-3.18bn £-3.30bn £-3.34bn £-3.38bn £-3.42bn £-3.48bn
Raise the State Pension by 10% £-7.80bn £-8.03bn £-8.00bn £-8.11bn £-8.40bn £-8.51bn £-8.84bn
Standard reforms: change in each reform's target: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Raise basic rate by 1pp: change in income_tax £6.61bn £6.91bn £6.83bn £7.03bn £7.23bn £7.46bn £7.73bn
Raise higher rate by 1pp: change in income_tax £3.69bn £3.99bn £4.04bn £4.25bn £4.47bn £4.74bn £4.93bn
Raise personal allowance by ~800GBP/year: change in income_tax £-3.95bn £-4.08bn £-4.19bn £-4.25bn £-4.34bn £-4.41bn £-1.76bn
Raise child benefit by 25GBP/week per additional child: change in child_benefit £2.33bn £2.14bn £1.95bn £1.84bn £1.73bn £1.62bn £1.50bn
Reduce Universal Credit taper rate to 20%: change in universal_credit £20.40bn £20.95bn £21.37bn £21.62bn £21.87bn £22.22bn £22.61bn
Raise Class 1 main employee NICs rate to 10%: change in national_insurance £11.28bn £11.74bn £12.06bn £12.37bn £12.69bn £13.03bn £13.49bn
Raise VAT standard rate by 2pp: change in vat £24.69bn £25.35bn £25.94bn £26.54bn £27.17bn £27.81bn £28.43bn
Raise additional rate by 3pp: change in income_tax £2.96bn £3.21bn £3.26bn £3.46bn £3.66bn suppressed suppressed
Raise the Universal Credit standard allowance by 10%: change in universal_credit £3.05bn £3.18bn £3.29bn £3.36bn £3.43bn £3.48bn £3.52bn
Raise the State Pension by 10%: change in state_pension £10.68bn £11.18bn £11.31bn £11.55bn £12.00bn £12.44bn £12.94bn

…rtner roles

From the independent review of 10e6b33: the fixture left
is_claimant_or_partner to inference from ages, which can read a married
couple aged 18 and 100 as parent and child, so neither is a spouse and
test_only_married_couples_transfer failed on a valid draw. Adults are now
claimant or partner and children are not, as in the other fixtures, and
that couple is pinned as an example.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Preserve final income tax inputs on the real simulation while recomputing
Marriage Allowance counterfactual tax. Add composition and couple-tax
regressions, and update the two Scottish Pension Credit fixtures from
independently sourced 2025-26 arithmetic.
@juaristi22

Copy link
Copy Markdown
Collaborator

At 00bf62a8, the continuation preserves the original branch history, clears inherited final income tax only in private election branches, declares the evidenced fiscal read, promotes pension division operands to float64, and isolates the Pension Credit no-Marriage-Allowance fixture. The original one-election and income-tax-only modelling choices remain.

The latest validation ran 807 Python IDs (806 passed; one credential-dependent skip) and 11 affected YAML calls (all passed). Qualified retained outcomes bring the accounting ledger to 8,241 unique IDs (8,137 passed; 104 skipped); they do not represent one whole-head execution. The unchanged Darwin/glibc skip keeps the actual strict checker exit at 1. Actual resulting-head Linux CI is pending. Independent committed-snapshot code/source review is COMPLETE/APPROVE with no open findings or material gaps; the fix verifier is CLEAN. C1/C2/C3 are resolved. Source verification covered 30 distinct cached official originals; it supplies no private-population or encoded-parity certificate.

Axiom parity and data #502/d833, CPS #2553/Steps #2545, consumer adoption and owner acceptance remain qualified; no merge or new lifecycle effect is requested by this reply.

@juaristi22

Copy link
Copy Markdown
Collaborator

Published c4cfc948 with privacy-safe accounting diagnostics and eight-way source/wheel test sharding. All 123 selected Python checks and the strict selected-outcome checker passed; independent engineering review is CLEAN. The preceding certified accounting failure remains under investigation: this update preserves its invariant and keeps the PR draft pending actual current-head CI.

MaxGhenis and others added 2 commits October 28, 2026 15:18
Main now adds the winter fuel payment charge to income_tax and settles the
property allowance route on the benefit unit's tax including the charge.

- The Marriage Allowance election compares income tax before the charge.
  The charge falls on a person whose own total income is over the
  threshold, which the election does not change, so it can differ between
  the two positions only through a relevant benefit, and the election does
  not weigh benefits. This keeps benefits out of the election's branches.
- The property allowance route is weighed on the benefit unit's total tax,
  since the route can move the spouse's tax through the election.
- Take main's six CI shards and drop the branch's eight-shard change and
  the declared capital gains tax read in fiscal_partition.yaml: the read
  made the counted-once guard report a false positive (capital gains tax
  never moves at any guard step).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Main now presumes a member 20 or more years younger than the claimant is
their child (613326b), so the pensioner transferor case read the couple
as parent and child and no one elected. Set is_claimant_or_partner for both,
as the variable's documentation advises. Also tighten the comment on why the
election compares tax before the winter fuel payment charge.

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

Copy link
Copy Markdown
Collaborator Author

Merged main into this branch (8117a4462), from the W7 session that owns #1963 for the Budget CI plan.

The head was c4cfc9489 and conflicting with main. Two commits were added on top, fast-forward, with no history rewritten:

  • 1cd797715 merges main 41fa5ddb5.
  • 8117a4462 fixes one YAML case for main's new household presumption.

Kept from the 22-23 Oct commits:

  • the private-branch delete_arrays, now on the tax the election compares (below);
  • the float64 State Pension division;
  • the Pension Credit Class 2 fixture;
  • the measure.py failure diagnostics and their tests;
  • the shard-accounting test, which reads the shard count from the workflow.

Dropped:

  • The eight-shard workflow change. Main has re-sharded to six since, and the merge takes main's workflow and testing doc.
  • The fiscal_partition.yaml declaration that income tax reads capital gains tax.
    • I reproduced the CI failure on a subset of the certified dataset holding the 89 flagged households (89 of 89).
    • capital_gains_tax moved in none of them at any guard step.
    • With main's partition, the guard passes on the same subset.
    • The flag came from the old reader tolerance: 2 × (0.02 + 2e-6 × head), so for a large enough household capital gains tax, a slope of 0 counted as one for one.
    • Main has since scaled that tolerance by the perturbation (76108fafe), and the merge takes main's guard.py.

Changed for main's winter fuel payment charge: main's income_tax now adds the charge, which reads Pension Credit and UC. The election's branches now compare income_tax_before_winter_fuel_payment_charge, so they don't compute benefits for every couple.

  • The charge falls on a person whose own total income is over £35,000, and the election doesn't change total income.
  • So the charge can differ between the two positions only through benefits, which the election already doesn't weigh.
  • The supplied-tax test runs for both taxes.

uses_property_allowance keeps main's charge-inclusive comparison and sums it over the benefit unit, as this PR did before.

To keep a single writer on this branch, please coordinate here before pushing: I'm continuing it through the impact re-run, review and hand-off to the UK hub. The PR body will carry the details.

@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

#2723 depends on this PR. Its certified aggregate-impact test (test_spending_never_rises_by_more_than_is_added_except_known_defects) fails on the defect fixed here, and passes on main.

  • What fails: when state pension rises £1, Council Tax Benefit rises in fewer than 10 households.
  • Why: the transferor's unused personal allowance crosses a £10 step, so the rounded transfer falls by £10 and the couple's income tax rises by more than the income added. Council Tax Reduction counts income net of tax, so the reduction rises.
  • Why only on Grow savings income by the OBR's savings income forecast to 2025-26 #2723: its lower 2025-26 savings income moves a few records onto such a step. Main's data happens to miss one.

The statutory transfer here (s.55B(4)-(6): a fixed £1,260, with the transferor's allowance cut by it) has no such step. I have not run the guard on the two branches together.

MaxGhenis and others added 4 commits October 28, 2026 17:15
Round-3 review of 8117a44 found tests that still asserted the old
mechanics:

- CPS switch suite (#2553): the expanded allowance is a Step 6 reduction
  at the basic rate, and a CPS rate alone keeps the income condition.
  The removed eligible_bands parameter is no longer read. Monotonicity
  is on the transferable amount; a couple that stops electing as the
  rate rises is pinned as intended. Bands are judged a penny lower,
  since tax_band calls taxable income at the basic rate limit higher.
- Pension Credit earnings oracle: the transferor's notional tax uses the
  allowance cut by s. 55B(6).
- Statutory paternity pay: the recipient's tax is monotone only while
  the election is unchanged.
- Marriage Allowance properties: tax before the winter fuel payment
  charge, which the election weighs; a couple whose election starts the
  charge is pinned.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
test_partial_setter_failure_restores_only_touched_input patched
child.set_input with a plain closure. Simulation.clone copies instance
attributes and rebinds only bound methods, so every branch the Marriage
Allowance election makes from the child kept the closure: the branch's
set_input wrote to the child, the branch never held its election, and
the election's formula branched again until the recursion limit (CI
shard 2/6 on 8117a44). Binding the setter to the child lets clone
rebind it to each branch; only the child's own write fails.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The conditions property judged tax_band a penny lower to step round
tax_band calling income exactly at the basic rate limit higher. That
missed an earner liable at the higher rate on less than a penny
(£50,270.005, stored as £50,270.00390625), so a £0.01 tolerance in the
condition could pass. The property now compares each earner's taxable
income with the 2025-26 limits, strictly, and checks both ways; the
sub-penny earner is an @example and a pinned case. A £0.01 tolerance
mutant now fails both tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
policyengine-core records each new branch in every calculation under way
(get_branch adds it to each open frame's created_branches), and those
frames keep it until they finish. So the two branches behind each
Marriage Allowance election, with their copies of the simulation's
arrays and their own results, lasted until the calculation that asked
for the election ended, though they were already out of
simulation.branches. tax_position now clears their storage when it drops
them. It clears it directly: delete_arrays and drop_computed_arrays would
record a change in each of those frames, though nothing reads the branch
again.

On a 2,033-household sample of the certified Enhanced FRS, the traced
peak while calculating one year's household_net_income is 48.4 MB on
main 41fa5dd, 77.1 MB on 5cedfaf and 52.0 MB here. Seven outputs over
three years are bit-for-bit the same as at 5cedfaf. CI's test shard 3,
which reaches 15.2 GB of a 16 GB runner on main, lost its runner at every
head of this PR since the 28 Oct merge.

test_the_election_releases_its_branches fails at 5cedfaf and passes
here.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis added a commit that referenced this pull request Nov 1, 2026
meets_marriage_allowance_income_conditions stops reading tax_band. It ports
liable_only_at_marriage_allowance_rates from #1963 and computes it from
income before the Marriage Allowance, since this head still takes the
allowance off earned_taxable_income: non-savings income up to the start of
the Scottish higher rate or the basic rate limit, savings above the limit
only where the savings nil rate and the starting rate for savings cover
them, and dividends counted without the dividend allowance (s. 55B(2)(ba)).
The CPS expanded reform reads the same helper, and the eligible_bands
parameter, which nothing else read, goes.

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

This branch has not been deployed

No deployments
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.

Marriage Allowance is booked on the recipient as the transferor's unused allowance, not the fixed s.55B transfer

2 participants