Repository navigation
Deduct only each person's own tax and NI on earnings from UC earned income - #1949
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>
… 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>
…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>
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>
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>
…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>
|
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 |
|
#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 |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- #1950: rent and dividends are not UC unearned income (reg. 66(1) is a closed list), so the single parent's unearned income is nil (UC 11,308.50) and the self-employed claimant's is the pension alone (UC 3,975.29). - #1881: without gross receipts, a trade profit above the trading allowance gets no allowance (ITTOIA 2005 s. 783AI), so the tax on the trade is (18,000 - 12,570) x 20% = 1,086. The tax and NI this PR deducts are unchanged in method; the cases' gaps against the pre-fix formula are still 0.55 x the tax on other income. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… unchanged The widened award clause of test_interest_dividends_and_rent_leave_the_award_unchanged gives one adult up to 270,000 of interest, dividends and rent beside up to 30,000 of earnings. Above 100,000 of adjusted net income the personal allowance tapers (ITA 2007 s. 35(2)), which raises the tax on the person's earnings and so the reg. 55(5)(b) deduction: the exception this PR states. Compare only benefit units whose personal allowances the income leaves unchanged, as test_uc_earnings_deductions_properties.py does by keeping adjusted net income under the taper. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…anged Review of c6412ee found the widened capital-income award invariance still false for the award after the cap. The model's earnings exception to the cap (reg. 82(1)(a)) nets tax on all of a person's income off their earnings (#1986), so dividends can switch the cap on while allowances and UC earned income are unchanged: a single London claimant earning 11,000 with 10,000 of dividends loses 2,081.80 to the cap. - Compare universal_credit only where is_benefit_cap_exempt is also unchanged; the other variables keep the personal allowance filter. - Pin the case as a strict xfail against #1986. - Qualify the YAML header's ITA 2007 s. 16 ordering comment. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…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>
# Conflicts: # policyengine_uk/tests/test_uc_income_from_capital_properties.py # policyengine_uk/tests/test_uc_state_pension_properties.py
|
UK hub merged main into this branch at
|
|
Merged by the UK hub under the PE-UK merge-on-gates rule:
|
Brings the branch up to main at be877c8, which includes #1949 (fc1ecc4) and #1943. Conflicts: - docs/book/programs/gov/dwp/universal-credit.ipynb: main's notebook with this PR's minimum income floor paragraph re-inserted after the earnings-deductions sentence. - uc_individual_earned_income.py: this PR's reg 62(2)/(3) floor. Main's Finance Act 2004 s.188(3)(a) age filter on pension contributions is ported to uc_individual_earned_income_before_mif, where this PR takes the deductions; the file is copied as is from #1999 (blob 73a9370) so the two PRs merge in either order. - test_uc_earnings_deductions_properties.py: main's file with this PR's start-up-period note, the pre-#1942 gross earnings and invariant 4's start-up setting. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Resolves conflicts with #1896 (claimant and partner), #1950 (no capital income in UC), #1949, #1973 and #2088 (reg. 32 exceptions): the income sums, the childcare work condition and the minimum income floor read is_uc_assessed_claimant; fixtures that relied on the old is_uc_claimant fallback flagging an adult child now supply the roles; the reg. 22 differential expects pensions plus tariff income, since savings interest is not reg. 66(1) income for anyone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…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. (cherry picked from commit 0dfcc26) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-3-single-claim #2081, #1973 and #1949 have merged, so main now carries this PR's base. Conflicts and their resolutions: - uc_individual_earned_income_before_mif.py: main's version (the age-75 pension contribution limit as #1949 landed it). uc_individual_earned_income.py and test_uc_earnings_deductions_properties.py merged cleanly and equal main's; so does test_uc_work_related_groups_properties.py, which now declares is_claimant_or_partner itself. - utils/uc_work_related_requirements.py: main's claimants() (it reads is_uc_assessed_claimant, which replaced couple_members() with the same formula), plus this PR's single-claim helpers. - uc_member_of_couple_claims_as_single_person.py and uc_is_ineligible_partner.py read is_uc_assessed_claimant instead of the removed couple_members(); no output changes. - is_uc_eligible.py: both docstrings' sentences. - uc_childcare_work_condition.py: #2088's reg 32 formula with this PR's reg 3(3) split, as posted on #2084: limb (a) for the s.40 claimant (is_uc_single_or_joint_claimant), limb (b) for every other member of the couple (is_uc_assessed_claimant), and reg 32(2) treatment only for a claimant. - is_benefit_cap_exempt_other.py merged without a conflict but lost `person = benunit.members`, which main removed and this PR's reg 83 line reads; restored. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.549bn against current main, from 292k benefit units (256 records), with no gainers. 90% of that is tax on dividends, and 43 records with over £50,000 of dividends carry £0.440bn of it. About 42% (£0.231bn) 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.530bn instead of £0.549bn. 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 on this branch. Both rows reflect main's #1950 (property income and dividends are not UC unearned income) and #1881 (the trading allowance is not given without gross receipts).
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,capital_high_dividend.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.224bn of it, 46 records), and £0.222bn 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 under £0.0001bn.
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.02pp in 2028-29 and 2030-31, and not at all in 2025-26 and 2029-30 (all people: at most 0.11pp). Each year it is at most one survey household crossing the AHC line. BHC child poverty rises 0.11pp from 2027-28 (all people: at most 0.03pp), one survey household, two 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, including working pensioners with a married couple's allowance, 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 and switch Marriage Allowance off, because a change in allowances legitimately changes the tax on earnings. A married couple's allowance is covered: one family shape has a working partner aged 93-96 with the allowance, whose reduction comes off the tax on earnings first.
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).Main's own property files had pinned #1942 as strict xfails; this PR removes them and widens the invariants:
test_uc_unearned_benefits_properties.py(Count contributory ESA, Maternity Allowance, industrial injuries benefit and capped Carer Support Payment as UC unearned income #1958): the award invariants now cover every family, with Marriage Allowance off.test_uc_income_from_capital_properties.py(Stop counting interest, dividends and rent as Universal Credit unearned income #1950): "interest, dividends and rent leave the award unchanged" now covers working families too, with Marriage Allowance off, comparing the benefit units whose personal allowances the income leaves unchanged. Above £100,000 of adjusted net income the allowance tapers (ITA 2007 s.35(2)), which raises the tax on earnings: the exception stated above.test_tax_on_dividends_does_not_remove_the_benefit_cap_earnings_exceptionpins that case: a single London claimant earning £11,000 with £10,000 of dividends loses £2,081.80 to the cap, although reg 82(1)(a) rests the exception on earned income. Use each rule's own Universal Credit earned income in the benefit cap, targeted childcare and local CTR #1999 fixes it.Results at 8c88c7c (main 7668506 merged in): the UC YAML folder passes locally (240 tests) and CI runs the full suite. The four UC property files give 21 passed and 2 xfailed (the Marriage Allowance and benefit cap pins).
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 that PolicyEngine UK bug fixes merge on gates whatever their effect on aggregates: 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