Repository navigation
Add bonus sacrifice, spousal maintenance, Widowed Parent's Allowance and heating fuel inputs, and fix student loan plan projection - #2573
Conversation
vahid-ahmadi
left a comment
There was a problem hiding this comment.
@juaristi22 thanks, this is a big but well-split PR, and most of it checks out against the legislation. Review at head 313b18c0, round 1. No must findings. There are four shoulds: two on the student loan projection, one on where the heating fuels sit relative to #2571, and one on Income Support. The rest are nits.
What I checked
- Primary sources for every new legal claim: NICs Act 2026 s.1 with ITEPA s.69A, FA 2004 s.228ZA(5)(b) and (6), ITTOIA s.727, UC Regs reg. 66(1)(b)(vi) and (d), SPC Regs regs. 9(f) and 15(5)(d), HB Regs Sch. 5 paras. 16, 47 and 47A, HB (SPC) Regs reg. 27(4)(c)(ii), reg. 29(1)(o) and Sch. 5 paras. 7 and 20, IS Regs reg. 54, reg. 55 and Sch. 9 paras. 16(h) and 73, ITEPA s.577, WRA 2012 s.96(10)(n), SSCBA s.39A, SI 1979/597 reg. 4, SSI 2023/302 reg. 16(3)(e), and the GOV.UK student loan plan and write-off pages.
- Tests in a fresh env at the head: all YAML in the touched areas (spousal maintenance, WPA,
hmrc/pensions, salary sacrifice NI, student loans, benefit cap, Carer's Allowance overlap, pension-age HB, Pension Credit): 697 passed. Pytest: all 50 new or changed tests passed, plus the salary sacrifice, annual allowance, ANI and HB regressions. The other regressions that read a changed list (benefit cap, carer overlap,disable_simulated_benefits, fiscal partition,gov_spending, uprating, war widow's pension, HBAI property income): 191 passed. - On the merge base, the three new YAML files give 19 failed and 1 passed, and
test_student_loan_plan_uprating.pyfails 5 of 5. So the new tests do test the change. The Reproducer fails on base and passes at the head. - CI at the head: YAML, Lint, Docs and smoke imports pass. Test / Python 1-5 and Aggregate impact were still pending when I posted, and there is no bot comment yet. I'd expect no aggregate change in any year: the only baseline path is the student loan relabelling, and stored
student_loan_repaymentsoverride the formula (#1764). - Data: none of the five new inputs has a writer in policyengine-uk-data or Microcosm yet (Microcosm lists the two fuels as
uncarried). So they all default to 0, and baseline results only move through the student loan projection, as the body says.
What is right
- Bonus sacrifice. The NICs Act 2026 cap applies to pension contributions under "optional remuneration arrangements". ITEPA s.69A covers both giving up pay (type A) and taking a benefit instead of a bonus (type B), so one £2,000 cap across both inputs is right. FA 2004 s.228ZA(5)(b) adds back "flexible remuneration arrangements" too.
_from_payis the only formula that reads the salary input, and every reader (cap, NI excess, threshold income, AA contributions, the workplace pension helper) uses_from_pay. Nothing reaches ANI oremployment_income, so there is no double count. - Spousal maintenance. Not taxable (ITTOIA s.727), and it stays out of income tax, ANI and tax credit income. UC, Pension Credit (with reg. 9(f)), and both HB regimes with the £15 child-conditional disregard all match the regulations. The savings-credit-only route and the general route are chosen with
where, so nothing is counted twice. - Widowed Parent's Allowance. Taxable, UC unearned income, counted for the benefit cap, £15 HB and £10 IS disregards, CA and CSP overlap, and tax credits in full: all match. HB reads it only through the new variable, so it is not counted twice through
social_security_income. Leaving Pension Credit alone is right under s.39A. As a reported input, it needs no encoding of the April 2017 closure to new claims or the s.39A end conditions; those matter for the rule-based amount in phase 2.
Findings
-
should: Steps 2 and 3 of the student loan projection give Plan 2 to people in Scotland and Northern Ireland.
policyengine_uk/data/economic_assumptions.py:621-644.plan1_new_cohortandplan2_new_cohorthave no region term. Before this PR, Step 3 could never fire, so it did not matter; the body says it now does. On a 2024 dataset projected to 2029, a degree-holder aged 31 to 35 with no loan in Scotland or Northern Ireland is labelledPLAN_2at the take-up rate. GOV.UK ("Repaying your student loan: which repayment plan you're on") puts SAAS borrowers on Plan 4 and Student Finance NI borrowers on Plan 1, whatever their start year. Step 4 already limits Plan 5 to England. I'd suggest:- Step 3: England and Wales get Plan 2, Scotland Plan 4, Northern Ireland Plan 1.
- Step 2: Scotland gets Plan 4 (Northern Ireland is already Plan 1).
Step 1 (unchanged) also relabels existing Scottish holders to Plan 1 or 2 by start year, and sends non-England Plan 5-age holders to Plan 2. That is outside this PR, but worth an issue. Impact is on the plan label only, since new holders repay nothing under #1764, but plan counts and any reform that recomputes repayments by plan see it.
-
should: Step 2 writes Plan 1 off 29 years after the course starts, but the loans it targets are written off at 65.
economic_assumptions.py:627(uni_start_year + _PLAN1_WRITEOFF_YEARS > year) and the test attest_student_loan_plan_uprating.py:101-103. GOV.UK ("When your student loan gets written off or cancelled") says Plan 1 loans first paid before 1 September 2006 are written off when the borrower is 65; only later Plan 1 loans go 25 years after the first April due. Step 2 now anchors on the 1998 intake, so in 2029 it covers ages 45 to 49, the 1998 to 2002 intakes, all pre-2006 loans. The 29-year rule then drops the 1998 to 2000 intakes (ages 47 to 49), whose loans are still outstanding, so only 45 and 46 are added, and the new test codifies that cut-off. Using the age-65 rule for start years up to 2005 (and the 29-year proxy after) would fix it; the test would then cover 45 to 49. Step 1's write-off of existing pre-2006 holders has the same issue, but is unchanged here. -
should: the heating fuels are held flat as if Ofgem-priced, and #2571 would keep it that way.
economic_assumptions.py:118-128.DATASET_UPRATING_EXCLUSIONSexists because electricity and gas are "NEED kWh converted at Ofgem Q2 2026 unit rates", so they already carry FY2026-27 prices (uprating_indices.yamlheader). The same header says LCFS consumption is "at data-year prices and do[es] need uprating". Ofgem's cap does not cover heating oil, LPG, coal or wood, and the only planned source is Microcosm's COICOP 04.5.3 and 04.5.4 figures, withperiod_value: 2024. So adding the fuels to the exclusions holds 2024 prices flat into 2026 and later, and overrides theuprating = CPIeach variable declares.On #2571: I checked it at
ff134627.uprate_energyand the reset group still take their variable set fromDATASET_UPRATING_EXCLUSIONS, so our #2571 should 2 still stands. Merged together, the "one-line merge" in the body would apply a dataset's FY2026-27 energy declaration to 2024-priced fuels, leaving them about two years of price growth too low. No output changes today (both default to 0 and nothing reads them), hence should.The simplest fix is to leave the fuels out of
DATASET_UPRATING_EXCLUSIONSand let them CPI-uprate like other LCFS consumption, until a dataset declares their own price year. That makes the two PRs independent in meaning. It also meanstest_heating_fuel_inputs.py("projection holds them flat"),KNOWN_UNUPRATEDintest_uprating_declarations.pyand theuprating_indices.yamllisting change with it. On order: I'd still merge #2571 first, as we suggested on #2571. It is ready and this PR is a draft, and the remaining conflicts (git merge-treegives three:economic_assumptions.py,uprating_indices.yamland the economic assumptions README) are all comment and doc text, which this PR can take on rebase. -
should: Income Support still ignores spousal maintenance.
variables/gov/dwp/income_support_applicable_income.py. IS Regs 1987 reg. 54 makes a spouse, former spouse, civil partner or former civil partner a "liable relative", and reg. 55 counts their payments as income; Sch. 9 para. 73 disregards only child maintenance. On a lone parent with a child in 2026, £100 a week of spousal maintenance moves HB income by £4,420 and IS income by £0. The gap predates the PR, and the IS population is small now, but the new input is the natural place to close it. Adding it to IS income here or noting it as not modelled in the IS variable would both do. -
nit: UC counts maintenance only "under a court order or an agreement for maintenance". UC reg. 66(1)(d).
spousal_maintenance_income's documentation includes voluntary payments, which Pension Credit (reg. 15(5)(d)(iii)) and HB (para. 47, "whether under a court order or not") count, but UC's wording does not clearly reach. Survey data cannot separate them and an informal arrangement may well be an "agreement", so a line in the variable's documentation is enough. -
nit: maintenance from a former unmarried partner. HB para. 47 covers maintenance from a "former partner", which includes a former cohabitee. The input is described as spouse or civil partner only. A sentence saying where former-cohabitee maintenance should go (here or
maintenance_income) would help whoever maps the data. -
nit: Council Tax Reduction counts neither new income.
council_tax_reduction_applicable_income.py:88-136. On the same lone parent, WPA of £156.65 a week adds £7,365.80 to HB income and £0 to CTR income, and spousal maintenance £4,420 and £0. The model's CTR otherwise mirrors HB income (the two match in the base case), and the Welsh prescribed scheme and English pension-age scheme copy HB's disregards. I have not checked each scheme's text, so I raise it as a question: add both to the CTR lists, or say why not. -
nit:
uprate_student_loan_plansnow readsperson_idbut its early-return guard only checksstudent_loan_planandhighest_education(economic_assumptions.py:557-559and:589). Every UK dataset has the column, so this is robustness only. -
nit: IS Sch. 9 para. 16 is "subject to paragraphs 36 and 37", the aggregate limit with war pensions. The HB variable notes its equivalent; a one-line "not modelled" note in the IS formula would match.
Related PRs
- #1737 (mine): it is docs only and adds
bereavement-support-plan.mdas a new file, but main already has a later version of that page, which this PR edits. No code or meaning conflict; this PR'swidowed_parents_allowanceis the variable #1737 planned. Merge this one on its own merits; #1737 then needs closing or rebasing to drop its copy of the page. - #2571: see finding 3.
🤖 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 New warnings
Notes
Moves flagged for reviewNone. Spending by benefit and service
Caseloads
Standard reform costings (change in gov_balance)
Standard reforms: change in each reform's target
Household net income by decileNo household's net income changes in any year. No change in any year: receipts by tax head; government balance, each component counted once; model totals; household income totals; population; poverty rates; people in poverty; inequality; mean household net income in each side's own deciles. Checks failing on both base and head (1)
Counted-once checkEach of 85 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
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
|
|
UK hub independent review (GPT-6.1 Sol) at
@juaristi22: since this needs to land today before your fallback build, shall the hub push these four fixes onto your branch now (merge commits only, with tests), or would you rather do them? Reply here and I'll act straight away. If you'd rather land as is and fix in a follow-up PR before the build, say that instead. |
|
Hand-off for the UK hub: draft stacked follow-up #2580, branch Four commits address the four P2 findings:
Validation: repository Ruff lint, Run when host capacity clears: UV_CACHE_DIR="$PWD/.job/uv-cache" python3 .job/run-gated.py /opt/homebrew/bin/uv run --frozen --no-sync pytest policyengine_uk/tests/test_income_support_spousal_maintenance.py policyengine_uk/tests/test_council_tax_reduction_maintenance_wpa.py policyengine_uk/tests/test_student_loan_plan_uprating.py policyengine_uk/tests/test_non_dependant_spousal_maintenance.py -n 2 -qImpact: nonzero maintenance/WPA now reach these means tests; projected loan labels follow nation. No aggregate impact or dataset build was run. CI does not automatically run on this stacked base: the PR workflow is filtered to Please keep both PRs draft and land the reviewed stack together. No merge or ready action was taken. |
|
@vahid-ahmadi thanks, all nine taken. The hub's stacked #2580 is now merged into this branch (fast-forward), and four commits on top cover the rest:
On #1737: once this merges, it will need closing or rebasing to drop its copy of the bereavement page. Could you take another look at the new head? |
|
Ready for review at @vahid-ahmadi and @MaxGhenis (for the hub's look at the combined head): could you take a last look? The changes since your reviews are listed in my reply above. I'll put it in the merge queue once you've both signed off. |
|
UK hub: approved. The independent review (GPT-6.1 Sol) of this PR with the fix stack #2580 approves at It now conflicts with main after #2571 landed overnight. The only conflict is the header comment of |
The £2,000 NICs exemption cap from 6 April 2029 covers salary-sacrificed and bonus-sacrificed pension contributions together, and FA 2004 s. 228ZA(5)(b) adds both back to annual allowance threshold income, so no rule treats the two routes apart. Datasets still need to: HMRC's salary sacrifice users and reliefs come from ASHE, which does not cover bonus sacrifice, and the OBR costs the bonus route on its own base (costing note, paragraphs 1.4, 1.5 and 1.9). pension_contributions_via_bonus_sacrifice is a person input uprated with average earnings, as the OBR projects its base (paragraph 1.8). pension_contributions_via_salary_sacrifice_from_pay now adds it to the salary input under the same pay condition, so the cap, the NICs excess and the annual allowance readers follow. A negative value in either input counts as none rather than offsetting the other. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Maintenance paid by a spouse, civil partner, former spouse or former civil partner is income for Universal Credit (UC Regs 2013 reg. 66(1)(d)), Pension Credit (SPC Regs 2002 reg. 15(5)(d)) and Housing Benefit (HB Regs 2006 reg. 40; HB (SPC) Regs 2006 reg. 29(1)(o)). Child maintenance is none of these. No means test counted either: maintenance_income holds the FRS child maintenance questions, and datasets put the payments from an absent partner in private_transfer_income, which reaches only HBAI and market income. spousal_maintenance_income is a new person input, uprated by GDP per head as the other two are. It counts: - in UC unearned income, from 2013-04-29; - in Pension Credit income, and in the savings credit non-qualifying income (reg. 9(f)); - in Housing Benefit income, less £15 a week where the family includes a child or young person (HB Regs 2006 Sch 5 para 47; HB (SPC) Regs 2006 Sch 5 para 20), under both regimes. On the pension-age savings credit only route the disregard comes off the Pension Credit figure, which counts the maintenance in full (reg. 27(4)(c)(ii)); - in market income, household market income and HBAI income. maintenance_income is documented as child maintenance, and the child maintenance page now describes the split. Datasets that do not supply the new input keep today's results. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… Support Payment FRS benefit code 6 is "Bereavement Support Payment / Widowed Parent's Allowance", and datasets put it in bsp_reported, which reaches household benefits and HBAI only. The two benefits are treated differently. Bereavement Support Payment is tax-free, is not Universal Credit unearned income and has had frozen rates since April 2017. Widowed Parent's Allowance (SSCBA 1992 s.39A) rises with CPI and: - is taxable (ITEPA 2003 s.577), so it is in social_security_income and also reaches Income Support and tax credit income; - is UC unearned income (UC Regs 2013 reg. 66(1)(b)(vi)); - counts in Housing Benefit less £15 a week (HB Regs 2006 Sch 5 para 16; HB (SPC) Regs 2006 Sch 5 para 7); - is disregarded by £10 a week in Income Support (IS Regs 1987 Sch 9 para 16(h)); - is a welfare benefit for the benefit cap (WRA 2012 s.96(10)(n)); - overlaps with Carer's Allowance (SI 1979/597 regs 4 and 12) and Carer Support Payment (SSI 2023/302 reg. 16(3)(e)), as a whole payment: its additional pension is not separated. widowed_parents_allowance_reported is a CPI-uprated person input and widowed_parents_allowance adds it. Like Bereavement Support Payment it is in household benefits, government spending, HBAI income and benefits, the pre-Budget household benefits, disable_simulated_benefits and the fiscal partition. Pension Credit is left alone: the allowance is not payable from State Pension age or while the recipient lives with a partner (s.39A(4) and (5)), so no Pension Credit claimant or partner can receive it. bsp_reported keeps its CPI uprating until datasets split code 6 (#2381). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
uprate_student_loan_plans had four problems: - Step 3 never fired. base_year_age = age - (year - 2023) and uni_start_year = year - age + 18 give base_year_age = 2041 - uni_start_year, so uni_start_year >= 2012 implies base_year_age <= 29, and Step 3's test base_year_age > 29 is never true. No Plan 2 holders were added above the base year's oldest. - It mixed two models. Step 1 assigns plans by the projection year's intake, which suits projected datasets: they copy the base year with ages fixed. Steps 2 and 3 tested base_year_age, which is a person's base-year age only if ages advance. - The base year was a constant, 2023, while datasets built on FRS 2024-25 start in 2024. - The generator was seeded by the year and drew by position, so a person could be a new holder in one year and not the next, and the result depended on row order. apply_uprating now passes the dataset's first year down. Steps 2 and 3 compare the data's age with the base year's oldest holder of the plan, who started university in the plan's first intake: 2012 for Plan 2 and 1998 for income-contingent Plan 1 loans. On a 2024 dataset, Step 3 adds Plan 2 holders aged 31 to 35 in 2029 and Step 2 adds Plan 1 holders aged 45 and 46. Each person's draw comes from splitmix64_uniform on person_id, with a salt of its own. Step 1 is unchanged. Stored student_loan_repayments still override the formula (#1764). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The engine's energy inputs are electricity_consumption, gas_consumption and domestic_energy_consumption. Homes off the gas grid heat with oil, LPG, coal or wood, which ONS household spending records as liquid fuels (COICOP 04.5.3) and solid fuels (04.5.4). Without inputs for them, an energy reform cannot reach those homes, which cluster in rural areas and Northern Ireland. liquid_fuel_consumption and solid_fuel_consumption are household inputs, annual, GBP, default 0. They are part of housing_water_and_electricity_consumption, so they are not added to consumption. Ofgem's price cap, which prices the electricity and gas inputs, does not cover them, so they stay out of the energy projector (DATASET_UPRATING_EXCLUSIONS) and are uprated by CPI from the data year, like other LCFS spending, until a dataset declares their own price year. No formula reads them yet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The fixture pinned Child Benefit, Universal Credit and the other awards with bare values, which do not apply to 2026, so the model computed them and they reached Council Tax Reduction income: 36 of the 45 cases failed. Keying the benefit-unit inputs by year, as the people inputs already are, isolates the statutory income list as intended. The CTR formula is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s in England only Step 2 wrote every Plan 1 loan off 29 years after the course started, but loans first paid before 1 September 2006 (the 2005 and earlier intakes) are written off at 65 (GOV.UK, "When your student loan gets written off or cancelled"). Projected from 2024 to 2029, Step 2 now adds holders at ages 45 to 49 (the 1998 to 2002 intakes), not 45 and 46. It also now requires the 1998 or a later intake, the first with income-contingent loans, which the 29-year write-off had bounded before. Step 1's write-off of existing holders is unchanged. Step 4 goes back to England only. Plan 5 is the only plan that did not exist in the base year; the 2023-and-later intakes in Wales, Scotland and Northern Ireland are on plans (2, 4 and 1) the data already holds at those ages, so adding holders there would count borrowers twice. The funding nation still sets the plan in Steps 2 and 3. uprate_student_loan_plans also returns early without person_id, which it keys the take-up draws on. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rd limit - spousal_maintenance_income: Universal Credit counts maintenance under a court order or an agreement (reg. 66(1)(d)); voluntary payments are counted too, as survey data cannot separate them. Maintenance from a former cohabiting partner does not belong in the input: Universal Credit and Pension Credit do not count it, while working-age Housing Benefit does (Sch 5 para 47), which the model does not capture. - income_support_applicable_income: paras 36 and 37 of Sch 9, which limit the widowed parent's allowance and war pension disregards together, are not modelled. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
vahid-ahmadi
left a comment
There was a problem hiding this comment.
@juaristi22 thanks, round 2 at head 7b73a30dd (round 1 was 313b18c0). All four shoulds are resolved, the five nits are addressed, and I found nothing new above nit level, so I'm approving. One thing before it can land: the branch now conflicts with main (#2571 merged), and #2592 is the fix; see the merge order section.
Round 1 findings
- should, student loan plan by region: resolved.
475682f26with0a7f9e5f4,economic_assumptions.py:598-608. New holders in Steps 2 and 3 takenew_borrower_plan: Scotland Plan 4, Northern Ireland Plan 1, England and Wales Plan 1 before the 2012 intake and Plan 2 after, and Plan 5 only for England from 2023. This matches GOV.UK ("which repayment plan you're on"): SAAS is Plan 4 and Student Finance NI is Plan 1 whatever the start year, Wales stays on Plan 2 after 2012, and Plan 5 is England from 1 August 2023. Step 4 is back to England only, and the reason in the comment is right: the other nations' 2023 and later intakes are on plans the base year already holds at those ages. Step 1 is unchanged, as agreed, and needs its own issue. - should, Plan 1 pre-2006 write-off at 65: resolved.
0a7f9e5f4,economic_assumptions.py:649-662. Intakes up to 2005 are written off at 65 and later ones after 29 years, and Step 2 now requires the 1998 or a later intake. GOV.UK ("When your student loan gets written off or cancelled"): Plan 1 loans first paid before 1 September 2006 go at 65, later ones 25 years after the first April due, so the 2005 cut-off and the 29-year proxy are right.test_the_two_plan_1_write_off_ruleschecks both rules in 2040. - should, heating fuels held flat: resolved.
de6fff1a6. Both fuels have leftDATASET_UPRATING_EXCLUSIONS, are listed under CPI inuprating_indices.yaml, and have leftKNOWN_UNUPRATED. On the merged state (#2592 at84b6488b4, which includes #2571), the exclusions are still only the three Ofgem-priced energy inputs and the fuels are CPI-uprated, so the two PRs no longer interact in meaning. - should, Income Support and spousal maintenance: resolved.
ae2196a3e,income_support_applicable_income.py. The claimant's and partner's maintenance now counts under reg. 40, with Sch. 9 para. 15(3)(a) keeping it out of the voluntary-payment disregard; child maintenance stays out under para. 73. - to 9. nits: all addressed. UC's court order or agreement wording and the former cohabitee case are in
spousal_maintenance_income's documentation (cf52f50d4); CTR counts both incomes (5e01b4e2a, see below); theperson_idguard is in (0a7f9e5f4); IS paras. 36 and 37 are noted as not modelled.
What I checked at the new head
- Primary sources for the new CTR rules: England SI 2012/2885 Sch. 5 paras. 7 and 20 and SI 2012/2886 Sch. 8 para. 49; Wales WSI 2013/3029 Sch. 4 para. 20 and Sch. 9 paras. 21 and 49; Scotland SSI 2012/319 Sch. 3 para. 19 and SSI 2021/249 reg. 57(1)(b)(vi), (1)(d), (4) and (6). Each gives £15, the maintenance disregard is one per family with a child (or family premium), aggregated across payments, and the WPA disregard is per recipient. The code matches. For the non-dependant bands, HB Regs reg. 74(9) and HB (SPC) Regs reg. 55(10) disregard no maintenance, so counting it in gross income is right.
- Tests in a fresh env at the head: the new and changed Python tests (student loans, heating fuels, IS maintenance, CTR maintenance and WPA, non-dependant bands, uprating declarations), 84 passed; YAML for CTR, spousal maintenance, WPA, pension-age HB, means-test income members, benefit cap and Pension Credit, 727 passed; pytest regressions that read CTR, IS, student loan plans, non-dependant deductions or the energy exclusions (63 files), 2,008 passed and 61 skipped.
- The new tests test the change: copied onto
313b18c0, 49 fail and 27 pass, including every funding nation, write-off, IS, CTR and non-dependant case. - CI at
7b73a30dd: all 17 checks pass, including Test / Python 1-5, YAML and Aggregate impact (no moves flagged).
New findings
- nit: Scottish new holders in Step 2 get the Plan 1 write-off, but Plan 4 has its own.
economic_assumptions.py:649-653. GOV.UK: Plan 4 loans first paid before 1 August 2007 are written off at 65 or 30 years after the first April due, whichever comes first; later ones 30 years after the first April due. So the Scottish 1998 to 2005 intakes are held too long (to 65, not about age 52) and the 2006 to 2011 intakes dropped about five years early. Within the 2032 projection horizon this reaches at most the 1998 intake in 2032, and new holders repay nothing under #1764, so it is a label issue only. A comment, or aPLAN_4branch inplan1_written_off, would do. - nit: the PR body's "Changed:" line still says the fuels "join the energy inputs in
KNOWN_UNUPRATED". Afterde6fff1a6they leave it. - nit: the
axiom:line marks the #2580 rules as "needed". The other entries name a queued rulespec-uk issue. Queuing the IS reg. 40 and Sch. 9 paras. 15(3)(a) and 73, CTR disregard and HB reg. 74 / reg. 55 cases, and citing the issue, would bring them in line. Fix evidence passes, so this is wording only.
Merge order
- #2571 is merged. GitHub now marks this PR as conflicting. The only conflict is the header comment of
uprating_indices.yaml. I checked #2592 against a plain merge of the same main into this branch: it differs only in that header, keeping main's text and adding the fuel sentence. #2592 also merges cleanly into current main. So merge #2592 (or resolve the same way), then this. - #1737 (mine): as before, once this merges I'll close or rebase it to drop its copy of the bereavement page.
🤖 Generated with Claude Code
7b73a30 to
112056f
Compare
|
Rebased on main after #2571 merged, so the hashes in the replies above have changed. The fuels CPI change (
Locally at |
|
Merged by the UK hub (Claude session 8e19f9d5) at head
|
Resolves the conflict with #2573's spousal maintenance in income_support_applicable_income.py by keeping both: statutory sick, maternity and paternity pay as other income (IS Regs 1987 reg 40(4)(a), Sch 9 paras 1 and 4) and spousal maintenance (reg 40, Sch 9 para 15(3)(a)), with child maintenance still disregarded (para 73). The Widowed Parent's Allowance disregard (para 16) is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Keeps #2573's account of what is now modelled (spousal_maintenance_income feeding UC, Pension Credit and Housing Benefit; Phases 1 and 2 done) and re-applies the corrections that still hold: the £39 default rate, the shared-care rules, and the CSJ paragraph. Drops the "gap is spousal maintenance" text, which #2573 made false, and narrows the changelog fragment to match. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Keeps #2573's account of the new Widowed Parent's Allowance variables (widowed_parents_allowance_reported and widowed_parents_allowance) and re-applies the corrections that still hold. Drops "the legacy WPA has no variable of its own", proposes replacing the adds of widowed_parents_allowance rather than adding it, notes that bsp_reported keeps CPI uprating until WPA also moves out, and cites GOV.UK for WPA being closed to deaths from 6 April 2017 while still taking new claims for earlier deaths. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#2573 added spousal maintenance (less the £15 disregard) and Widowed Parent's Allowance (less £15) to the Housing Benefit means test, in the formula this PR moved into hb_income_before_deductions. Add both to the helper, keeping this PR's claimant-or-partner mask, Child Benefit and Social Fund disregards and statutory pay as earnings. Both variables are already limited to the claimant and partner. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#2573 (merged) implemented the spousal maintenance and Widowed Parent's Allowance inputs that two pages here still called pending: - Child maintenance: take main's description of the split inputs and the done Phases 1 and 2, and keep this batch's corrections: the £39 one-child default, shared care, and the CSJ point that the model reproduces UC's side of the asymmetry but not the combined marginal rates. - Bereavement support: take main's status for WPA's own variables, keep the batch's statutory WPA eligibility conditions, make Phase 2 replace the existing adds with a formula, and state both conditions for freezing bsp_reported's CPI uprating: code 9 out, and WPA out of code 6. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e limit #2573 split maintenance into maintenance_income (child maintenance) and spousal_maintenance_income. The £100,000 gross income test counts all maintenance (income guidance questions 8 and 9), so add the spousal input, with regressions above the limit and at exactly £100,000. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fixes #2572
The issue lists five engine changes that Microcosm UK needs. This PR makes all five, one commit each, in the issue's order. The UK hub's review then added four commits in #2580, merged into this branch, and three more answer Vahid's review (his fuels point is folded into the fuels commit): 12 commits in all, rebased on main after #2571 merged.
1. Bonus sacrifice: a second input
pension_contributions_via_bonus_sacrificeis a new person input: annual, GBP, default 0. It is uprated with average earnings, as the OBR projects the bonus-sacrifice base (costing note, paragraph 1.8), and listed under that index inuprating_indices.yaml.pension_contributions_via_salary_sacrifice_from_paynow adds it to the salary input, under the same pay condition. Everything downstream reads_from_pay, so it follows:_adjusted);salary_sacrifice_pension_ni_excess;pension_annual_allowance_threshold_income(FA 2004 s. 228ZA(5)(b) adds back pay given up under flexible remuneration arrangements);pension_contributions_for_annual_allowanceand the workplace pension helper.A negative value in either input counts as none, rather than offsetting the other. No other formula reads the salary input.
2. Spousal maintenance: an input the means tests count
spousal_maintenance_incomeis a new person input: maintenance paid by a spouse, civil partner, former spouse or former civil partner. It is uprated by GDP per head, asmaintenance_incomeandprivate_transfer_incomeare. It counts:savings_credit/excluded_income.yaml(reg. 9(f));maintenance_incomeis documented as child maintenance, which counts in none of the means tests. Datasets that do not supply the new input keep today's results. The child maintenance page in the book now describes this split.3. Widowed Parent's Allowance: its own input
widowed_parents_allowance_reported(person, annual, GBP, CPI-uprated) andwidowed_parents_allowance, which adds it. Unlike Bereavement Support Payment, the allowance:social_security_income, so it also reaches Income Support and tax credit income;social_security_income;It is also in household benefits, government spending, HBAI income and benefits, the pre-Budget household benefits,
disable_simulated_benefitsand the fiscal partition (a head next tobsp).The issue asked to check Pension Credit's and Housing Benefit's income lists. Housing Benefit is above. Pension Credit is left alone: the allowance is not payable from State Pension age or while the recipient lives with a partner (SSCBA 1992 s. 39A(4) and (5)), so no Pension Credit claimant or partner can receive it.
This covers the reported side of #465; a rule-based amount stays phase 2 of the bereavement plan page.
bsp_reportedkeeps its CPI uprating until datasets split FRS code 6 (PolicyEngine/microcosm#1222, PolicyEngine/policyengine-uk-data#578); #2381 can then freeze it.4.
uprate_student_loan_plansapply_upratingpasses the dataset's first year down;_FRS_BASE_YEARis gone.agewith the base year's oldest holder of the plan. That holder started university in the plan's first intake: 2012 for Plan 2 (age 30 in 2024), and 1998 for income-contingent Plan 1 loans (age 44 in 2024). Step 3 now fires: on a 2024 dataset it adds Plan 2 holders aged 31 to 35 in 2029.person_id(splitmix64_uniform, a new salt), so the same people take up a loan in every year and in any row order.Step 1's relabelling and write-off of existing holders are unchanged. #1764 still applies: stored
student_loan_repaymentsoverride the formula, so new holders repay nothing in the data.5. Liquid and solid heating fuels
liquid_fuel_consumptionandsolid_fuel_consumption: household, annual, GBP, default 0 (COICOP 04.5.3 and 04.5.4). Both are part ofhousing_water_and_electricity_consumption, so they are not added toconsumption. Ofgem's price cap, which prices the electricity and gas inputs held inDATASET_UPRATING_EXCLUSIONS, does not cover them, so dataset projection uprates them by CPI like other LCFS spending until a dataset declares their own price year, so they stay out of #2571's energy projector. No formula reads them yet.Tests
New tests, each failing on main:
policyengine_uk/tests/policy/baseline/gov/hmrc/pensions/pension_contributions_via_bonus_sacrifice.yaml(6 cases) andpolicyengine_uk/tests/test_bonus_sacrifice_properties.py: moving any part of a sacrifice between the two inputs changes none of 15 outputs, from the cap to HBAI income, in 2028 to 2030; £10,000 of bonus sacrifice on £80,000 of pay raises employee NICs by £160 and employer NICs by £1,200 in 2029-30.policyengine_uk/tests/policy/baseline/gov/dwp/spousal_maintenance_income.yaml(7 cases) andpolicyengine_uk/tests/test_spousal_maintenance_income.py: £100 a month lowers a UC award by £100 a month and guarantee credit by the same; child maintenance changes no means test; both reach market and HBAI income.policyengine_uk/tests/policy/baseline/gov/dwp/widowed_parents_allowance.yaml(7 cases) andpolicyengine_uk/tests/test_widowed_parents_allowance.py: £156.65 a week raises taxable income by the annual amount and lowers a UC award by the same amount, and Bereavement Support Payment changes neither; Housing Benefit, Income Support, the benefit cap and Carer's Allowance; CPI projection.policyengine_uk/tests/test_student_loan_plan_uprating.py: on a 2024 dataset, 2029 adds Plan 2 holders aged 31 to 35 at the take-up rate and Plan 1 holders aged 45 and 46; a 2023 dataset also adds age 30; a person's draw is the same in 2028 and 2029 and in any row order.policyengine_uk/tests/test_heating_fuel_inputs.py: the defaults are zero, the fuels add nothing toconsumption, and projection uprates them by CPI.test_income_support_spousal_maintenance.py,test_council_tax_reduction_maintenance_wpa.py,test_non_dependant_spousal_maintenance.pyand the funding-nation cases intest_student_loan_plan_uprating.py; 47 of their 72 cases fail on the commit before Fix means-test income and student-loan funding routes #2580.Changed:
test_uc_incapacity_income.py(the 2013 UC list now has the two new sources) and, from #2580, the funding-nation cases intest_student_loan_plan_uprating.py.Mutation checks: dropping the bonus input from
_from_pay, reg. 9(f), the child condition on the maintenance disregard, the allowance from the benefit cap, the Carer's Allowance overlap list or the Income Support disregard each fails a test.Run locally: before #2580, every Python test file that reads a changed list or function (100 files: 1,290 passed, 11 skipped for want of dataset credentials, 1 existing xfail); at #2580's head with the CTR fixture fixed, the 43 test files that read its changes (1,763 passed, 61 skipped) and all YAML policy tests (4,253); after rebasing on main with #2571, all YAML policy tests (4,338) and the new and touched Python tests (3,013 passed). The full suite runs in CI.
Reproducer: policyengine_uk/tests/test_student_loan_plan_uprating.py::test_new_plan_2_holders_are_aged_31_to_35_in_2029, policyengine_uk/tests/test_student_loan_plan_uprating.py::test_the_two_plan_1_write_off_rules, policyengine_uk/tests/test_income_support_spousal_maintenance.py::test_spousal_maintenance_reduces_an_existing_income_support_award, policyengine_uk/tests/test_non_dependant_spousal_maintenance.py::test_housing_benefit_non_dependant_band_counts_spousal_maintenance
axiom: uksi/2013/376/66 encoded, with companion cases for (b)(vi) and (d) queued in TheAxiomFoundation/rulespec-uk#392 | TheAxiomFoundation/rulespec-uk#390 queued (SPC Regs 2002 regs 9(f) and 15(5)(d)) | TheAxiomFoundation/rulespec-uk#485 queued (HB Regs 2006 Sch 5 paras 16, 47 and 47A; HB (SPC) Regs 2006 reg 29(1)(o) and Sch 5 paras 7 and 20; IS Regs 1987 Sch 9 para 16(h)) | TheAxiomFoundation/rulespec-uk#434 queued (HB (SPC) Regs 2006 reg 27(4)(c)(ii)) | TheAxiomFoundation/rulespec-uk#425 queued (WRA 2012 s.96(10)(n)) | TheAxiomFoundation/rulespec-uk#398 queued (SI 1979/597 reg 12; SSI 2023/302 reg 16(3)(e)) | TheAxiomFoundation/rulespec-uk#422 queued (ITEPA 2003 s.577) | TheAxiomFoundation/rulespec-uk#477 queued (NICs Act 2026 s.1) | from #2580, IS Regs 1987 reg. 40 and Sch. 9 paras. 15(3)(a) and 73, the Council Tax Reduction schemes' maintenance and widowed parent's allowance disregards, and HB Regs 2006 reg. 74 / HB (SPC) Regs 2006 reg. 55: needed (on the UK hub's open list for #2580) | n/a for the student loan projection and the fuel inputs: no legal rule
🤖 Generated with Claude Code