Repository navigation
Fix the annual allowance on survey data, the UC benefit cap earnings threshold, relevant UK earnings and Scottish water uprating - #2536
Conversation
vahid-ahmadi
left a comment
There was a problem hiding this comment.
@juaristi22 thanks, this is my review at baf3bf28f. I've reviewed #2536's own changes only (diffed against main merged with the heads of #2488 and #1863).
No blockers. The law is right in all four parts and the touched tests pass. The benefit cap threshold and the water split can go in as they are once items 1 and 11 are settled. The annual allowance part is right, but item 3 matters before Microcosm stores a carry-forward value.
What I ran (fresh venv, no private data):
- Pensions YAML 144 passed, income tax reliefs YAML 60, the annual allowance property and taper tests 27.
- Benefit cap,
benefit_cap_by_scheme,uc_trading_lossandclaims_as_single_personYAML 167 passed. The water, projection-horizon, uprating-inputs, code-health uprating and benefit cap split tests: 61 passed. - On the base, the new YAML fails as it should: relevant_uk_earnings 3 of 4, defined_benefit_pension_input 6 of 6, unused_pension_annual_allowance_default 2 of 7.
- Not run:
test_annual_allowance_against_hmrc.py, which needs the certified dataset.
Findings
PR body
-
should:
axiom: neededis only for external contributors. As a maintainer the line should beencoded-correct,encoded,queuedorn/a: <reason>. The benefit cap threshold and relevant UK earnings are policy changes, soqueuedwith a rulespec-uk issue fits those. The water series is data plumbing, son/awould do for that part. -
should: say the minimum wage freezes the benefit cap threshold.
uc_benefit_cap_earnings_exception_threshold.py:43-52readsgov.hmrc.minimum_wage.non_apprentice, which ends at the April 2026 rate (£12.71). So the threshold is £10,572 in every year from 2026 to 2030 (I checked 2027, 2028 and 2030), while projected earnings grow. The share of capped families meeting the exception then creeps up each year. That gap is in the minimum wage parameter, not this PR, and the minimum income floor has it too. A line in the changelog or the docstring is enough here, plus a follow-up issue to project the national living wage.
Annual allowance and relevant UK earnings
-
should: a stored carry-forward value doesn't stay in its own year.
unused_pension_annual_allowance.py:20-21anddocs/engineering/pension-annual-allowance.md:194say a supplied value applies to its own year only. Butextend_single_year_dataset(data/economic_assumptions.py:228-229) copies every base-year column into each projected year, and the variable isn't inuprating_indices.yaml. So a 2024 value stored in Microcosm would be reused unchanged in every year to 2032. Under s. 228A the three-year window moves each year, so that's wrong. uk-data supplies no value today, so nothing breaks yet. Since Microcosm is the reason for this PR, either keep stored values out of the projected years, or say in the docs not to supply one until that's fixed. -
should: the defined-benefit path changes nothing on survey data yet. With
defined_benefit_input_per_contributionat 1,defined_benefit_pension_input_amountequals the contributions it replaces. Sopension_contributions_for_annual_allowanceis unchanged, even with a share supplied. The one exception is net-pay member contributions to a defined-benefit scheme from age 75, which now count. The body says "With no share supplied, nothing changes". "Nothing changes unless the data also supplies a valued input amount" would stop anyone reading the fall from 267,000 to 259,000 payers as a defined-benefit effect. That fall comes from carry forward and relevant earnings. -
should: say the ratio of 1 is a chosen midpoint. I re-derived the figures in
defined_benefit_input_per_contribution.yaml:9-20from the cited accrual and revaluation rates:- NHS: 16/54 + 10 × (1/54) × 16 × 1.5% = 34% of pay.
- Teachers: 33%.
- Alpha: 16 × 2.32% = 37%.
- LGPS: 16/49 = 33%.
The ratios (0.94-1.18, 0.81-0.90, 1.00-1.10, 0.97-1.23) follow from the cited contribution rates. Three things should be written down. The ten years of service and the step to exactly 1 are judgement calls. The reasoning holds only because revaluation has fallen on 6 April since 2023, so it shares the s. 235 CPI; it fails for pension input periods before 2023-24. Private-sector defined-benefit schemes, whose employer rates are often much higher, would push the ratio below 1.
-
should:
prior_year_input_ratiois an assumption, and its label is wrong. HMRC publishes no count of carry-forward use (prior_year_input_ratio.yaml:15). So the description should say 1 is a convention, not a value drawn from statistics. The label (:22) says three previous years, but the formula uses five. Also, a ratio of 1 with smooth earnings growth almost rules out carry forward for anyone over this year's allowance. Real excesses are lumpy (defined-benefit promotions, one-off contributions), so this probably understates carry forward. The docs should say which way the bias runs. -
should: the xfail puts the whole HMRC gap down to one cause. HMRC's Private pension statistics (30 July 2026) confirm the PR's targets:
- Table 7: 30,440 Self Assessment payers in 2024-25 (provisional).
- Table 6: £300m of charges.
The model's 259,000 payers and £3.5bn are about 8.5 and 12 times those.
test_annual_allowance_against_hmrc.py:36and the allowlist name only policyengine-uk-data#606. But employer contributions are 69% of payers' input, so even removing the threefold imputation may not bring the count within 1.5 times. Either show a quick sensitivity (for example employer contributions divided by 3), or word the reason as "known data gaps including #606". Otherwise the strict xfail can survive #606 being fixed and get blamed on the wrong cause. Optional: Table 7's £672m of excess contributions is a cleaner second target, since it doesn't depend on marginal rates. -
should: cite the authority for deducting employment expenses.
relevant_uk_earnings(utils/pensions.py:26-39) now usestaxable_employment_income, which is net ofemployment_expenses. FA 2004 s. 189(2)(a) says only "employment income", and PTM044100 is silent on expenses. The reading is defensible through ITEPA ss. 9(2) and 11 (the amount charged is net taxable earnings). But by that route the net-pay deduction would come off too, and the PR keeps it in. Please cite ss. 9 and 11 in the docstring and inrelevant_uk_earnings.yaml, and say why pension deductions stay in. The "allowable employment expenses" test case depends on this reading. -
nit: carry forward over five years slightly under-counts. The year-3 excess should draw on year 6 first, but year 6 is outside the window (
unused_pension_annual_allowance.py:7). The 2015-16 alignment rules are also ignored when 2020-21 looks back. This is rare with a ratio of 1, but worth a line in the docstring. Separately, furnished holiday lettings income counted as relevant UK earnings before 2025-26 (s. 189(2)(ba), (bb)), and the model has no such variable. That's also a line in the docstring. -
nit: most of the carry-forward cases also pass on the base. Four of the seven do: excess every year, supplied override, no input, and pre-2020 nil. Only "steady saving" and "earliest first" test the new logic. A case where the taper binds only in an earlier year would help.
Benefit cap and water
-
should: source the 2027-29 Scottish water rises, or use 4%. The 3.9%, 3.9% and 4.1% in
yoy_growth.yaml(scottish_water, 2027-01-01 to 2029-01-01) andwater-bills.mdrest on "WICS's CPI assumption of 1.9%, 1.9% and 2.1%". The cited WICS page gives the CPI + 2% cap and a real-terms bill table, but no CPI path, and I couldn't find those figures in any WICS document. Either cite the page and table they come from, or use the model's own CPI (OBR March 2026, 2.0%) plus 2%, which gives 4.0%. Either way, the Final Determination on 29 October replaces them, so a tracking issue would help. -
nit: the £430 parameter starts on 2013-04-29.
earnings_exception/fixed_monthly_amount.yaml:8gives that date, so the threshold for 2013 raisesParameterNotFoundError. 2013-04-01 would remove one blocker for early years. -
nit: one row in
docs/book/validation/hbai.md:111is stale. It still says water is uprated by CPI "as proxy". My #2527 updates that row, so please carry it over.
Overlap with my PRs
- #2527 (Scottish water): the published years (4.2%, 5%, 8.8%, 9.9%, 8.67%) match value for value, and so do Northern Ireland flat and the Ofwat fallback. #2536 also resolves the #1863 conflict. Both add
changelog.d/scottish-water-uprating.fixed.md, so they can't both merge. I'll close #2527 as superseded once this lands. Keeping theupratingattribute after 2032, as you do, is fine by me. - #1863: the merge of its current head is clean. The only changes to it are the intended ones: water is dropped from the Ofwat entry and added to
KNOWN_UNUPRATED. The Ofwat values and docs are untouched. #1863 is going into the merge queue first, as your body assumes. If this were squash-merged with #1863 still open, #1863 would conflict. - #2386 (docs): after this merges,
pension-tax-relief.mdandpensions-comparison-stbm.mdthere say carry forward defaults to nil, that defined-benefit accrual counts only through contributions, and give the old relevant-earnings definition. The £70,000 worked example still holds. I'll update those passages on my side, so nothing is needed here.
What looks right
-
Carry forward: it matches s. 228A. It uses the earliest year first (s. 228A(7)), an excess is used up only where it cut the charge (s. 228A(6)), and the window is three years.
-
Taper:
annual_allowance_for_yearreproduces the pre-2020 (A - £10,000)/£60,000 and post-2020 one-half rates, the whole-pound floor, the s. 228ZA(3)(b) threshold and the £4,000 and £10,000 minimums. -
Adjusted income: money purchase input plus the defined-benefit share equals the old total. Adjusted income becomes defined-benefit input less member contributions, per s. 228ZA(4).
-
Trading profits: taken after capital allowances and the trading allowance, and before brought-forward losses, which matches s. 189(2)(b).
-
Earlier PRs kept: #2464's SSP and SMP are still in. #2488's relief-at-source-only £3,600 rule is kept, because
adjusted_total_incomenow callspension_contributions_relief_amount, and I checked it is equivalent to #2488's formula. -
Benefit cap threshold: right against reg 82(1)(a) and reg 6(1A)(za) as amended by SI 2017/138:
- 16 × £11.44 × 52/12 = 793.17, so £793 (2024-25);
- £846 (2025-26) and £881 (2026-27), matching GOV.UK;
- £430 in 2016 and £520 from April 2017.
The order is right: floor the monthly amount, then multiply by 12. No hard-coded 10,152 is left in formulas.
-
Water mechanism: a per-region map like council tax's. Northern Ireland is flat, an unknown region uses Ofwat, and an unrecognised label raises an error. Water is excluded from the post-2030 fallback and
test_water_charges_are_uprated_onceguards against double growth. -
Allowlist: its only new entry is the strict HMRC xfail.
🤖 Generated with Claude Code
|
@vahid-ahmadi thanks. Each item is addressed in 25b5c3d, except where noted below. PR body
Annual allowance and relevant UK earnings
Benefit cap and water
Overlaps. Thanks for closing #2527 once this lands. #1820 and Max's draft #1999 also touch the reg 82 threshold, with a dated 🤖 Generated with Claude Code |
Aggregate impact on certified data: passBase Checks: 0 broken by this change, 1 failing on both sides, 0 fixed. Moves flagged for review: 0 (thresholds in Notes
Moves flagged for reviewNone. Receipts by tax head
Spending by benefit and service
Caseloads
Government balance, each component counted once
Model totals
Household income totals
Poverty rates
People in poverty
Inequality
Mean household net income in each side's own deciles
Standard reform costings (change in gov_balance)
Standard reforms: change in each reform's target
Household net income by decileMean 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.
No change in any year: components the model counts twice; population. Checks failing on both base and head (1)
Counted-once checkEach of 84 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.
Provenance
Dataset: Receipts by tax head: head levels
Spending by benefit and service: head levels
Caseloads: head levels
Government balance, each component counted once: head levels
Model totals: head levels
Components the model counts twice: head levels
Household income totals: head levels
Population: head levels
Poverty rates: head levels
People in poverty: head levels
Inequality: head levels
Mean household net income in each side's own deciles: head levels
Standard reform costings (change in gov_balance): head levels
Standard reforms: change in each reform's target: head levels
|
vahid-ahmadi
left a comment
There was a problem hiding this comment.
@juaristi22 thanks for the quick turnaround. This is my second look, at 25b5c3dfb. The delta since my review at baf3bf28f is your one commit, with no main merges, so I read all of it.
Every item from my first review is settled: 12 fixed, 1 withdrawn. One new item stops me approving: the branch now conflicts with main, because #2462 merged this morning, and fixing that touches the annual allowance code itself. Once that merge is in and the tests below pass, I expect to approve.
What I ran (fresh venv, no private data):
- Carry-forward default, relevant UK earnings and benefit cap YAML: 116 passed, including the new earlier-year taper case.
test_unused_annual_allowance_projection.py,test_water_uprating.pyandtest_projection_horizon.py: 27 passed.- With
YEAR_SPECIFIC_INPUTSemptied, both projection tests fail (2 failed), so they guard the fix. - CI at
25b5c3dfbis green, including the Python install jobs.
Earlier items
- Axiom line: fixed. rulespec-uk#399, #481 and #444 are queued, and the data assumptions are
n/a. - Frozen threshold: fixed. The docstring and changelog now say it holds at £10,572 after 2026-27, and #2537 tracks the national living wage.
- Stored carry-forward: fixed.
apply_single_year_upratingdropsYEAR_SPECIFIC_INPUTSfrom each projected year (data/economic_assumptions.py:302-307). Years after 2032 have no stored value either, so they fall to the formula. The new test fails without the drop. - Defined-benefit wording: fixed. The body and
pension-annual-allowance.md:191-195now say results move only once data supplies a valued input amount. - Ratio of 1 as a midpoint: fixed.
defined_benefit_input_per_contribution.yaml:18-26names the judgements, the 2023-24 start and the private-sector direction. prior_year_input_ratio: fixed. It is now called a convention, the label no longer says three years, and the docs give the direction of the bias.- xfail cause: fixed. Thanks for running the sensitivity. With employer contributions at a third, all three figures land inside the band, and that backs "chiefly #606". The Table 7 excess is now a third check.
- ITEPA ss. 9 and 11: fixed. They are cited in
utils/pensions.py:29-36and the YAML header. The reason for keeping the s. 327(5) pension deductions in (the limit would otherwise depend on itself) is sound. - Docstring limits: fixed.
- Earlier-year taper case: fixed. I checked the arithmetic. 2019-20 adjusted income of 200,000 / (1.0614 × 1.059 × 1.003) = 177,400, which gives an allowance of 40,000 - floor(27,400.16 / 2) = 26,300.
- Scottish water 2027-29: fixed. I checked the Draft Determination PDF. Para. 9.1.3 gives the CPI + 2% core cap for the first three years, and Table 68 (p. 258) gives CPI of 1.9%, 1.9% and 2.1%. I'd missed that table, sorry. #2538 tracks the Final Determination.
- £430 date: withdrawn. You're right:
convert_to_fiscal_year_parameters(utils/parameters.py:119) only builds 2015 to 2040, so no April 2013 date would help. - HBAI row: fixed.
New
-
should: merge main and take #2462's treatment of returned salary sacrifice. GitHub shows the PR as conflicting. A local merge of
origin/main(68ce6a6fb) conflicts in three files:utils/pensions.pypension_annual_allowance_threshold_income.pypension_contributions_for_annual_allowance.py
Under #2462, a supplied
salary_sacrifice_returned_to_incomeis actual pay and an actual member payment. It is already insideemployment_incomeandemployee_pension_contributions. The NICs-only excess never enters either. So main dropped the- returnedsubtractions. This branch still has them:utils/pensions.py:79-82:annual_allowance_net_income, withremoved_pay=returned.utils/pensions.py:199:workplace_pension_contributions.pension_annual_allowance_threshold_income.py:29-43.pension_contributions_for_annual_allowance.py:49-61: the member contributions and the defined-benefitnet_pay.
If the merge keeps those lines, a supplied return comes off twice in the defined-benefit path. My suggestion: take main's convention, so drop
- returnedand theremoved_payargument. Keep this PR's defined-benefit share and therelevant_uk_earnings(person, period, hmrc)helper. Then re-run the annual allowance property and taper tests, the pensions YAML andtest_salary_sacrifice_ni_only.py.
For context, not a finding: the Aggregate impact comment compares against 5f4b81c47, which is from before #1863 and #2488 merged. So its flagged moves in pensioner poverty after housing costs (up to +116k in 2031-32) mostly reflect water being uprated at all (#1863), plus #2488. They don't come from this PR's country split. The rerun after the merge should show this PR alone.
Overlap
#1863 and #2488 are both on main now, so after the merge the diff will be this PR's changes only, as the body says. I'll close #2527 once this lands.
🤖 Generated with Claude Code
… wage Universal Credit Regulations 2013 reg. 82(1)(a) sets the earnings exception each year as the pay for 16 hours a week at the national living wage, times 52 and divided by 12, with any fraction of a pound disregarded (reg. 6(1A)(za)). Before 1 April 2017 (SI 2017/138) it was a fixed £430 a month. The model held it at the 2025-26 figure of £10,152 a year for every year. Add uc_benefit_cap_earnings_exception_threshold, read by is_uc_benefit_cap_exempt_earnings, with parameters for the hours and the pre-2017 fixed amount. The threshold is now £9,516 in 2024-25 and £10,572 in 2026-27. Part of #2523; the threshold part of #1986. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…mily in the gap The differential test's reference earnings exception still compared with a fixed 10,152 in 2026, so a working-age family earning between 10,152 and 10,572 would fail the invariant on the threshold, not the scheme split; the fixed examples happened to miss that range. The reference now reads uc_benefit_cap_earnings_exception_threshold, and a single claimant earning 10,400 joins the explicit examples. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…income adjusted_total_income gave contributions relief up to its own earnings figure (taxable employment income plus trading profits before losses brought forward), while pension_contributions_relief and the annual allowance tests used pay before expenses plus trading profit before capital allowances and the trading allowance. Finance Act 2004 s. 189(2) counts employment income chargeable to income tax, here pay and statutory pay less allowable expenses but before the pension deductions the relief itself gives, and income chargeable under ITTOIA 2005 Part 2, which is after capital allowances and the trading allowance and before losses. Move that definition into utils/pensions.py (relevant_uk_earnings and trading_profits_before_losses) and use it, with pension_contributions_relief_amount, in all four places. The relief-at-source limit on the basic amount is unchanged from #2488. Part of #2523; follows #2488. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ofwat's water bills series is the average household bill in England and Wales. Scottish Water sets Scotland's household charges within limits from the Water Industry Commission for Scotland, and they followed a different path: 8.8% in 2024-25, 9.9% in 2025-26 against Ofwat's 26%, and 8.67% in 2026-27. Northern Ireland has no domestic water charges. Add gov.economic_assumptions.yoy_growth.scottish_water.water_bills, with WICS's draft CPI + 2% cap for 2027-28 to 2029-30 until the Final Determination (29 October 2026), and uprate water_and_sewerage_charges by country in apply_single_year_uprating, as council tax is: England, Wales and unknown regions by Ofwat's series, Scotland by Scottish Water's, Northern Ireland not at all. The column was previously held flat to 2030 and then grown by the England and Wales index everywhere. It is kept out of the post-2030 fallback, reset with the other growth-factor inputs, and covered by the projection fingerprint. Fixes #2501. Part of #2523. Overlaps #1863, which lists the column under the Ofwat index: whichever lands second drops that line, and test_water_charges_are_uprated_once fails until it does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…st; document growth after 2032 The fingerprint read-set test now carries the water column, so it runs uprate_water_and_sewerage_charges; reading the two series by full path keeps every read inside the listed paths (dropping either path from the read set now fails the test). After the projection ends in 2032, policyengine-core grows the column by Ofwat's index in every country, as before; the docs say so. The Ofwat growth table in water-bills.md now matches the parameter. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…wance Survey data records contributions paid this year, but the annual allowance counts a defined-benefit arrangement's increase in the value of rights (Finance Act 2004 s. 234), not the cash paid, and adds unused allowance from the three previous years (s. 228A), which surveys do not record. - defined_benefit_pension_share (input, default nil) says how much of a person's workplace contributions (employer, net pay and salary sacrifice) go to defined-benefit arrangements. defined_benefit_pension_input_amount replaces them in pension_contributions_for_annual_allowance; where it is not supplied it is those contributions times gov.simulation.pensions.defined_benefit_input_per_contribution, set at 1: in the four largest public service schemes in 2024-25 the statutory input of an active member is 0.8 to 1.2 times employer and member contributions together. With no share supplied, results are unchanged. - unused_pension_annual_allowance now defaults, from 2020-21, to an estimate: each of the five previous years' input is this year's times gov.simulation.pensions.prior_year_input_ratio (1), with input and incomes deflated by average earnings growth, against that year's tapered allowance, used earliest first (s. 228A(7)). A supplied value overrides it. - The 2024 charge and payers on the certified dataset are checked against HMRC's 2024-25 statistics (30,440 people, £0.3bn) within half to one and a half times, as a strict expected failure: the dataset imputes employer contributions as three times employee contributions (PolicyEngine/policyengine-uk-data#606). The taper moves into utils/pensions.py (annual_allowance_for_year) so the carry-forward estimate can apply earlier years' rules. Part of #2523. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lied pension inputs apply The UC earnings deductions case expects the whole excess charged, so it now fixes unused_pension_annual_allowance at nil. The prior-year ratio applies to all five years the estimate uses. A supplied unused allowance applies to its own year, and past the projection horizon the estimate replaces a stored one. A supplied defined-benefit input amount should come with the share of the contributions that fund it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#1863 lists water_and_sewerage_charges under the Ofwat index in uprating_indices.yaml. uprate_water_and_sewerage_charges now grows it by country, so the list entry would grow it a second time: drop it, and record the column among the code-health test's deliberate exceptions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…assumptions, test against Table 7's excess - Dataset projection no longer copies a stored unused_pension_annual_allowance into later years (YEAR_SPECIFIC_INPUTS): its three-year window moves each year, so projected years use the estimate. - The HMRC check also compares the chargeable amount with Table 7's £672m of excess, and names the gap as known data gaps, chiefly policyengine-uk-data#606: with employer contributions at a third of their stored values, count, charge and excess all fall within the band. - Parameters and docs say the defined-benefit ratio of 1 is a chosen midpoint (ten years' service; valid from 2023-24, when public service schemes revalue on 6 April; private schemes would push it below 1), that the carry-forward ratio of 1 is a convention which probably understates carry forward, and what a supplied share alone changes (only net pay to a defined-benefit scheme from age 75). The carry-forward ratio's label now matches its five-year use. - relevant_uk_earnings cites ITEPA 2003 ss. 9(2) and 11 for allowable expenses and says why pension deductions stay in; it and the carry-forward estimate note the limits left (furnished holiday lettings before 2025-26, the five-year window, 2015-16 alignment). - A carry-forward case where the taper binds only in an earlier year. - The UC benefit cap threshold notes that the national living wage parameter ends at April 2026, so the threshold holds at £10,572 after it. - Scottish water 2027-29 cite the Draft Determination's core cap (para. 9.1.3) and CPI forecast (Table 68); the HBAI validation row for water is updated, as in #2527. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
25b5c3d to
baf2d54
Compare
vahid-ahmadi
left a comment
There was a problem hiding this comment.
@juaristi22 thanks. Third and last look, at baf2d54ff. You rebased onto 68ce6a6fb rather than merging, so I compared each commit with its earlier version (git range-diff) and each touched file with origin/main. Only commits 3, 6 and 9 changed, all in the salary-sacrifice handling. Approving.
Item 14: fixed
pension_annual_allowance_threshold_income.pyis now byte-identical to main.relevant_uk_earnings(person, period, hmrc)has lost itsremoved_payargument.annual_allowance_net_incomeno longer subtractsreturned.workplace_pension_contributionsand the defined-benefitnet_payinpension_contributions_for_annual_allowance.py:49-58now takeemployee_pension_contributionsas it is, so a supplied return is counted once as a member payment.- No
salary_sacrifice_returned_to_incomereads are left outside the two input aggregates (employment_income,employee_pension_contributions). - The PR's own pieces survived: the defined-benefit share and input amount,
trading_profits_before_losses, the newrelevant_uk_earnings,annual_allowance_for_yearandworkplace_pension_contributions.
I checked it on a household, 2026: pay £250k, £20k net pay, £15k employer, £10k sacrifice, with a supplied £5k return. With no defined-benefit share, main and this branch give identical employment income, contributions for the AA, threshold income (£240k), adjusted net income, AA (£50k) and relief. The AA contributions are £50k on both. The code before the rebase gave £45k, because it took the return off. With the share at 1 and a £30k input amount, the AA contributions are £30k, so the defined-benefit path still works.
What I ran (fresh venv, no private data)
- Pensions YAML: 147 passed.
- Income tax reliefs YAML, plus the two #2462 salary-sacrifice NI YAMLs: 77 passed.
- AA properties and taper,
test_salary_sacrifice_ni_only.py,test_salary_sacrifice_needs_pay_properties.py,test_unused_annual_allowance_projection.pyandtests/code_health: 2,568 passed. - CI at
baf2d54ff: Lint and Test / Result pass. One Release test shard is still pending. Aggregate impact now compares68ce6a6fbwith this branch merged onto it (111c97477), so it shows this PR alone. It flags no moves.
Nothing new at must or should.
🤖 Generated with Claude Code
Main's version of the page predates #2488 (merged 20 October) and #2536 (merged 21 October). Corrections, each checked against main a45baf5: - #2488 has merged; the page no longer calls it open. - unused_pension_annual_allowance is estimated from 2020-21 (formula_2020) rather than defaulting to nil; nil for the £100k example. - Defined-benefit accrual counts through defined_benefit_pension_share and defined_benefit_pension_input_amount, not only a supplied pension_contributions_for_annual_allowance. - Relevant UK earnings are taxable employment income (less expenses) plus trading profit after capital allowances and the trading allowance, before losses (utils/pensions.py). - The annual allowance charge keeps relief-at-source contributions in income and raises the rate limits only when s. 192(4)'s condition holds (private_pension_contributions_tax.py); new £2,946 example. - s. 192(4) raises the higher rate limit too; the higher-rate top-up can be claimed outside Self Assessment and differs in Scotland. - The model gives no relief on basic-rate RAS above taxable income. - Tapered allowance minimum was £4,000 in 2020-21 to 2022-23. - marginal_tax_rate perturbs the two eldest adults in turn. - #704 gives no year or age, and its reporter expected £0 and -£1,200, so the column is the total relief; examples re-run on 2.148.2. - Replaced the released .added fragment with a .changed fragment. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
STBM claims checked against ScottishTaxBenefitModel.jl at f534ca1: - calculate_pension_taxation! raises non_savings_thresholds from the basic rate onwards (IncomeTaxCalculations.jl:138), so it widens the basic band and moves the higher limits up, not "every band". - pension_contrib_basic_amount is £10,000 in the 2023-24 to 2026-27 rUK files, which the Scottish systems inherit; £3,600 before. - In its rUK systems from 2023-24, non_savings_basic_rate points at 40%. PolicyEngine UK claims checked against main a45baf5: - The relief cap rises to £3,600 for relief-at-source contributions (utils/pensions.py). - employer_pension_contributions is an input: Microcosm UK draws it from ONS ASHE Table P10 bands, the Enhanced FRS uses 3x employee contributions. - unused_pension_annual_allowance is estimated from 2020-21 (#2536). - Threshold income has been tested at £200,000 since 2020-21 (taper 240,000 - 40,000), and pre-2015 salary sacrifice is an input. - Clarified "default chargeable amount" and the Scottish starter-rate case. - Replaced the released .added fragment with a .changed fragment. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fixes #2523
Fixes #2501
Four fixes Microcosm UK needs before it locks the next release. Each is its own commit (or two), so they can be read separately. The branch also merges the heads of #2488 and #1863 so that it can resolve them against this PR; once they are on main, the diff here is only this PR's changes.
1. UC benefit cap earnings threshold (#2523 item 2)
is_uc_benefit_cap_exempt_earningscompared earned income with £10,152 a year in every year: £846 a month, the 2025-26 figure.The Universal Credit Regulations 2013 set the threshold each year:
The new
uc_benefit_cap_earnings_exception_thresholdreadsgov.hmrc.minimum_wage.non_apprentice, as the minimum income floor does. It gives:These are DWP's published figures. The national living wage parameter ends at its April 2026 rate, so the threshold holds at £10,572 after 2026-27 until that parameter is projected (#2537), as the minimum income floor does. The benefit cap differential test's reference now reads the threshold too, and a £10,400 earner in 2026 joins its examples. This also covers the threshold part of #1986. #1820 and #1999 also touch this threshold, with a dated parameter.
2. One relevant-earnings rule (#2523 item 3, after #2488)
adjusted_total_incomeandpension_contributions_reliefdefined relevant UK earnings differently. FA 2004 s. 189(2) counts:relevant_uk_earningsandtrading_profits_before_lossesinutils/pensions.pynow serve the relief,adjusted_total_income, threshold income and the annual-allowance net-income rebuild.adjusted_total_incomedoes not change. Relief changes only where contributions exceed earnings once expenses, capital allowances or the trading allowance are counted.3. Water and sewerage charges by country (#2501)
Ofwat's series covers England and Wales. Scottish Water's household charges rose 8.8% in 2024-25, 9.9% in 2025-26 (Ofwat: 27%) and 8.67% in 2026-27. Northern Ireland has no domestic water charges.
yoy_growth.scottish_water.water_bills:uprate_water_and_sewerage_chargesgrows the column by country during projection, as council tax is:4. The annual allowance on survey data (#2523 item 1)
Defined benefits. The pension input amount of a defined-benefit arrangement is the increase in the value of rights (FA 2004 s. 234), not the cash paid.
defined_benefit_pension_share(input, default nil) says how much of the workplace contributions (employer, net pay, salary sacrifice) go to defined-benefit arrangements.defined_benefit_pension_input_amountreplaces those contributions; supply it where known.gov.simulation.pensions.defined_benefit_input_per_contribution, set at 1:Carry forward.
unused_pension_annual_allowancestays an override. A supplied or stored value applies to its own year only: its three-year window moves on, so dataset projection drops it from later years, which use the estimate. From 2020-21 the estimate is:gov.simulation.pensions.prior_year_input_ratio(1), with input and incomes deflated by average earnings growth;The ratio of 1 is a convention: HMRC publishes nothing on carry-forward use. Real excesses are lumpy, so it probably understates carry forward.
Against HMRC. HMRC's Private pension statistics (July 2026) give, for 2024-25 (provisional): 30,440 people reporting an excess through Self Assessment, after carry forward, with £672m of excess (Table 7), and £0.3bn of charge (Table 6).
On the certified Enhanced FRS 1.58.1 in 2024 the model gives:
Employer contributions are 69% of payers' input, because the dataset imputes them as three times employee contributions (policyengine-uk-data#606). With employer contributions at a third of their stored values, the 2024 model gives about 27,000 payers, £0.21bn of charge and £0.47bn of excess, all within half to one and a half times HMRC's figures.
test_annual_allowance_against_hmrc.pychecks all three against that band. It is a strict expected failure, allowlisted to "known data gaps, chiefly policyengine-uk-data#606", and it reports no model value.Reproducer:
policyengine_uk/tests/policy/baseline/gov/dwp/benefit_cap/uc_benefit_cap_earnings_exception_threshold.yaml::Earnings between the 2024-25 threshold and the 2025-26 figure lift the cap in 2024-25;policyengine_uk/tests/policy/baseline/gov/hmrc/pensions/relevant_uk_earnings.yaml::Capital allowances reduce relevant UK earnings;policyengine_uk/tests/test_water_uprating.py::test_each_country_takes_its_own_water_growthTests
New:
uc_benefit_cap_earnings_exception_threshold.yaml(11 cases);relevant_uk_earnings.yaml(4; 3 fail before);test_water_uprating.py(11);defined_benefit_pension_input.yaml(6);unused_pension_annual_allowance_default.yaml(8, including the earliest-first case and a taper that binds only in an earlier year);test_unused_annual_allowance_projection.py(a stored carry-forward stays in its own year);tests/microsimulation/test_annual_allowance_against_hmrc.py.Updated:
test_projection_horizon.pyand the fingerprint read-set mock now cover water by country;Validation, run locally (targeted to the touched surfaces):
axiom: TheAxiomFoundation/rulespec-uk#399 queued (UC reg 82 threshold); TheAxiomFoundation/rulespec-uk#481 queued (FA 2004 s.189(2) relevant UK earnings and the s.191(7) limit); TheAxiomFoundation/rulespec-uk#444 queued (s.228A carry forward); n/a for the survey-data defaults and the water series, which are data assumptions
🤖 Generated with Claude Code