Conversation
…ncome uc_earned_income subtracted benunit_tax, the whole benefit unit's income tax and NI on all income, from the unit's gross earnings. UC Regs 2013 reg. 55(5)(b) and reg. 57(2) step 3 deduct only tax and NI paid by the person in respect of their employment or trade. - uc_income_tax_on_earnings: income tax on the person's earnings, taken as the lowest slice of their non-savings income after their allowances (savings and dividends sit above it under ITA 2007 s. 16, property income above it in the engine and under s. 16A from 2027-28). - uc_national_insurance_on_earnings: Class 1 employee, Class 2, Class 4. - uc_individual_earned_income: gross earnings less the person's own pension contributions, tax and NI, floored at nil. - uc_earned_income: sum over the unit, less the work allowance. Fixes #1942. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- test_uc_earnings_deductions_properties.py: unearned income never changes earned income; UC is non-increasing in every kind of unearned income; the tax deducted equals the tax on earnings alone; earned income is never lower, and UC never higher, than under the pre-fix formula. - test_uc_state_pension_properties.py: the #1942 strict xfail now passes and loses its marker; pound-for-pound and the property-income equivalence cover families with earnings; a new strict xfail pins the Marriage Allowance transfer, which the model books on the recipient as the transferor's unused allowance (ITA 2007 s. 55B gives a fixed reduction). - Document the earnings deductions in the UC docs page. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Sep 30, 2026
… at #1947 The docs edit left 'plus all of its unearned income' hanging off the earnings taper, which read as if unearned income were tapered. State the two deductions separately. The strict xfail's reason and the module docstring now cite #1947. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 1, 2026
…able pension contributions Review of #1949 found two defects in the new deductions. - uc_income_tax_on_earnings capped the tax on the earnings slice at the person's income_tax. When a tax reduction (the married couple's allowance) made that cap bind, the pension annual allowance charge and the High Income Child Benefit Charge raised the deduction, and so did more pension income. Reductions now come off the earnings slice first, as allowances do, so the deduction never includes a charge and does not move with other income. - uc_individual_earned_income deducted every pension contribution. Contributions paid after 75 are not relievable (Finance Act 2004 s. 188(3)(a)), so reg. 55(5)(a) does not deduct them. Adds YAML cases for both, draws a married couple's allowance in the property tests, and says in the test docstring that payroll giving (reg. 55(5)(c)) is not modelled. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…roperty tests The random generator rarely produced an earner old enough for the married couple's allowance, so the properties did not exercise tax reductions. A dedicated family shape now does. With it, restoring the old min(tax, income_tax) cap fails both the invariance and the differential property. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
uc_income_tax_on_earnings hard-coded savings, dividends and property as the incomes outside earned_taxable_income. A reform that adds another income to gov.hmrc.income_tax.earned_taxable_income_exclusions, such as one exempting pensions, then had that income subtracted twice, and the tax on earnings fell to nil. The formula now reads the list. Adds a YAML case with the reform as a parameter input. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 1, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…iew's counterexamples - test_uc_unearned_benefits_properties.py (added on main by #1958) had a strict xfail pinned to #1942, which this fix turns into a pass. Remove it and widen the earnings-free invariants to all families, with Marriage Allowance off as in the State Pension file (#1947). - The tests review found that random draws missed two mutations. Pin its minimized counterexamples as @example cases: a pound of State Pension for a pensioner just above the personal allowance with an earning partner, and a pound of pension for an earner just above it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> # Conflicts: # policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/income/uc_earned_income.yaml # policyengine_uk/tests/test_uc_unearned_benefits_properties.py # policyengine_uk/variables/gov/dwp/universal_credit/income/uc_earned_income.py
… property fixtures #1896 presumes that a member under 20 who is at least 16 years younger than the claimant is the claimant's child. The State Pension and earnings property tests generate couples such as 67 and 18 without relationship inputs, so the presumption would read them as a parent and child and the tests would pass without exercising a couple (including the tests review's pinned 67-and-18 example). Set is_claimant_or_partner from each generated role, as #1896 did for main's fixtures. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis
added a commit
that referenced
this pull request
Oct 2, 2026
After #1896, is_claimant_or_partner is inferred when not given, and an adult under 20 who is 16+ years younger than the other can be presumed a child. The generated families have at most two adults, both claimants, as #1949's fixtures now declare. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis
added a commit
that referenced
this pull request
Oct 2, 2026
…y fixtures Main's claimant-or-partner inference (#1896) presumes a member under 20 who is at least 16 years younger than the claimant to be the claimant's child. The work-related group properties generate couples such as 52 and 18 without relationship inputs, so after main was merged in the 18-year-old was read as a child, the reg 3(3) flag landed on someone outside the couple, and test_partner_who_cannot_be_a_joint_claimant failed (it fails the same way on 6db0ab1, before this branch's own changes). Set is_claimant_or_partner from each generated role, as #1949 did for its fixtures in a57caf1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Collaborator
Author
|
Coordination note from #1950 (UC capital income). #1950 adds Whichever of #1949 and #1950 merges second should:
#1950's merge is waiting on a decision from Max (over the £1bn/yr line). 🤖 Generated with Claude Code |
Collaborator
Author
|
#1950 merged (770bc06), so #1949 lands second. On rebase:
#2107 gives that file's generated adults explicit claimant roles, so rebase on it if it has merged. 🤖 Generated with Claude Code |
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
uc_earned_incomesubtractedbenunit_taxfrom the benefit unit's gross earnings. That is income tax and NI summed over every member, on all their income. So a claimant's tax on their own dividends, property or pensions came off their earnings, and any tax beyond a member's own earnings came off their partner's. The taper then bit less and UC was overstated.UC Regs 2013 reg 55(5)(b) and reg 57(2) step 3 deduct only tax and NI paid by the person "in respect of" their employment or trade. This PR deducts, per person:
The work allowance then comes off the combined earnings, and the taper applies.
The #1942 counterexample: a couple in 2026, a pensioner aged 70 with State Pension £16,000 and a partner aged 45 earning £13,000, council rent £20,000. UC goes from £5,297.16 to £4,919.86, the legal figure.
On the Enhanced FRS the change cuts 2026 UC by £0.487bn against current main, from 258k benefit units (176 records), with no gainers. 90% of that is tax on dividends, and 17 records with over £50,000 of dividends carry £0.396bn of it. About 45% (£0.219bn) is one member's tax no longer coming off their partner's earnings. Those units get UC at all only because of a separate capital-modelling problem (#1948).
#1943 (State Pension as UC unearned income), which this was stacked on, has merged. Fixes #1942.
Law (legislation.gov.uk, read 2026-09-30 from
/data.xml)UC Regs 2013 reg 55(5) (https://www.legislation.gov.uk/uksi/2013/376/regulation/55):
UC Regs 2013 reg 57(2) step 3 (https://www.legislation.gov.uk/uksi/2013/376/regulation/57):
Reg 22(1)(b) then tapers "their combined earned income" above the work allowance. So:
Which part of a person's income tax is "in respect of" their earnings
When one person has both earnings and other taxable income, the law does not say how to split their tax.
This PR follows the statutory words. It takes earnings as the lowest slice of the person's non-savings income, after the allowances they actually have (
uc_income_tax_on_earnings):The rule makes one invariant exact: unearned income never changes a person's deductions unless it changes their allowances (the personal allowance taper above £100,000, or Marriage Allowance). Tax reductions such as the married couple's allowance come off the tax on earnings first, as allowances do, so they keep the invariant. The property tests check this, including for working pensioners with a married couple's allowance. Tax refunds are an exception in law: reg 55(4A) counts a whole repayment as employed earnings, even tax on other income. The model does not model refunds.
For working pensioners whose State Pension is coded against their job, this deducts less than DWP probably does in practice.
The alternative is earnings as the top slice of non-savings, non-property income (rule C), which is closer to RTI when HMRC codes out a State Pension. In this model rule C never deducts less tax than this rule: its earnings slice is at least as wide and sits higher, and the marginal rates never fall. The runs agree, with no unit getting less UC under rule C. As a sensitivity, a real run of rule C cuts 2026 UC by £0.468bn instead of £0.487bn. The issue's own sketch, "tax on all income minus tax without earnings", would not fix a single claimant whose other income is within the personal allowance. For example, with £20,000 of earnings and £5,000 of property income it deducts the whole £2,286, the same as the bug.
Change
uc_income_tax_on_earnings(new, Person):earned_taxable_incomethat earnings occupy, reading the incomes outside that base fromgov.hmrc.income_tax.earned_taxable_income_exclusions, so a reform that changes the list is followed;earned_income_taxdoes;gov.hmrc.income_tax.income_tax_subtractions) off that tax first, floored at nil, so it never exceeds the person's tax and never includes the High Income Child Benefit Charge or the pension annual allowance charge.uc_national_insurance_on_earnings(new, Person):ni_class_1_employee+ni_class_2+ni_class_4.uc_individual_earned_income(new, Person):uc_mif_capped_earned_incomeless the person's own relievable pension contributions (none after 75: Finance Act 2004 s.188(3)(a)), tax and NI, floored at nil.uc_earned_income: the sum ofuc_individual_earned_incomeover the unit, lessuc_work_allowance, floored at nil.benunit_taxis no longer used by UC. It stays as a variable.Household results (hand-computed, in
uc_earnings_deductions.yaml)The YAML also covers:
The "before" figures are from the pre-fix formula, run as a scenario.
Enhanced FRS impact (real runs)
Setup:
enhanced_frs_2024_25.h5(sha256 e433e532…), one private copy per run.Microsimulationper package state, all from clean trees:dataset_impact.py,compare_impact.py,decompose.py,poverty_flips.py) and outputs are kept outside the repo.This PR against main:
Checks across every year:
Where the 2026 loss comes from. The fix does two things, and a real run separates them:
Step 1 is almost all couples (£0.215bn of it, 40 records), and £0.210bn of it is in units with over £10,000 of dividends. The pattern is a member whose tax, mostly on dividends, is larger than their own earnings, with the rest netted off their partner's earnings. A member with no earnings at all, as in the #1942 couple, accounts for only £0.00002bn.
By the kind of tax no longer deducted (
decompose.py: each losing unit's loss split pro rata over the deductions it no longer gets):Poverty. AHC child poverty rises 0.30pp in 2026-27 and 2027-28, 0.18-0.21pp in 2028-29 and 2029-30, and 0.02pp in 2030-31 (all people: at most 0.11pp). Each year it is one to three survey households crossing the AHC line. BHC poverty moves by at most 0.007pp: one household in 2025-26 and one from 2029-30. All of this is sampling noise, not a finding.
Invariants (tests)
policyengine_uk/tests/test_uc_earnings_deductions_properties.pyis a Hypothesis test (derandomized, 10 examples each). It runs over singles, couples and mixed-age couples with:uc_individual_earned_income, anduc_earned_income, unchanged.uc_income_tax_on_earningsequals theincome_taxthe same person pays with their earnings as their only income;uc_national_insurance_on_earningsequals their NI. Neither exceeds the person's own tax or NI.Invariants 1-3 keep adjusted net income under the £100,000 taper, keep ages below the married couple's allowance, and switch Marriage Allowance off, because a change in allowances legitimately changes the tax on earnings.
test_uc_state_pension_properties.py(from #1943):test_tax_on_state_pension_does_not_reduce_partners_earned_incomenow passes, and its marker is removed.with_earnings=Falseis gone), with Marriage Allowance switched off.test_state_pension_does_not_change_partners_marriage_allowancepins Marriage Allowance is booked on the recipient as the transferor's unused allowance, not the fixed s.55B transfer #1947.min(partner's unused allowance, £1,260).Results: the full YAML policy suite passes (1,394 tests), and both property files give 9 passed and 1 xfailed (the Marriage Allowance pin).
Not changed here (follow-ups)
axiom: TheAxiomFoundation/rulespec-uk#387 queued (regs 52, 53, 55 and 57 are not in the pinned corpus, so no signed encode is possible yet; the EFRS oracle bridge feeds
uc_earned_incomeinto reg 22, so it cannot test this)Merge
This merges on gates under Max's 2026-10-02 rule for PolicyEngine UK fixes under £1bn a year: CI green on the head, an independent review approving that head, and the impact above from real runs. #1943 has merged, so stack order allows it.
🤖 Generated with Claude Code