Repository navigation
Apply the UC minimum income floor to net earned income against a net threshold - #1973
Conversation
…threshold UC Regs 2013 reg 62 compares a self-employed claimant's earned income, after their own tax, NI and pension contributions, with the reg 90(2) individual threshold "converted to net ... amounts by ... deducting such amount for income tax and national insurance contributions as the Secretary of State considers appropriate" (reg 62(4)), and treats them as having that net amount. The model took max(gross earnings, gross floor) and then subtracted the tax and NI on the claimant's actual profits, so a claimant under the floor was treated as having the gross threshold less the tax on their actual profits. - uc_minimum_income_floor_gross: minimum wage x expected hours x 52 / 12, rounded down to whole pounds (reg 6(1A)(a)), times 12. - uc_minimum_income_floor_income_tax / _national_insurance: the notional income tax and primary Class 1 NI on the threshold as the person's only pay (the modelling choice for the Secretary of State's discretion; a parameter switches to Class 2 and Class 4). - uc_minimum_income_floor: the net floor. - uc_individual_earned_income_before_mif: actual earned income after the reg 55(5) / 57(2) deductions. - uc_individual_earned_income: reg 62(2) for single claimants and reg 62(3) for couples (the couple threshold is the sum of the claimants' floors). - uc_mif_applies: claimants only. - uc_mif_capped_earned_income is removed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… review findings - The notional NI now defaults to Class 2 and Class 4 on profits equal to the threshold. DWP's 2017 guidance on applying the floor says so, and the floors DWP has given claimants match it to the penny (1,144.15 a month in 2019-20, 1,556.30 in 2024-25); the parameter switches to primary Class 1. - The threshold keeps its pence: reg 6(1A)(a) rounds amounts calculated for reg 90 itself, and DWP's floors are unrounded. - The threshold uses the minimum wage for the person's age, never the apprenticeship rate (reg 90(2) names NMW Regs reg 4 and 4A(1)(a)-(c)). - A trading loss counts as self-employment for the floor (reg 64, ADM H4503) and is not set against employed earnings (reg 57(2)). - The couple is at most the two eldest claimants, so an adult child flagged as a claimant does not join the couple threshold. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The no-rounding evidence is the net floor of 1,642.72 a month that DWP supplied for 2025-26 on a threshold of 1,851.85 (University of Bath IPR, "Going it alone", 2025), and the floor set on a claimant's award at 1,144.15 a month in 2019-20. The 1,556.30 figure for 2024-25 came from a forum user, not DWP, so it no longer appears as DWP's. - Property tests now draw trading losses. Monotonicity excludes the one intended exception, a loss raised to exactly zero, which the model reads as no self-employment; a YAML test pins that edge. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The generated populations now include a couple with an adult child the model also flags as a claimant, and the test reference takes the couple as the two eldest claimants, as the model does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…c-mif-net-floor # Conflicts: # docs/book/programs/gov/dwp/universal-credit.ipynb
…c-mif-net-floor # Conflicts: # docs/book/programs/gov/dwp/universal-credit.ipynb
|
Formula changes in #1949, which this PR stacks on. Head b8555a2: three added commits on c98c53c, no history rewrite.
Change 3 is in |
|
Coordination with #1999 (fixes #1986: benefit cap earnings exception, targeted childcare, local CTR). #1999 needs earned income without the floor for reg 82(4), so it adds this PR's # Contributions paid after the person reaches 75 are not relievable
# (Finance Act 2004 s. 188(3)(a)), so reg. 55(5)(a) does not deduct
# them.
age_limit = parameters(
period
).gov.hmrc.pensions.pension_contributions_relief_age_limit
relievable_pension_contributions = person("pension_contributions", period) * (
person("age", period) < age_limit
)
tax_and_national_insurance = add(
person,
period,
["uc_income_tax_on_earnings", "uc_national_insurance_on_earnings"],
)
return max_(
0, earnings - relievable_pension_contributions - tax_and_national_insurance
)It also adds the FA 2004 s.188 reference. This PR has not merged #1949's 🤖 Generated with Claude Code |
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>
Main now gives no trading allowance on a profit above 1,000 when gross receipts are unknown (#1881), so the YAML expectations that took 1,000 off taxable profit are recomputed by hand: tax on 20,000, 22,000 and 24,000 (less 3,000 pension) of profit, and the earned income that follows. The "before this fix" figures are main's own (be877c8), from a real run of those households. Without the allowance, this claimant's tax and Class 4 are the ones the net floor deducts, so a profit below the gross threshold always leaves them below the net floor. The case showing the floor does not apply above the net floor, though below the gross threshold, now has gross receipts of 23,200 on a profit of 23,000: with 200 of expenses the trading allowance (ITTOIA 2005 s. 783AI) gives a taxable profit of 22,200. is_uc_claimant now follows is_claimant_or_partner, which flags at most two people, so the third-claimant case flags all three by input, and the comments no longer say the model flags an adult child. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Main (#1907) split Class 4 out of ni_liable into ni_class_4_liable: Class 4 is due for the whole tax year from anyone not over State Pension age on 6 April (SI 2001/1004 reg. 91(a)). The floor's notional NI still multiplied Class 4 by ni_liable, so a claimant who reaches State Pension age between 7 April and 6 October paid Class 4 on their actual profits but had none deducted from the threshold. In 2026 that is everyone aged 66 with the default birthday: notional NI 0 against 633.73, a floor 633.73 too high. Class 1 and Class 2 keep ni_liable; Class 4 uses ni_class_4_liable, matching the model's actual contributions. An exhaustive check over ages 16-80, birthday months 0-11, 2020, 2026 and 2027 and both NI bases finds no difference from ni_class_2 + ni_class_4 or ni_class_1_employee on the threshold. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The notional NI invariant now covers people who reach State Pension age during the tax year, for whom Class 4 stays due (SI 2001/1004 reg. 91(a)) while primary Class 1 and Class 2 stop. Ages 65 and 66 were never drawn, so the Class 4 liability mismatch fixed in the previous commit went unseen. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…the year The generated draws reach a 66-year-old in 2026 only by chance, so the notional NI differential gets two explicit examples: that person, under both NI bases. On the previous formula the self-employed example fails by the 633.73 of Class 4. The docstring now says the older-partner shape starts at 65, under State Pension age, and the over-pension-age YAML case is named for the start of the tax year. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Merged Merge (
After the merge:
Review. Independent Opus reviews via Subfleet:
Tests:
Impact. Re-run as three real Enhanced FRS runs: base 🤖 Generated with Claude Code |
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>
…age changes moved - Three cases with £15,000 of profit (uc_mif_earned_income.yaml, "the elder nominated by default"; uc_is_responsible_carer.yaml, "works more hours" and "a recorded nomination"): main's #1880 fix gives no trading allowance relief on a profit above £1,000 when gross receipts are not given, so income tax is (15,000 - 12,570) x 20% = 486, not 286, and earnings before the floor are 14,368.20. The floored partner is 20,386.03 - 14,368.20 = 6,017.83, and 30,960.75 - 14,368.20 = 16,592.55 where the nominated carer has a 16-hour threshold. - uc_minimum_income_floor.yaml, main's #1973 case for someone reaching State Pension age after 6 April: at 66 they are over State Pension Credit qualifying age, so in section 19 (reg. 89(1)(a)) with no threshold. Set the group as the age-70 case does, to keep testing the notional Class 4. Review: vahid-ahmadi on #2081, item 1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…text - uc_mif_earned_income.yaml, "Couple from gross incomes": the children became 15 and 14 in this PR (to keep 35 expected hours), and the universal_credit line was dropped. Both were born before 6 April 2017, so the first has the higher child element: uc_maximum_amount is 666.97 x 12 + (351.88 + 303.94) x 12 + 12,000 = 27,873.48 and universal_credit is 27,873.48 - 0.55 x 35,648.06 = 8,267.05. - universal-credit.ipynb: the nomination default ends with the elder and hours_worked defaults to 0; the responsible carer's hours are 30 only from February 2025 (16 and 25 from April 2017); the couple threshold includes 35 hours at the national living wage for a reg. 3(3) partner. Review: independent review of 5ed0fd4, findings 3 and 4. 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
The Universal Credit minimum income floor (MIF) compared the wrong things.
uc_mif_capped_earned_incometookmax(gross earnings, gross floor), where the gross floor was the minimum wage × 35 hours × 52.uc_individual_earned_incomethen subtracted the tax and NI on the claimant's actual profits. So a self-employed claimant below the floor was treated as having the gross threshold less tax they never paid on it. For most of them (no tax or NI on small profits) that meant the whole gross threshold.UC Regs 2013 reg 62 says something different:
A 2026 example: a single claimant aged 30 with £5,000 of profits and council rent of £9,600.
On the Enhanced FRS this raises 2026 UC by £0.270bn. About 197,000 benefit units gain (151 records, a mean of £1,458 a year). About 3,600 lose (8 records), all with pension contributions. The UC caseload rises by about 2,300.
Builds on #1949 (per-person tax on earnings, which fixes #1942) and #1943, both now merged into
main.Law (legislation.gov.uk, read 2026-09-30 from
/data.xml)UC Regs 2013 reg 62 (https://www.legislation.gov.uk/uksi/2013/376/regulation/62):
Reg 90(2) (https://www.legislation.gov.uk/uksi/2013/376/regulation/90):
Reg 90(3)(a) makes the couple threshold for joint claimants "the sum of their individual thresholds". The rate is the one for the person's age: reg 4A(1)(d), the apprenticeship rate, is not in the list.
The Secretary of State's deduction (the modelling choice)
Reg 62(4)(b) leaves the tax and NI deduction to the Secretary of State. The model deducts what a self-employed person would pay if the threshold were their only income:
This is DWP's own method, on the evidence:
DWP's guidance "Applying the Minimum Income Floor", version 9 (3 April 2017, deposited in Parliament as DEP2017-0556), step 4: "Taking off the Income Tax and Class 2 and Class 4 NI contributions payable on actual earnings at that level, to get an assumed net monthly income - this is the claimant's MIF." Later versions say "appropriate NI contributions" (v12, 2018) and "Notional tax and National Insurance payable on actual earnings" (v16 to v23, 2021 to 2025). None names Class 1.
Figures DWP issued or applied match this basis. Each row below is the minimum wage × 35 × 52 / 12, less income tax and NI, computed monthly at that year's statutory rates with no rounding of the threshold. The last two columns show the alternatives.
A May 2024 thread agrees. DWP told the claimant "£1556", which fits self-employed NI (£1,556.30) and not Class 1 (£1,542.59). The pence there came from another forum user, so it is a consistency check only.
The 2013 original text of reg 62 made the deduction "an amount that the Secretary of State considers appropriate to take account of any income tax or national insurance contributions for which the person would be liable in respect of the assessment period if they had earned income of that amount" (https://www.legislation.gov.uk/uksi/2013/376/regulation/62/2013-04-29). It was discretionary then too, but tied to tax on the threshold itself, not on actual profits.
The other reading is employee NI. DWP's self-employment quick guide describes the floor as "the amount of money an employed person in a similar situation to you would earn on the National Living Wage or National Minimum Wage, after tax and National Insurance". LITRG's illustrative example ("say £225.07" for 2025-26) uses Class 1. The parameter
gov.dwp.universal_credit.means_test.minimum_income_floor.self_employed_national_insurance(defaulttrue) switches to primary Class 1 when set tofalse. A real run with it set tofalseraises 2026 UC by £0.294bn, against £0.270bn by default.No whole-pound rounding. Reg 6(1A)(a) disregards "a fraction of a pound" in amounts calculated "for the purposes of ... regulation 90", the conditionality earnings threshold. The floor is calculated for reg 62. The two pence-level DWP figures above come from the unrounded thresholds (£1,851.85 and £1,245.18), and rounding down first would give £1,642.09 and £1,144.02. The ADM's illustrative example rounds to whole pounds (£1,016 less £70). Reg 62(4)'s reference to "the amounts set out in regulation 90(2)" could be read either way; the model follows DWP's figures.
Change
uc_minimum_income_floor_gross(new): the minimum wage for the person's age (never the apprenticeship rate) × expected hours × 52.uc_minimum_income_floor_income_tax,uc_minimum_income_floor_national_insurance(new): the notional deductions above.uc_minimum_income_floor: now the net floor, gross less both deductions.uc_individual_earned_income_before_mif(new): actual earned income. That is earnings less the person's own relievable pension contributions (none from age 75, Finance Act 2004 s.188(3)(a)) and their own income tax and NI on those earnings. A trading loss counts as nil self-employed earnings; reg 57(2) does not set it against employed earnings. Deduct only each person's own tax and NI on earnings from UC earned income #1949 put this inuc_individual_earned_income.uc_individual_earned_income: applies reg 62(2) and (3).is_uc_claimant(nowis_claimant_or_partner) flags at most two. Where inputs flag more, the two eldest are the couple.uc_mif_applies: now claimants only (reg 62 applies to "a claimant"), and a trading loss counts. Gainful self-employment is a trade carried on "in expectation of profit" (reg 64), and ADM H4503 applies the floor in a loss-making period.uc_mif_capped_earned_income: removed. Nothing else in this repo, policyengine-uk-data, policyengine.py or policyengine-api uses it.test_fix_only_removes_deductions) compared against the pre-UC earned income deducts the whole benefit unit's tax, including tax on pensions and other unearned income #1942 formula, which useduc_mif_capped_earned_income. It now runs with everyone in a start-up period, because the net floor moves earned income in either direction.Known gaps (follow-ups, not in this PR)
uc_is_in_startup_periodand has no gainful self-employment test (reg 64). So every self-employed claimant in a UC benefit unit is treated as subject to the floor: 309,000 people in 2026.uc_earned_incomesums the claimant and partner and the benefit unit's UC children and qualifying young persons, so a dependent qualifying young person's earnings count towards the claimants' UC. That predates this PR.Tests
YAML tests are hand-computed from the law at the rates the model holds, in
income_floor/uc_minimum_income_floor.yaml,income_floor/uc_mif_earned_income.yamlandincome_floor/uc_MIF_applies.yaml. They cover:Property tests (
test_uc_minimum_income_floor_properties.py, Hypothesis) run over generated populations of single people, couples, couples with an older partner aged 65 to 80 (mixed-age once past State Pension age) and couples with an adult child in their benefit unit, in England, Wales and Scotland, in 2020, 2026 and 2027. The notional NI differential also always runs a 66-year-old in 2026, who reaches State Pension age after 6 April, under both NI bases. Each invariant below holds for every input.Invariants
max(E, T − max(0, T + E_partner − T_couple)), written in the test from the regulation. The two forms agree because the couple threshold is the sum of the individual thresholds.income_taxof the same person paid the gross threshold as their only income.ni_class_2 + ni_class_4with profits of that amount (default basis), or theirni_class_1_employeeon pay of that amount (parameter switched).uc_earned_incomeand never raise UC before the benefit cap. The property draws profits from −£10,000 to £40,000. The one intended exception is a loss raised to exactly £0, because the model reads zero profit as no self-employment (see known gaps). Before this PR, UC rose with the profits of a self-employed claimant under the floor: profits of £20,000 gave £715 more UC than £15,000 in 2026, because more tax on actual profits came off the gross threshold.Marriage Allowance is off in the property tests. A transfer can lower one partner's earned income when the other's earnings rise, which is lawful and not part of this change.
Impact (Enhanced FRS, real runs)
Base:
mainatbe877c81c(2.115.0, which includes #1949 and #1943). Branch:c711defaa, this PR merged with thatmain(later commits change only tests). No run had uncommitted tracked changes. All three use a copy of the Enhanced FRS 2024-25 (enhanced_frs_2024_25.h5, sha256e433e532…), one realMicrosimulationper state. The Class 1 column is a separate real run with the parameter set tofalse. Benefit units are counted in thousands, with records in brackets. Poverty is in percentage points.How 2026 decomposes, for people in UC benefit units:
salary_sacrifice_returned_to_income). So most 2029 losers are people to whom the data give salary sacrifice but no pay.Every data invariant holds in every year: the floor never lowers anyone's earned income, leaves everyone it doesn't apply to unchanged, and never lifts anyone above their own floor.
Earlier runs, before #1949 merged, gave +£0.281bn for 2026 with base
c98c53c58and branchae8f59fd1(impact/run2-base-c98c53c58); +£0.281bn for 2026 with based0fc22f39and branchee16391c4(impact/run1-base-d0fc22f39).Axiom parity
axiom: uk:regulations/uksi/2013/376/62 encoded-correct (rulespec-uk
uk/regulations/uksi/2013/376/62.test.yaml:single_claimant_below_individual_threshold_is_deemed_to_threshold,couple_claimant_uses_surplus_allocation_with_lowest_combined_income); composed UC pipeline couple floor TheAxiomFoundation/rulespec-uk#396 queuedThe reg 62 module passes all 13 law-derived cases in the issue, including this PR's net floor and couple cases (its deduction is a leaf). The composed UC pipeline applies one floor to the couple's combined earnings, so it fails 4 of 9 couple cases. Example: a self-employed claimant on £400 a month with a partner on £1,000 is deemed £927.68 a month too high. rulespec-uk#396 has the dispatch-ready re-encode (
review_finding, companion tests from the same figures as these YAML tests). Signed encodes are OpenAI-billed, so dispatch is Max's call.🤖 Generated with Claude Code