Repository navigation
Conversation
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>
…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>
|
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 |
|
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. |
…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>
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>
The two take-up cases both had the gaining spouse as the elder, so they could not tell the gaining spouse's draw from the elder spouse's. Add the same cases with the electing spouse older. A mutant that reads the elder spouse's draw survived the YAML before and fails both now. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
policyengine-core 3.33.0 makes an input set on a branch invalidate the values derived from it. The election's branches set the election and the allowance cut, so under 3.33 they dropped the property allowance route they had inherited and chose one of their own for their counterfactual. CI (core 3.33.2) caught it in test_no_one_lowers_their_tax_by_switching_ route_alone: a partner with receipts within the allowance was taxed as electing on actual expenses (269.79), where the couple would not elect. Pass the settled route to both branches as an input. Under 3.32.27 the branches already inherited it, so nothing changes there; under 3.33.2 the failing example now gives the same taxes as under 3.32.27. A 2027 YAML case pins it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The cleanup test's couple never elected (electing cost exactly what it saved) and its landlord had no receipts, so neither branch ran. Use a couple who elect every year, with receipts within the property allowance and finance costs. Give generated adults gross receipts too, so the property route is a choice weighed in branches in most examples. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
policyengine-core 3.33 (#560) makes an input set on a branch drop what the branch holds that may have been calculated from the value it replaces. The election's branches set the election itself, which the simulation may first have calculated inside branches of its own: the property allowance route's, a CPS marriage-neutral reform's split of adjusted net income, or a labour supply response's measurement. Everything stored after that is dropped in the election's branches and calculated again there, with the election held off, so each of those choices could be made afresh for the counterfactual. 0c9c4d6 held the route; this holds the rest of what the formula resolves before branching (now including employment income) at the simulation's values. Under 3.32.27, which the lockfile pins, branches already inherit these values, so nothing changes there. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Marriage Allowance received is a Step 6 tax reduction (ITA 2007 s.55A(2), s.55B(1)), so it leaves the non-savings band base at 30,000 - 12,570 = 17,430; the case pinned the old 16,170 until this fix. 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>
|
The last description update dropped this PR's 🤖 Generated with Claude Code |
Fixes #1947.
Builds on #1949 (the #1942 fix, 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):
fixed_amountparameter)would_claim_marriage_allowancewould_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:
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'sis_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 toincome_tax_subtractions.Changed:
marriage_allowanceis 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 fromearned_taxable_income_exclusions.personal_allowancesubtractsmarriage_allowance_relinquished.meets_marriage_allowance_income_conditionsis the rate test above.Removed:
partners_unused_personal_allowance: it is not part of the law and nothing else used it.eligible_bandsparameter.The CPS expanded-MA contrib reform now overrides
meets_marriage_allowance_income_conditionsandmarriage_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 whenremove_income_conditionis set; before, raising onlyma_ratelifted 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_electionruns two policyengine-core branches, the pattern PE-US uses for itemisation: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:set_inputon 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) sets the Step 6 reductions inincome_tax_subtractionsagainst 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 xfailtest_state_pension_does_not_change_partners_marriage_allowancenow passes.Known interaction (intended): the election weighs the couple's income tax, 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_taxpins 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.Departures in the issue, each now a YAML test
Also covered:
meets_marriage_allowance_income_conditions.yamlis 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:UC properties now run with Marriage Allowance claimed:
test_uc_state_pension_properties.py: non-increasing, pound-for-pound, the private-pension equivalence, and property income excluded while State Pension counts;test_uc_earnings_deductions_properties.py: invariants 1-3, with Deduct only each person's own tax and NI on earnings from UC earned income #1949's married couple's allowance cases;test_uc_income_from_capital_properties.py: invariant 2.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 testing is part of the GPT-6.1 Sol review now running at 99182e3; results go here when it reports.
Enhanced FRS impact
Real 2026
Microsimulationruns on a private copy of the Enhanced FRS 2024-25 (enhanced_frs_2024_25.h5, sha256e433e532…), main (e01f2f5) against this branch (99182e3), both trees clean. There are four runs: main, branch, and each with no one claiming (would_claim_marriage_allowancefalse for all). Branch minus main, 2026-27:Differential check: with no one claiming, main and branch agree exactly on all 32 recorded person, benefit-unit and household arrays. So this changes nothing but Marriage Allowance.
2026 decomposition (aggregates only):
Take-up: the dataset draws
would_claim_marriage_allowanceper person at 50%. The couple's election uses the gaining spouse's draw, the same draw that decided take-up before. A first version used the elder spouse's draw, which moved take-up between couples for no legal reason, so it was dropped.Cost of the election's branches: six years (2025-2030) in one Enhanced FRS simulation, main against this branch. Byte-compiled,
/usr/bin/time -l, two runs each; logs in the review folder (impact/timing_1012/):The branches share the simulation's cached arrays until they write (policyengine-core ≥3.32.12). On core 3.32.9 the same design took peak memory from 3.54 to 5.83 GB.
Not modelled, and limits inherited from the model
Not modelled:
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):
Main has since fixed two limits this PR first listed: savings and dividends now band above property income, and a Scottish taxpayer's personal savings allowance is set on UK bands.
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_creditslumps it with other reductions. Total liability is unaffected; only the split of the reduction is.SPI dataset:
policyengine-uk-data's SPI build setsmarriage_allowanceas an input whereMAIND == 1. SPI: keep Marriage Allowance on recipients (MAIND) and give transferors their cut allowance policyengine-uk-data#502 (approved; it waits on this PR) establishes thatMAINDmarks recipients, keeps the input there, and gives transferorsmarriage_allowance_relinquished.Axiom
axiom: TheAxiomFoundation/rulespec-uk#362 queued (s.55C; extended on 2026-09-30 for this PR with s.55C(1)(ca) and s.55B(2)(ba) and seven companion cases) + the s.55B re-encode queued in rulespec-uk
bulk/worklist.yaml(batch PE-UK-CHILD-DEFS), which already requires the fixed transferable amount, the s.55B(6) cut and the s.55B(1) reduction.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
20261012-084806-ma1963-sol-review-2).🤖 Generated with Claude Code
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