Repository navigation
Treat income derived from capital as capital in the legacy means tests - #1995
Conversation
Income Support, Housing Benefit, Pension Credit and council tax reduction counted property_income as income and deducted tax on rent, interest and dividends. The regulations treat income derived from capital as capital (IS reg 48(4) and Sch 9 para 22; HB reg 46(4) and Sch 5 para 17; HB (SPC) Sch 5 para 22; SPC Sch IV para 18; the CTR schemes' equivalents) and disregard tax only on income taken into account. Rent for letting part of the home stays income less the £20 weekly sub-tenant disregard. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Hand-computed YAML cases for Income Support, working- and pension-age Housing Benefit, Pension Credit and the English, Welsh and Scottish council tax reduction schemes, and Hypothesis properties: invariance to income from capital, home-letting bounds and monotonicity, and bounds on the tax the means tests deduct. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Resolve council_tax_reduction_applicable_income: keep #1909's guarantee credit and savings-credit-only rules and this branch's income-from-capital rules, documentation and references. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-capital-derived-income Resolve the four means-test income formulas: keep #1896's claimant/partner member filter and this branch's income-from-capital rules (no property income, legacy_means_test_income_tax in the tax line, home-letting income). Keep #1952's Pension Credit references and this branch's Sch IV para 18 reference, and #1926's HB docs with this branch's sentence. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Count land and property other than the home in council tax reduction capital (national schemes and the modelled local schemes), so a let property worth more than 16,000 ends CTR now that its rent is not income (SI 2012/2885 Sch 1 para 31(1); WSI 2013/3029 reg 30). Shares stay out pending the pension-wealth split (#1936). - Deduct only the Step 4 tax on counted income (ITA 2007 s.23), less its pro-rata share of Step 6 reductions; Step 7 charges (HICBC, annual allowance charge; s.30) are not tax on counted income. - Select the Scottish working-age scheme under SSI 2021/249 reg 3 (under pensionable age, or over it with UC or a qualifying income-related benefit) and date its exclusion of sub-tenant rent from 1 April 2022; before then SSI 2012/303 Sch 4 para 25 counted it less 20. - Count only the claimant's and partner's sublet rent; give the pension-age disregard its pre-October-2003 value (4) so early years compute. - Update #1909's CTR property test to the new income and capital rules, hold Marriage Allowance fixed in the invariance property, qualify the isolated HB fixture, and add YAML cases for each fix. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… tax attribution docs - Add council_tax_reduction_tariff_income to the national schemes' general route: pensioners £1 a week per £500 over £10,000 (SI 2012/2885 Sch 1 para 37; WSI 2013/3029 Sch 1 para 31; SSI 2012/319 reg 27(2)); Wales and Scotland under pension age £1 per £250 over £6,000 (WSI 2013/3029 Sch 6 para 33; SSI 2012/303 reg 51; SSI 2021/249 reg 63(1)(b)); with UC the £4.35 monthly assumed yield. Nil on the Pension Credit routes and for IS/income-based JSA/income-related ESA recipients. - Value land and property in CTR capital less 10% sale expenses (capital.sale_expenses), moving capital_sources to capital.sources. - Take UC recipients' CTR capital from the UC assessment (default scheme para 37(6); WSI 2013/3029 Sch 6 para 9(6); SSI 2012/303 reg 26), except Scotland from April 2022; apply Oxford's Appendix 6 disregard. - Select the Scottish working-age scheme as ~council_tax_reduction_pensioner, so a UC award held only by people over pension age does not select it. - Correct the tax helper's documentation: foreign tax credit and finance-cost relief are source-specific (TIOPA 2010 ss.18, 36; ITTOIA 2005 ss.274A-274AA); other_tax_credits has no source, so pro rata stays a stated convention. - New YAML cases for each; update #1909's capital and tariff expectations; tariff property test; #1909's property reference includes tariff income. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rd routing, delegated UC income, finance-cost relief) Merge origin/main (1,566 commits) into legacy-capital-derived-income, resolving conflicts with main's CTR applicant model, property income after finance costs, pensioner Child Benefit exclusion and earnings disregards. Round-3 review fixes: - council_tax_reduction_universal_credit_award: an award of UC is a payment or a claim entitled before the cap and deductions (UC Regs 2013 reg 81; SSI 2021/249 reg 10A; Oxford Appendix 3), not the applicant's where the household head applies alone. CTR capital, tariff income, home-letting income and Oxford's Appendix 6 use it, and the UC capital route applies only to working-age applicants. - Passported applicants' whole capital is disregarded where the scheme says so: Wales (Sch 10 para 8), Scotland before April 2022 (SSI 2012/303 Sch 5 para 7; new dated parameter), and per local scheme (Kingston, Merton, Westminster yes; Newham no, its Sch 10 para 8 is blank). - Rent from part of the home is not counted where a working-age applicant's income is the Secretary of State's UC assessment (default scheme para 37; WSI 2013/3029 Sch 6 para 9; SSI 2012/303 reg 26; UC Regs reg 66). - CTR property capital deducts the secured debt inputs after sale expenses. - legacy_means_test_income_tax takes property_finance_cost_relief off the tax on property income first (ITTOIA 2005 ss.274A-274AA); other reductions stay pro rata. - The tax-floor fixture supplies income_tax as an isolation; gross property receipts are supplied where the tests use the property allowance. - New YAML cases for each; property-test references updated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…perty is capital Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The UC-route home-letting exclusion now covers the mixed-age UC case, so a mixed-age couple on Income Support is what distinguishes the reg 3 selector from an age-only one (a surviving mutant in the round-3 check). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…l-derived-income Resolve the CTR conflicts with #2078's applicant model: - applicant_has_universal_credit (#2078's helper in _legacy.py) now reads council_tax_reduction_universal_credit_award, so the local schemes, Oxford and this PR's capital, tariff and home-letting routes share one award test, including an award the benefit cap or deductions reduce to nil; - Kingston and Westminster keep this PR's capital (council_tax_reduction_assessable_capital) with #2078's helper; - council_tax_reduction_applicable_income keeps both documentations and reference lists; - drop this PR's head-applies-alone checks on the passport, which #2078 now builds into council_tax_reduction_relevant_income_based_benefit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Aggregate impact on certified data: passBase Checks: 0 broken by this change, 1 failing on both sides, 0 fixed. Moves flagged for review: 49 (thresholds in Notes
Moves flagged for review
9 more flags are in the report artifact. 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)
The report suppresses a reform's costing and target change in these years, for the reasons below (
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: population. Checks failing on both base and head (1)
Counted-once checkEach of 92 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
|
juaristi22
left a comment
There was a problem hiding this comment.
Source-only re-review at unchanged 1919788159e18ce20e29d815c1d8af85d4894b5e. PARTIAL: the evidence gap is narrower; no new findings. C1 and C2 remain resolved.
The new Northern Ireland reply corroborates the ordinary capital-income, tax and home-rent rules. SR 2007/396 replaces the printed £4 IS/JSA home-rent disregard with £20 from each beneficiary's first benefit week on or after 7 April 2008. The historical Scottish valuation question is also resolved using SSI 2012/303 regulation 46 at 31 March 2022.
One material source question remains: the complete operative NI IS/JSA capital-disregard exception cross-references, including the intervening unapplied amendments. SR 2006/359 establishes additions but cannot reconstruct the whole current lists from the printed base wording. This is incomplete authority, not a confirmed wrong-money finding.
The prior 157 passing code checks and final CTR/household-income evidence remain applicable at this identical head; no model tests were rerun. The filed Axiom/data follow-ups retain their previous dispositions.
|
Ready for hub review Verdict: REQUEST_CHANGES at This is an independent review of the delta What the delta closes:
Every check run at the head passed, on the merge of the head into main I am requesting changes for five P2s:
Every local test and simulation below ran in detached scratch worktrees (head, base The previous round
P2P2-1. Pension Credit floors the deducted tax before it takes off the actual tax on self-employed profit, so its tax line can still go negative
Suppose a member the test does not count moves the Child Benefit charge off the counted member, and the counted member has self-employed profit. The counted sum stays small or slightly negative. The household is the floor cases' own, in England in 2026:
At the head, by a script calling
Change needed. Take the actual self-employment tax off inside the floor. In # The deduction can be negative for one person, where another
# member's earnings move a Step 7 charge off them. The test takes the
# counted members' sum less the tax actually payable on self-employed
# profit, which the notional tax replaces, and floors that at nil, so
# it never adds tax to income.
income_tax = max_(
0,
benunit.sum(
person("legacy_means_test_deducted_income_tax", period)
* is_claimant_or_partner
)
- benunit("pension_credit_self_employment_income_tax", period),
)
tax = income_tax + benunit.sum(
(
add(
person,
period,
[
"national_insurance",
"pension_credit_self_employment_deductions",
],
)
- add(person, period, ["ni_class_2", "ni_class_4"])
)
* is_claimant_or_partner
)I checked that change in a scratch copy (
A second checker ran the same change against 546 YAML cases and 39 Python tests, and all passed. Add this case after the two floor cases in - name: Pension Credit floors the counted tax after taking off the actual tax on self-employed profit
# The floor cases' household, with a aged 70 and a self-employed profit of
# 2,000. The model presumes b, 26 years younger, to be a's child, so
# Pension Credit counts a alone. b's pay moves the 703 Child Benefit charge
# off a, so a's deduction is the 800 of tax the profit adds less 703: 97.
# The profit's actual tax (pension_credit_self_employment_income_tax) is
# 940: the 800, and the 140 it adds to the charge when b has no pay (843,
# 60% of 1,406 rounded down, against 703). The formula replaces that tax
# with notional tax, nil on 2,000, and deducts no other tax for a:
# max(0, 97 - 940) = 0. Income is the profit less the £20 weekly lone
# parent disregard: 2,000 - 1,040 = 960.
period: 2026
absolute_error_margin: 0.01
input:
people:
a:
age: 70
savings_interest_income: 70_000
self_employment_income: 2_000
b:
age: 44
employment_income: 80_000
child:
age: 3
benunits:
benunit:
members: [a, b, child]
would_claim_uc: false
households:
household:
members: [a, b, child]
country: ENGLAND
tenure_type: RENT_PRIVATELY
rent: 9_000
council_tax: 1_500
output:
is_claimant_or_partner: [true, false, false]
legacy_means_test_deducted_income_tax: [97, 20_838, 0]
pension_credit_self_employment_income_tax: [940]
pension_credit_income: [960]Text to change with it:
P2-2. The delta makes earnings a condition of the no-finance-costs limit, but the model reaches the limit without earningsThe delta added:
The code does not support the condition. Without earnings the deduction is Counterexample, England 2025, no earnings: a pensioner aged 70 with six children aged 10, a private pension of 35,300,
The minimum guarantee is 33,399.60. By hand, the counted Step 4 tax is (35,300 − 12,570) × 20% = 4,546, and the property tax is 500 or 660. The 3,000 reduction, shared pro rata, leaves:
The Marriage Allowance and the personal allowance taper also tie the counted tax to the profits (P3-4). Change needed:
P2-3. main moved after the brief, and the PR now conflicts with it
The only conflict is #2776, with a version bump, is main's only change since Change needed: merge main, and resolve the documentation as: "income under the Housing Benefit rules, including the separate "
"Working Tax Credit disregard before childcare. Income derived from "
"capital, such as rent from property, interest and dividends, is not "The rest of the PR's sentences stay as they are. On a scratch merge resolved that way, the YAML under CI has not run on that merge. P2-4. The bounds property asserts the benefit unit's whole rent, so it can fail on families its own strategy draws, and it cannot see the maskThis predates the delta: the PR added the property at 58e8cdc, and the delta leaves it alone. The delta's body leans on it (P3-2). At Take a
CI passes only because Change needed. Mask the reference, and pin the family. Import ZERO_INCOME = dict(
employment_income=0.0,
state_pension=0.0,
private_pension_income=0.0,
property_income=0.0,
savings_interest_income=0.0,
dividend_income=0.0,
)Then change the property's head to: @PROPERTY_SETTINGS
@given(populations, st.sampled_from(YEARS + [2007]))
# A couple 23 years apart: the model presumes the younger, who has the rent,
# to be the elder's child, so the measure counts none of it.
@example(
[
dict(
shape="couple_working",
members=[
("adult", dict(ZERO_INCOME, age=56, sublet_income=0.0)),
("adult", dict(ZERO_INCOME, age=33, sublet_income=5_000.0)),
],
region="NORTH_EAST",
tenure="RENT_PRIVATELY",
rent=6_000.0,
savings=0.0,
reported=False,
universal_credit=0.0,
)
],
2026,
)
def test_home_letting_income_bounds(units, year):
sim = Simulation(situation=situation(units, year))
counted = np.asarray(sim.calculate("legacy_benefits_home_letting_income", year))
# The claimant's and partner's rent and ages, which the measure counts.
counted_member = np.asarray(
sim.calculate("is_claimant_or_partner", year), dtype=float
)
rent = sim.map_result(
np.asarray(sim.calculate("sublet_income", year)) * counted_member,
"person",
"benunit",
)
pension_age = (
sim.map_result(
np.asarray(sim.calculate("is_SP_age", year), dtype=float) * counted_member,
"person",
"benunit",
)
> 0
)The rest of the property is unchanged. On a temporary copy it passes at the head ( P2-5. The #483 addendum's output 1 contradicts its own cases and the PR
Change needed. Replace item 1 with:
The addendum's other points are in P3-7. P3P3-1. The floor closes review 680's P2-1 only where the counted sum turns negativeThe floor binds only where the sum turns negative. Where the counted member also pays tax on counted income, the charge an uncounted member's earnings move off them still lowers the deduction. This predates the delta: the delta implemented P2-1's option (a) as asked. The household: England 2026, one child aged 3, rent 9,000, council tax 1,500. a is aged 64, with a pension of 20,000 and interest of 50,000. b is aged 44 and is presumed a's child. Head and base give the same:
So b's uncounted pay raises every test's income by 703. Fixing it needs per-test branches that zero only the counted members' earnings, or documentation. For the documentation:
P3-2. Two mutation-check bullets in the body are wrongThe mask mutant (body, Tests, " Replace the bullet with:
If P2-4 lands, replace the bullet's last sentence with: " The pension-age mutant ("The CTR pension-age test read from..."). Neither the law comment nor the body supports "before any council tax reduction scheme began" with a quoted date. At the head the model computes Replace the bullet's last sentence with:
P3-3. Test strength of the new cases and properties(a) No case pins the savings-credit-only half of the tariff gate. The mutant
The strategies never draw a head who applies alone beside a savings-credit-only couple. Add after - name: A household head applying alone keeps her tariff income when the couple she lives with gets savings credit only
# As the guarantee credit case, but the couple are 80 and 78 and the elder
# has a £20,000 private pension, so the model gives them savings credit of
# £928 a year and no guarantee credit. That award is theirs, so the head
# keeps her tariff income. The head, aged 90, reached State Pension age
# before 1 April 2021, so her allowance is 256 x 52 = 13,312. Tariff
# 8 x 52 = 416; tax (13,000 - 12,570) x 20% = 86; income 13,000 - 86 + 416
# = 13,330. CTR 2,000 - 20% x (13,330 - 13,312) = 1,996.40.
period: 2026
absolute_error_margin: 0.01
input:
people:
head:
age: 90
is_household_head: true
private_pension_income: 13_000
older_partner:
age: 80
is_parent: true
is_household_head: false
private_pension_income: 20_000
younger_partner:
age: 78
is_parent: true
is_household_head: false
benunits:
family:
members: [head, older_partner, younger_partner]
claims_all_entitled_benefits: true
would_claim_uc: false
households:
home:
members: [head, older_partner, younger_partner]
country: ENGLAND
local_authority: MAIDSTONE
council_tax: 2_000
savings: 14_000
output:
is_claimant_or_partner: [false, true, true]
council_tax_reduction_head_applies_alone: [true]
in_receipt_of_guarantee_credit: [false]
in_receipt_of_savings_credit_only: [true]
council_tax_reduction_assessable_capital: [14_000]
council_tax_reduction_tariff_income: [416]
council_tax_reduction_applicable_income: [13_330]
council_tax_reduction_applicable_amount: [13_312]
council_tax_reduction: 1_996.40(b)
Below the property's # A Scottish working-age head, whose scheme does not count the rent.
@example(
[
dict(
head_age=50,
parent_ages=(16, 16),
head_pension=12_000.0,
head_wages=0.0,
head_rent=5_200.0,
parent_rent=0.0,
region="SCOTLAND",
)
],
2026,
1_000.0,
)On a temporary copy this passes at the head and fails without the exception ( # Where her own rent with the extra exceeds the £20 weekly disregard, with
# a margin for float32 rounding, her letting income is positive.
above = np.array([(h["head_rent"] + extra) / 52 > 20.01 for h in households])
assert np.all(more_own[letting][above] > 0)The property's last check (:704-708) asserts only that the couple's legacy measure does not fall. An Body, invariant 7: replace the parenthesis "(a separate strategy: a liable head aged 40 to 95 with a pension, wages and rent, sharing...)" with:
(c) The new no-earnings landlord case asserts one route. With no finance costs the model never computes the expenses route, so nothing pins the invariance the case's name claims. Add a twin after it, named "2025 the same single landlord on expenses has the same Pension Credit income". Copy the case and add output:
legacy_means_test_deducted_income_tax: [5_612.40, 0, 0, 0, 0, 0, 0]
pension_credit_income: 35_019.60
pension_credit: 0
winter_fuel_payment_charge: [200, 0, 0, 0, 0, 0, 0]
income_tax: [6_472.40, 0, 0, 0, 0, 0, 0]It passes at the head; the file then has 43 cases. P3-4. The body's landlord section misnames the shared reduction, and its "same amount" needs a qualifierBody line 71. The model does not share the Marriage Allowance. "Only" is also false. Without earnings, the route moves the deduction through:
Replace "The model taxes property profits on top of pensions, so the route moves the deduction only through the share of a general reduction such as the Marriage Allowance." with:
Body line 72. The disregard is capped at net earnings after the employment income tax (
P3-5. Body and law comment: the voice pass, links and wordingThe voice pass. I compared the author's body (comments 6598582879 and 6598583207) with the applied body and the law comment, sentence by sentence. There are 23 changed sentences, plus the Law section's move to a comment: 24 changes in all. Twenty-two of the sentences keep the author's facts. Two changes need fixing:
The author's own text, which the voice pass left as it was. These sentences need changes too:
P3-6. Wording in the delta's code, tests, YAML and changelog
Reflow after editing.
Changelog, last sentence. "a household head who applies alone counts her own rent" makes the head the actor. Replace from "Council tax reduction counts the rent its own applicant..." to the end with:
Add P2-1's clause to it. P3-7. The #483 addendum, beyond output 1
Checked and soundC1 against the quoted law. The C1 households.
By hand, from the parameters read at 30 April (
So 80 × 52 = 4,160, 16,160 − 12,376 = 3,784, and 2,000 − 756.80 = 1,243.20. Against 7381a46 the four new cases give 3 failed and 1 passed, the control. The tariff case, by hand.
The floors.
By hand: Child Benefit is 27.05 × 52 = 1,406.60; 50% × 1,406 = 703; a's tax is 32,200 × 20% + 19,730 × 40% = 14,332, so a's deduction is 14,332 − 15,035 = −703. C2, by run and by hand (2025-26 values, read at 30 April 2025): minimum guarantee 227.10 single and 346.60 couple; children 78.10 for the first and 67.42 for others; earnings disregard £20 for a lone parent and £10 for a couple; savings credit maximum 1,006.51.
Each re-derived case still sits on its boundary: flipping the route flips Pension Credit, or the savings credit, and with it the charge. Case 2 remains a real limit, 6,272.40 against 6,312.40. With Mutants, each run on a scratch copy of the head ("killed" means a named test fails).
Each of the three properties can fail:
The split property's new strategy (pensions 12,570 to 20,000, interest 7,000 to 20,000) makes The merge.
CI at the head. The check runs on 1919788 tested the merge
The 5,545 is the body's 5,488 plus the 57 YAML cases main added since
Local runs at the head:
The voice pass and the addendum. I diffed the author's texts (comments 6598582879, 6598583207 and 6598583511) against the live body, the law comment and the brief's copy of the posted addendum. The body and law comment have the 24 changes in P3-5. The addendum has three changed lines: the branch name, twice, which is now right, and line 66, which rewords "Both are England, 2026-27" without changing its facts and replaces "is liable for" with "owes". Every backticked name exists at the head, except names that are not this repository's by design: the old variable name, main's removed parameter lists, #2090's names, policyengine-uk-data code, the Axiom paths, the data file and its hash, CI job names and the pseudo-code The addendum's companion cases by hand. Case (a): 5,200 / 52 = 100; less 20 = 80; × 52 = 4,160; income 16,160; applicable amount 12,376; 2,000 − 20% × 3,784 = 1,243.20. Case (b): 12,000 < 12,376, so 2,000. Under #483's rule, 7381a46 gives 2,000 and 1,243.20. Every figure in the addendum's text and YAML reproduces. What I couldn't check
Outside this PRThis is on main, untouched by the PR, and could be an issue there.
Generated by Claude Code |
|
Picking this up for the UK hub. Reply here if you're mid-change and I'll stop. Generated by Claude Code |
Resolve the Housing Benefit applicable income documentation: keep main's sentence on the separate Working Tax Credit disregard before the branch's sentences on income derived from capital. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LwQq6HUt6Xxx55oAoWZeeW
… self-employed profit Pension Credit floored the counted members' deduction at nil before it took off the tax actually payable on self-employed profit, which its notional tax replaces, so its tax line could still turn negative where an uncounted member's pay moved a Child Benefit charge off a counted member with profit. The formula now takes that tax out of the sum before the floor. A YAML case pins the household: Pension Credit income 960, not 1,803. The documentation and changelog say so, and the documentation says that the floor binds only where the sum turns negative. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LwQq6HUt6Xxx55oAoWZeeW
…d-alone cases - winter_fuel_payment_charge.yaml and uses_property_allowance: the property route moves the tax Pension Credit deducts without earnings too, through a shared tax reduction, the Marriage Allowance or the personal allowance taper. The no-earnings case names what it holds, and a twin pins the expenses route. - The bounds property counts only the claimant's and partner's rent and ages, and pins a couple 23 years apart, where the younger lets. - The head-alone property pins a Scottish working-age head, checks her letting income turns positive above the disregard, and checks the couple's legacy measure moves by the parent's extra rent. - A YAML case pins a head's tariff income beside a couple on savings credit only. - Wording: actors in the landlord cases and tests, and no fix history. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LwQq6HUt6Xxx55oAoWZeeW
juaristi22
left a comment
There was a problem hiding this comment.
Re-reviewed the new detailed feedback at unchanged head 1919788159e18ce20e29d815c1d8af85d4894b5e. PARTIAL — requesting changes for C3; prior C1/C2 remain resolved.
C3 — Reconcile the filed Axiom home-letting output contract. The actual #483 addendum defines output 1 weekly and separately for each recipient, but its case/YAML expects annual £4,160. Under its stated one-occupier assumption the £20 weekly disregard applies once to the applicant's and partner's combined receipts. Two £100 weekly receipts therefore give £9,360 annually, rather than £8,320 from separate disregards. Make the output, units and companion cases consistent; no encoder run is needed.
Three nonblocking code/documentation points also remain: the floor-before-self-employment-swap composition does not guarantee a nonnegative final deduction; the landlord limitation can occur without earnings; and the rent-bounds property's reference must use the same applicant/partner mask as production. The reported £843 Pension Credit comparison is a conditional model-contract example, not a confirmed statutory underpayment: its adult-child HICBC-transfer premise is not established by the law.
The existing NI IS/JSA capital-disregard exception-chain source gap remains. No new primary evidence closes it. Three focused diagnostics passed; the relevant 157 prior passes and final CTR evidence were retained after identity/dependency checks. Exact-head CI is green. Approval remains withheld.
|
Generated by Claude Code |
|
Generated by Claude Code |
|
Generated by Claude Code |
|
Generated by Claude Code |
|
Generated by Claude Code |
|
Ready for hub review Head: Commits on top of
Hub documents, posted above: Start check. The last push to FindingsP2-1. Pension Credit's floor.
P2-2. The landlord limit. In P2-3. The merge. I merged main at P2-4. The bounds property. In
P2-5 and P3-7. The #483 addendum.
P3-1. I applied the floor's limit in the documentation, in invariant 4 and in the law comment's sentence. I reran the household at the head: with b's pay at 80,000, a's deduction is 783 against a split of 1,486, and IS, HB and PC income come to 19,217 against 18,514 with b's pay at nil. That is 703 more, and CTR income moves from 19,920.60 to 20,623.60. P3-2. I rewrote both bullets from my runs (Tests, "At this head"). The mask bullet uses the review's text, with P2-4's last sentence, and the pension-age bullet uses the review's sentence. P3-3.
P3-4. I replaced body lines 71 and 72. The no-earnings sentence now cites two YAML cases. P3-5. I applied every replacement in the body and the law comment, and checked the Northern Ireland and Scotland bullets against comment 6603122584:
The #2002 bullet takes the review's text. I also:
P3-6. I applied each wording change:
Body. I updated the counts, cases, mutants and merge notes, and added a #2776 Overlaps bullet. Under "Enhanced FRS impact", the body now gives CI's Aggregate impact at Tests at the head (clean
|
| Mutant | Result |
|---|---|
Old floor order (1919788159e's pension_credit_income) |
Killed by the new case: 1,803 against 960. |
home_letting_income ignores its mask |
Killed. income_from_capital.yaml fails 3 of 69: "A dependant's sublet rent is not the claimant's", the head's own rent (legacy 4,160 against 0) and the converse (CTR 4,160 against 0). The bounds property fails at counted <= rent, and the head-alone property fails. The letting-income property passes. |
| Bounds property with the old, unmasked reference | Fails on the pinned couple (0 against 3,960). |
CTR pension-age test from is_SP_age |
Survives: the YAML under legacy_means_tests and CTR gives 409 passed, and the three properties pass. |
| No Scottish working-age exception | Killed by the head-alone property ([0]: 1000.0 (ACTUAL), 0.0 (DESIRED)). |
| CTR letting income nil | Killed by the head-alone property's above check. |
| Legacy measure keeps only members over SP age (it drops the parents' rent) | Killed by the new assert_allclose (0 against 1.00). |
| Tariff gate on guarantee credit only | Killed by the new case (0 against 416). |
Skipped, and judgment calls
- I didn't rerun Aggregate impact, because it needs survey data. I ran no test that needs the survey dataset.
- I didn't run the savings-credit-only case at 7381a46.
- I didn't check the NI or SSI text against legislation.gov.uk, which doesn't resolve from here. I relied on comment 6603122584.
- I replaced P3-3(b)'s weak "does not fall" check with the exact
assert_allclose, instead of keeping both. - "at c10280e when we checked" keeps Deduct only the tax Council Tax Reduction schemes allow #2834's SHA honest as that branch moves.
- The new law sentences on the self-employed household and the 1,020 invariance come from my runs.
Generated by Claude Code
juaristi22
left a comment
There was a problem hiding this comment.
Re-reviewed adopted head 7ba0ea7afd9352778c3bc0bf060eee84c784aa7a, incrementally from 1919788159e. PARTIAL — C3 still open; requesting changes.
- A1 and A3 resolved. The Pension Credit floor now follows removal of the actual self-employment tax, and the bounds oracle uses the production counted-member mask. C1/C2 remain resolved with fresh current-head tests; the new savings-credit-only head-alone CTR case also passes.
- A2 remains open only in the actual PR description. The committed explanation/tests are fixed, but the landlord paragraph still says that without earnings the route changes the deduction only through a general-reduction share. The personal-allowance taper is another route. Apply the proposed qualified wording to the actual description.
- C3 remains open in the filed issue. Actual #483 comment 6598849709 still defines output 1 as a weekly, per-recipient calculation while its companion case/YAML uses annual £4,160. It also needs a consistent combined-receipts, one-occupier disregard. The proposed addendum has not been filed there. This still fails the repository's dispatch-ready queued-issue requirement.
The operative NI Income Support/JSA capital-disregard exception-list gap remains unverified; the new commits supply no primary evidence resolving it.
Validation: 172 focused checks passed on the updated locked Core 3.34.1, including final PC/HB/CTR/HNI readbacks and the head-alone CTR cases. The floor trigger retains its explicit upstream-model qualification; it is not a new statutory HICBC award claim. Source integrity passed for 85 originals, 81 extracts and two retained renders. Hosted Python/aggregate CI was pending at capture and is checked separately from these local passes.
juaristi22
left a comment
There was a problem hiding this comment.
Re-reviewed the actual description and filed Axiom update at unchanged head 7ba0ea7afd9352778c3bc0bf060eee84c784aa7a. A2 and C3 are resolved; no review findings remain open. Review status remains PARTIAL because of the retained NI source gap.
The actual landlord explanation now includes the personal-allowance taper and the restricted example assumptions. Filed #483 addendum 6598849709, edited at 16:35:54Z, now defines the combined-receipts output annually and agrees with its companion cases. This is an adopted edit, rather than reliance on the earlier proposed addendum. C1/C2/A1/A3 remain resolved.
The operative NI Income Support/JSA capital-disregard exception lists still lack the complete unapplied-amendment reconstruction. The current source comment explicitly leaves that question open, so approval remains withheld.
The validated same-head 172 passing checks and their qualified native floor result were retained after code, dependency, runtime and artifact checks. No unchanged model tests were rerun. Current source integrity passed for 81 originals, 79 extracts and two retained renders.
|
On your re-review, we changed the description, the #483 addendum and the Northern Ireland comment; the head stays at A2. The description's Without earnings bullet now names three routes by which property profits change the tax on counted income: that tax's share of a general reduction, the Marriage Allowance and the personal allowance taper. C3. #483 comment 6598849709 now defines output 1, an annual amount, for the applicant and partner together: max(0, R / 52 − £20) × 52, where R is their combined annual rent from letting part of the home, assuming one occupier. The amending rules give both current Northern Ireland exception lists, which we've added, with links, to the Northern Ireland comment:
Both lists name paragraph 1, "The dwelling occupied as the home", in Great Britain's words. So the Northern Ireland decision maker counts rent for part of the home as income and treats interest and dividends as capital, as the model's Income Support does in every region. We've also settled the comment's other open point, the Income Support disregard for boarders. legislation.gov.uk prints an older £35 rule in IS (NI) Sch 9 para 20, which the Department of Health and Social Services replaced from April 1994 (SR 1994/77 reg 4(9)(c)). Since then the decision maker has disregarded up to £20 a week of each boarder's payments and half of any excess, as in Great Britain under SI 1994/527 reg 9(4). |
juaristi22
left a comment
There was a problem hiding this comment.
Re-reviewed the new NI primary evidence at unchanged head 7ba0ea7afd9352778c3bc0bf060eee84c784aa7a. COMPLETE — the retained NI gap is resolved; no findings or material gaps remain.
The official 1988/2006 amendments and linked income-disregard provisions establish the operative IS/JSA exception lists. The conversion and income-disregard lists were checked separately, including commencement and later-effects metadata. Six relevant physical pages were verified at 300 DPI; source integrity passed for 90 originals, 88 extracts and eight renders.
C1/C2/C3/A1/A2/A3 remain resolved. The same-head 172 passing checks were validated and retained without rerunning unchanged tests. The updated aggregate-result attribution was matched to the actual successful head run, without a new population analysis.
Approval is deferred because the PR remains a draft. The completed review can be retained for a later readiness/current-CI check.
juaristi22
left a comment
There was a problem hiding this comment.
Rechecked ready-for-review head 7ba0ea7afd9352778c3bc0bf060eee84c784aa7a. COMPLETE — no open findings or material evidence gaps. C1, C2, C3, A1, A2 and A3 remain resolved, including the Northern Ireland exception-chain evidence and the actual filed Axiom #483 contract/companion cases.
The code and description are unchanged from the completed review. Independent code/source retention checks verified the previous 172 passing checks, current runtime/dependency pins and the preserved primary evidence; no tests were rerun. The previously documented conditional Pension Credit/HICBC scenario qualifications remain. Applicable checks pass at this exact head, and the PR is now ready. Approved.
#1995's head-alone property asserted that the couple's rent never moves the household head's council tax reduction income. With this branch's rent-a-room relief, the couple's first receipts halve the head's limit, so part of her own letting rent becomes taxable and the tax the scheme deducts rises: Hypothesis found a head of 65 in the North East with £5,254.85 of rent whose income fell by £300.97 when the couple's rent rose from nil to £1. The equalities now hold where her taxable rent-a-room income stays the same, the couple's rent never raises her income, and an example pins that household. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QmsZJW5Q229j9RMjeKuoC2
#1995 moved the council_tax_reduction_relevant_income_based_benefit lookup in oxford_council_tax_reduction above its new capital rule. The merge keeps that lookup and this branch's Appendix 3 block without its second lookup. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…perty tests test_legacy_means_test_income_from_capital_properties.py, which #1995 brought, expected the Council Tax Reduction line to respond one-for-one to legacy_means_test_deducted_income_tax for every family. The line now deducts council_tax_reduction_tax_deduction, which reads that measure only in the English and Welsh working-age schemes and outside Great Britain, so at 8700602 invariant 6 failed on a Scottish working-age couple in 2025 whose Council Tax Reduction income did not move when the test raised the measure by a pound. Invariant 6 now expects no response in Scotland's working-age scheme and Great Britain's pensioner schemes, and invariant 1 reads the line's own deduction when it splits the change in Council Tax Reduction income. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QpcqEDGGUyf1Vv1xacPmhJ
main now has #1995: council_tax_reduction_assessable_capital (property at 90% less secured debt, the UC route, and passported capital by scheme), council_tax_reduction_tariff_income (the Welsh and Scottish working-age tariffs and the UC assumed yield) and council_tax_reduction_universal_credit_award, plus council_tax_reduction_home_letting_income. The devolved working-age route now reads them: - the capital limit tests council_tax_reduction_assessable_capital for every national claim, so council_tax_reduction_working_age_capital goes; - the route's income adds council_tax_reduction_tariff_income, so council_tax_reduction_working_age_tariff_income and its two parameter files (the same £1 per £250 over £6,000 as main's) go; - the passport excludes an applicant with an award of UC as council_tax_reduction_universal_credit_award defines it; - without UC, the route counts rent from letting part of the home in Wales, and in Scotland only before SSI 2021/249. main's two let-property cases in income_from_capital.yaml pinned Welsh and Scottish working-age awards under the shared formula; they are re-derived under the devolved route. Two devolved cases cover sub-tenant rent. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LwQq6HGqHxxCpeUeyX9Vy8
Summary
PolicyEngine UK made two errors in the legacy means tests: Income Support, Housing Benefit, Pension Credit and council tax reduction.
property_income, which is rent from property other than the home, as income.Under the regulations, income derived from capital counts as capital, not income, through the capital limits and tariff or deemed income. The tests disregard tax only on income they take into account. Rent for letting part of the claimant's own home stays income, less £20 a week per occupier.
This PR makes six changes:
property_incomefrom IS, HB (working age and pension age), Pension Credit and council tax reduction income.gov.local_authorities.council_tax_reduction.capital.sources). The formula values land and property at 90% of market value for the expenses of sale (capital.sale_expenses), then takes off any debt secured on that property (main's asset-specific inputs).council_tax_reduction_universal_credit_awarddefines the award: a payment, or a claim entitled before the benefit cap and deductions, which reduce an award rather than end it.council_tax_reduction_tariff_incomeadds the income the national schemes (England for pensioners, Wales, Scotland) treat capital as yielding, which the model left out before. It closes the tariff part of Pensioner council tax reduction: applicable amount (post-April-2021 allowance, children, severe disability premium) and income (Child Benefit, tariff income) #1933. It gives no tariff on the Pension Credit routes only where the Pension Credit belongs to the applicant: a household head who applies alone keeps her tariff income when the benefit unit's claimant and partner receive Pension Credit.legacy_benefits_home_letting_income(BenUnit): the claimant's and partner'ssublet_incomeless the sub-tenant disregard, assuming one occupier. The disregard stands at £20 a week; working-age claimants had £4 before April 2008, as did everyone before Pension Credit in October 2003. IS, HB and Pension Credit count it. Council tax reduction reads its own applicant's rent:council_tax_reduction_home_letting_income(BenUnit) applies the same function (home_letting_income) to the CTR applicant and partner (is_council_tax_reduction_applicant_or_partner), with the pension-age disregard where the applicant falls under the pension-age scheme (council_tax_reduction_pensioner). Where the liable household head applies alone, her own rent counts and the benefit unit's claimant's and partner's does not (SI 2012/2885 reg 2(1), Sch 1 paras 11 and 16(1)(v), Sch 5 para 10). Council tax reduction leaves it out where a working-age applicant's income comes from the Secretary of State's UC assessment, and in Scotland's scheme for people under pension age from 1 April 2022.legacy_means_test_deducted_income_tax(Person), which all four tests deduct in place ofincome_tax_before_winter_fuel_payment_charge. It takes the pre-charge tax lesslegacy_means_test_uncounted_income_tax_without_earnings: the tax on rent, interest and dividends, and the Step 7 charges other than the winter fuel payment charge, due if nobody had employed earnings or self-employment income. So earnings carry the extra tax they cause, including what they add on income from capital, as Pension Credit measures it (Apply the Pension Credit earnings disregards (SPC Regs 2002 Sch VI) #2019). Each test sums it over the members it counts and floors that sum at nil. Pension Credit replaces the tax on self-employed profit with notional tax (SPC reg 17(11)): it takes the tax the profit actually bears out of the sum before the floor and adds the notional tax after it. IS, HB and CTR deduct the tax the profit actually bears.legacy_means_test_income_tax(Person) on that branch without earnings: the Step 4 tax on earnings, pensions and taxable benefits (ITA 2007 s.23), less its share of the Step 6 reductions. The residential finance-cost reduction comes off the tax on rent, and the formula shares the other reductions, including main's Pension Tax Allowance, pro rata.legacy_means_test_uncounted_income_tax_without_earnings(Person) holds that uncounted tax; all four tests read it throughlegacy_means_test_deducted_income_tax.~council_tax_reduction_pensioner, from Apply the benefit cap to mixed-age couples on Universal Credit, and keep them off pensioner Council Tax Reduction #1944). A UC award held only by people over pensionable age no longer selects it.This PR leaves Universal Credit alone: #1950 holds its own fix. Tax credits already count investment and property income, as their regulations require (see "Tax credits").
Worked case (2026), from SPC Regs Sch IV para 18: a single pensioner aged 80 with a State Pension of £10,000 and £4,000 of rent from a let flat.
Worked case (2026), from SI 2012/2885 Sch 1 para 16(1)(v) and Sch 5 para 10: a liable household head aged 70 shares one benefit unit with two parents aged 16 and their baby, and applies alone in Maidstone. She has a £12,000 private pension, £5,200 a year of rent from letting part of the home, council tax of £2,000 and no savings.
Law
We set out the law this PR applies in this comment.
Tax credits
Tax Credits (Definition and Calculation of Income) Regs 2002 reg 3, Step One, adds "the investment income (as defined in regulation 10)" and "the property income (as defined in regulation 11)":
So
tax_credits_applicable_incomerightly counts all three, and this PR leaves it alone. Two small gaps remain, both in years before tax credits ended in April 2025:savings_interest_incomeincludes;What still counts
legacy_benefits_home_letting_income(IS, HB, Pension Credit);council_tax_reduction_home_letting_income(CTR, for its applicant and partner)self_employment_incomeData caveat and release gate. Owner-occupiers' rent from letting part of their home (FRS SUBRENT) sits in uk-data's
property_income, notsublet_income. It stays there until PolicyEngine/policyengine-uk-data#508 moves it. Until then this PR drops it from these means tests instead of counting it less £20.This PR prepares the channel; on its own it does not correct production assessments for that rent. PolicyEngine/policyengine-uk-data#508 records the order:
property_incomeequals the newsublet_income);In the raw FRS 2024-25, 33 owner households report SUBRENT, a weighted £0.41bn a year. Fewer than ten of them report Pension Credit, Income Support, Housing Benefit, JSA, ESA or council tax reduction, by uk-data's own benefit codes.
The landlord's property allowance election and Pension Credit
uses_property_allowancecompares routes, on income tax including the winter fuel payment charge, for landlords with finance costs to relieve; other landlords use the allowance whenever it lowers taxable property income. On main, Pension Credit counted property profits net of their tax, so the route moved Pension Credit income directly. Under this PR it counts no property profits (SPC Sch IV para 18) and deductslegacy_means_test_deducted_income_tax, so the route reaches Pension Credit only through the tax it deducts:other_tax_credits; through the Marriage Allowance, whose amount and eligibility read income including the profits; and through the personal allowance taper. Two YAML cases show a single landlord without earnings, tax reductions or a tapered personal allowance whose Pension Credit income stays at £35,019.60 on both routes.winter_fuel_payment_charge.yaml, 2025): wages £27,000, private pension £9,070, five children, finance costs £1,500. Pension Credit income is £29,830 with the allowance and £29,970 with expenses, against the model's minimum guarantee of £29,893.76, so only the allowance route gets Pension Credit (£63.76) and the exemption. The landlord elects for partial relief: £5,200 against £5,060 + £200.uses_property_allowance's documentation names: wages £31,562, private pension £9,070, six children. Pension Credit income is £33,479.60 with the allowance and £33,319.60 with expenses, against £33,399.60, so the allowance ends £80 of Pension Credit and costs £6,312.40 against £6,272.40. The model takes the allowance.test_property_income_properties.py::test_a_landlord_without_finance_costs_takes_the_allowance_even_where_it_costs_morepins the same household, and the pensioner property draws these wages.Invariants (stated and tested)
policyengine_uk/tests/test_legacy_means_test_income_from_capital_properties.py(Hypothesis) checks these over populations of up to four families: single, couple and lone-parent, pension and working age, in five regions including Scotland, Wales and Northern Ireland, in 2025 and 2026:legacy_means_test_deducted_income_tax, summed over the members it counts) and its earnings disregard do not move, which covers every unit without earnings.pension_credit_reported_capital. It leaves the CTR assessment when the savings credit ends, because the general route uses the household capital. The data causes this limit, and the route should not reuse a former Pension Credit determination.legacy_means_test_income_tax≤ min(income_tax,earned_income_tax), with equality toincome_taxwhen a person has no savings, dividend or property income and no Step 7 charge, the winter fuel payment charge included.legacy_means_test_deducted_income_tax:income_tax_before_winter_fuel_payment_charge. The upper bound holds for every person, since the uncounted tax never falls below nil. The lower bound holds for every generated person, and for the claimant and partner together, but the formula sets no per-person floor. Where one partner's pay moves the High Income Child Benefit Charge off the other, that partner's value turns negative. In a YAML case, one partner has interest of 70,000 and the other pay of 80,000, and their values come to −703 and 20,838. Together those make the 19,432 of tax on the pay plus the 703 of charge the pay adds. Each test floors the counted members' sum at nil, so where it counts only the member whose value turns negative it deducts nothing (two YAML cases, covering IS, HB, Pension Credit and CTR). Pension Credit takes the tax that the claimant's and partner's self-employed profit actually bears out of that sum before the floor, and adds its notional tax after it, so its deduction never turns negative. A third YAML case has one child, a member aged 70 with interest of 70,000 and a self-employed profit of 2,000, and a member aged 44 with pay of 80,000, whom the tests do not count. Pension Credit income comes to 960, the profit less the £20 weekly disregard. The floor binds only where the sum turns negative. Take a household in England in 2026 with one child, a member aged 64 with a pension of 20,000 and interest of 50,000, and a member aged 44 with pay of 80,000, whom the tests do not count. Each test deducts 783 for the elder rather than 1,486, so each test's income rises by 703.legacy_means_test_income_tax, except where the nil floor on the uncounted tax binds, where it gives the pre-charge tax (the Step 4 split then exceeds it).test_deducted_tax_without_earnings_is_the_step_4_splitdraws its own families for this: each adult has no earnings, a private pension of 12,570 to 20,000, interest of 7,000 to 20,000, and rent and dividends of up to 20,000, so the uncounted tax is positive. The property asserts that some drawn person'slegacy_means_test_uncounted_income_tax_without_earningsexceeds nil.legacy_means_test_income_tax. Hypothesis found no counterexample in the generated families, which stay inside the basic rate band, hold Marriage Allowance at nil and have no Child Benefit charge. Outside them it fails for the couple above (−703 against a split of nil), as intended.council_tax_reduction_tariff_income≤ £2,088, the yield at the £16,000 limit (40 steps of £250 at £4.35 a month). The formula gives none in Northern Ireland and to English people under pension age, whose local schemes set their own. It never falls when savings rise while the family stays on the same route: pensioner, UC award, passport, guarantee credit, savings credit only.legacy_means_test_uncounted_income_tax_without_earnings. The test checks it from the formulas, not the shared variable. With each person's deduction raised by 1, each test's income falls by the number of members it counts, adjusted for an earnings disregard capped at net earnings. Raisinglegacy_means_test_income_taxby 1 with the deduction held moves none of them.council_tax_reduction_home_letting_incomeequalslegacy_benefits_home_letting_income(the two use different pension-age tests, but both disregards have stood at £20 since April 2008). Where the head applies alone, her CTR income moves with her own rent by the change incouncil_tax_reduction_home_letting_incomewherever the scheme counts it (all but Scotland's working-age scheme). That letting income exceeds nil wherever her rent exceeds the £20 weekly disregard. A separate strategy draws these heads. Each head, liable for council tax and aged 40 to 95, lives in North East England, London, Wales, Scotland or Northern Ireland. She has a pension and wages of up to 20,000 each and rent of up to 10,000, any of which may be nil, and shares a benefit unit with parents aged 16 or 17 and their baby. Her CTR income does not move with a parent's rent, which the couple's ownlegacy_benefits_home_letting_incomecounts, less the £20 weekly disregard. The property pins a Scottish working-age head with rent of £5,200, whose scheme counts none of it.Three of main's property tests now hold or subtract the taxes these tests deduct:
test_means_tests_see_the_loss_only_through_tax(trading losses) holdslegacy_means_test_income_tax,legacy_means_test_uncounted_income_tax_without_earningsandlegacy_means_test_deducted_income_taxalongside the pre-charge income tax. The simulation recomputes each from the tax components, so a loss that moves interest between bands moved Housing Benefit income with the income tax held: by 1,047 in the failing family. Its separate failure on main (Trading-loss property test fails when a loss wipes out a profit beside a private pension (UC earned income) #2832) stays the same here.test_council_tax_reduction_pension_credit_properties.pydeductslegacy_means_test_deducted_income_tax.#1909's CTR property tests now use the new income and capital definitions as their reference, recomputed from the regulations rather than read from the variables under test:
The impact runs below (at b379e00, before the shared tax measure) add a differential check. In 2026 the rent step lowers each means-test income by the unit's rent less its tax, wherever neither run hits a floor, to float32 rounding. The check sums the rent and tax over the members each test counts. UC and tax credits match in every year. Under the shared tax measure this no longer holds for HB and CTR where a member has earnings.
Worked cases for the tax measure (2026)
Single adults aged 40 in England with no other income, except (c). "Before" means 16d6dd3, where IS, HB and the CTR line deducted
legacy_means_test_income_tax; "after" means 7381a46 and later, where all four deductlegacy_means_test_deducted_income_tax. Pension Credit's tax line does not change. No household has an award of IS, HB, Pension Credit or CTR: none rents, and each has too much income.legacy_means_test_income_taxBy hand, from the 2026-27 parameters (personal allowance 12,570; 20% to 37,700 of taxable income, 40% above; savings starting rate 0% on 5,000 less taxable non-savings income; personal savings allowance 1,000 basic-rate, 500 higher-rate; employee NI 8% from 12,570):
income_tax_caused_by_earnings.yamlholds (b) and (d) as YAML cases. (b) pins IS, HB and the CTR line deducting the tax the pay adds on interest (13,000, not 3,486). (d) pins the same for rent, net of the finance-cost reduction (2,686, not 1,486). The same file adds the three Step 7 cases above, the couple case under Invariants, the two floor cases, and the Pension Credit floor case with self-employed profit.Tests
uv sync --frozen --extra dev.gov/dwp/legacy_means_tests,gov/dwp/pension_credit,finance/benefit/family/housing_benefitandgov/local_authorities/council_tax_reduction, plusproperty_income_after_finance_costs.yamlandwinter_fuel_payment_charge.yaml: 1,376 passed, none failed. One file at a time:income_from_capital.yaml69 passed (1 new case: the tariff of a head who applies alone beside a couple on savings credit only);income_tax_caused_by_earnings.yaml9 passed (1 new case: Pension Credit's floor with self-employed profit);winter_fuel_payment_charge.yaml43 passed (1 new case: the no-earnings landlord on expenses);property_income_after_finance_costs.yaml6 passed.pension_credit_income1,803 against 960), and passes here. We ran the floor cases' household with the counted member aged 70 and a self-employed profit P of 0, 1,020, 2,000, 3,000 and 5,000. Pension Credit income comes to 0, 0, 960, 1,960 and 3,960 here, the same as with the uncounted member's pay at nil. At 1919788 it came to 0, 478, 1,803, 2,873 and 5,014. At P = 1,020, inside the disregard, it stays at nil.-n 4:test_housing_benefit_segment_masks.py30 passed,test_housing_benefit_wtc_disregard.py55 passed,test_housing_benefit_wtc_disregard_segments.py18 passed.-n 4each:test_legacy_means_test_income_from_capital_properties.py9 passed;test_pension_credit_earnings_disregard_properties.py5 passed;test_pension_credit_tax_branch_reuse.py3 passed;test_property_income_properties.py16 passed;code_health3,126 passed.test_home_letting_income_boundspassed: Hypothesis reports 500 passing examples, none failing, and 57 invalid draws..github/check_fix_pr.pyon this title and body, the waypr_fix_evidence.yamlruns it:Fix evidence: PASS, 13 changed executable tests.uvx ruff@0.17.2 format --check .,ruff check .,ruff check --select F811,F821 policyengine_ukandgit diff --check 1919788159e..HEAD: clean.git merge-tree --write-tree --name-onlyof this head: with main at 1d4d020 and Hold UC trade tax parts in the trading loss netted world #2849's head cd994ad, clean; with Model rent from boarders, lodgers and sub-tenants for the householder #2002's head f7788dc, conflicts inlegacy_benefits_home_letting_income.pyandcouncil_tax_reduction_applicable_income.py(see Overlaps); with Deduct only the tax Council Tax Reduction schemes allow #2834's head c10280e, conflicts incouncil_tax_reduction_applicable_income.pyandtest_council_tax_reduction_pension_credit_properties.py.pension_credit_income1,803 against 960).home_letting_incomeignoring its mask: killed.income_from_capital.yamlfails 3 cases: the converse (CTR counts the parent's 4,160), the head's own rent (the couple's legacy measure counts her 4,160) and "A dependant's sublet rent is not the claimant's" (4,160 against nil);test_a_head_applying_alone_counts_only_her_own_letting_incomefails too.test_council_tax_reduction_letting_income_matches_the_legacy_measurecannot catch this mutant: it compares two measures that callhome_letting_incomewith the same members in every family it checks. The bounds property kills it (counted income above the claimant's and partner's rent).is_SP_age) in place ofcouncil_tax_reduction_pensioner: survived the YAML undergov/dwp/legacy_means_testsandgov/local_authorities/council_tax_reduction(409 passed) and the bounds, letting-income and head-alone properties (3 passed). The mutant changes no value the model computes: the model computescouncil_tax_reduction_pensioneronly from 2015, and the two pension-age tests choose between disregards that have both stood at £20 since April 2008.council_tax_reduction_applicable_income: killed by the head-alone property on its Scottish example (CTR income moves by 1,000 against nil).(applicant_family & in_receipt_of_guarantee_credit) | in_receipt_of_savings_credit_only: killed by the new savings-credit-only case (council_tax_reduction_tariff_income0 against 416).uv sync --frozen --extra dev.Test / YAMLruns it (policyengine-core test policyengine_uk/tests/policy -c policyengine_uk), at 0346ada, which differs from 1919788 only in the changelog fragment: 5,488 passed, none failed (CI counted 5,481 at 7381a46; 1919788 adds 7 cases). CI'sTest / YAMLon the merge of 1919788 into main at 52b3080 passed 5,545.income_from_capital.yaml: 68 passed (4 new cases: the head's own rent, the control in separate benefit units, the converse, and the tariff of a head who applies alone).income_tax_caused_by_earnings.yaml: 8 passed (2 new floor cases).winter_fuel_payment_charge.yaml: 42 passed (the three landlord cases re-derived, and one new case without earnings).-n 4each, at 0346ada:test_property_income_properties.py16 passed;test_legacy_means_test_income_from_capital_properties.py9 passed (two new properties);test_council_tax_reduction_claimant_properties.py3 passed;test_council_tax_reduction_pension_credit_properties.py9 passed;code_health3,123 passed.legacy_benefits_home_letting_income: killed by 2 cases inincome_from_capital.yamland the head-alone property;pre_charge - 0.5 * uncounted: killed bytest_deducted_tax_without_earnings_is_the_step_4_split, which at 7381a46 it survived.uv sync --frozen --extra dev.income_from_capital.yaml(64 cases) and the newincome_tax_caused_by_earnings.yaml(6 cases): all pass.gov/dwp,finance/benefit,gov/local_authorities/council_tax_reductionandproperty_income_after_finance_costs.yaml: 2,985 passed.Test / YAML3 failed and 5,478 passed,Test / Python 7/71 failed and 1,676 passed. The three landlord cases inwinter_fuel_payment_charge.yamlfailed, and so didtest_property_income_properties.py::test_a_landlord_without_finance_costs_takes_the_allowance_even_where_it_costs_more. All four pinned Pension Credit counting property profits. They also failed at 16d6dd3 and passed on main at 46a6067. 1919788 re-derives them (see "The landlord's property allowance election and Pension Credit").-n 4: 449 passed, 1 skipped (test_claims_all_entitled_benefits.py, which needs the survey dataset).code_health: 3,122 passed. Ruff format and check,ruff check --select F811,F821andgit diff --check: clean.legacy_means_test_income_tax, the YAML set above fails 12 cases: 9 inincome_from_capital.yaml, (b) and (d) inincome_tax_caused_by_earnings.yaml, and main's "Housing Benefit, no finance costs". The property files fail 4 tests: this PR's invariance and differential tests,test_awards_follow_the_pension_credit_routesandtest_not_claiming_pension_credit_never_raises_a_recipients_ctr.income_from_capital.yaml: the nine working-age cases with earnings and rent, interest or dividends, listed below with their new values, each worked by hand. This PR adds one case: the shared Step 6 reduction without earnings.property_income_after_finance_costs.yaml: main's "Housing Benefit, no finance costs" moves from 24,654 to 24,540, as below.gov/dwp/legacy_means_tests/income_from_capital.yaml: 69 cases we worked by hand, with the arithmetic in each case. Where a case supplies an amount (an applicable amount, an award, a tax component) to isolate one rule, its comment says so, and the case then checks a model-contract expectation, not a complete statutory award.property_rental_income). Main now treats a reported profit above £1,000 as already net of the allowance.winter_fuel_payment_charge.yamland the matching Python test: re-derived with wages, as described above. Their expected Pension Credit incomes keep main's boundaries (£29,830 against £29,970; £33,479.60 against £33,319.60; savings credit £16 against nil), and their expected income tax moves with the new inputs (£5,200; £6,312.40 against £6,272.40; £6,943.51).test_savings_credit_only_receipt_properties.py: the owner now has savings of 12,000 and property of 4,400 (main's tests used 5,000), so that the award comparison stays live. On Pension Credit, capital of 16,400 exceeds the 16,000 limit, so the savings-credit-only CTR award comes to nil (savings credit 11.958 a week, 621.82 a year). Once Pension Credit stops, CTR counts the property less 10%: 12,000 + 3,960 = 15,960, with tariff income of 624 a year. Income of 12,480 + 624 = 13,104 falls short of the 13,312 applicable amount, so CTR pays the whole 2,000. The known-family check compares both capital and the award.pension_credit_earnings_disregard.yamlrepeats the wages case with 1,040 of statutory sick pay in place of the wages: income stays 12,000, and the uncounted tax is 1,086.pension_credit_earnings_disregard.yaml. A claimant aged 70 in 2024-25 has State Pension 12,000, interest 12,000 and attendance allowance. Both cases move from 10,914 to 12,000, and they still agree with each other.legacy_means_test_uncounted_income_tax_without_earnings= 1,086. The tax deducted is 1,388 − 1,086 = 302, and income is 12,000 + 1,040 − 302 − 738 = 12,000.test_pension_credit_earnings_disregard_properties.py(test_statute_bounds_differential_and_monotonicity) now subtracts the same term,income_tax_before_winter_fuel_payment_chargelesslegacy_means_test_income_tax, from its own no-earnings simulation (not the model's branch). It also asserts that the model'slegacy_means_test_uncounted_income_tax_without_earningsequals that reference, as a differential check of the branch. Its draws include interest of 0 or 3,000, so the property reaches both the nil and non-nil cases.income_from_capital.yaml63 passed; the YAML undergov/dwp,finance/benefit,gov/local_authorities/council_tax_reductionandproperty_income_after_finance_costs.yaml2,978 passed;code_health3,120 passed. We reverted each change in the merge in turn and reran that YAML: each revert failed 1 to 5 cases, except that startinglegacy_means_test_income_taxfromincome_taxmakes Pension Credit and Income Support circular.test_means_tests_see_the_loss_only_through_taxfails on main and here for a single 18-year-old with wages 40,000, profit 9,000, private pension 20,000 and a trading loss of 16,000:uc_earned_incomecomes to 32,319.60 with the loss against 30,519.60 when the loss nets the profit, a gap of 1,800 (20% of 9,000). This PR does not change that Universal Credit check.max_(0, …)floor changes nothing, since counted tax never exceedsincome_tax.Enhanced FRS impact
CI's Aggregate impact at 7ba0ea7. CI's Aggregate impact job (run 41667425314), on the merge of 7ba0ea7 into main at 9b456bd, reports Pension Credit −£452.8m in 2025-26 and −£566.9m in 2026-27, the same figures it gave at 1919788 against main at 52b3080 (run 41601607215). It finds no check broken by this change, one failing on both sides and none fixed.
Stale after the shared tax measure. IS, HB and the CTR line now deduct the tax earnings add on rent, interest and dividends. That changes the Housing Benefit, council tax reduction and household net income figures below, and the IS figure for 2025, for every unit with earnings and income from capital. It also changes the HB and CTR columns of steps 3 and 4 and the step 3 differential check, which assumed the rent step lowers HB and CTR income by the rent less its whole tax. The Pension Credit figures change only through the merge of main described next: its tax line does not move.
Pending. The figures below come from runs at b379e00 against main at 76cfc9e. Since then this branch has merged main at 46a6067, which brings #2211 (Housing Benefit exclusions), #2509 (statutory pay as Pension Credit earnings), #2545 (Step 6 reductions limited to the Step 5 tax), the winter fuel payment charge and the claimant-and-partner mask. Each changes code these figures rest on, so they need a rerun on the certified dataset before they describe this branch.
We calculated every figure from
Microsimulationruns on copies of the certified dataset:enhanced_frs_2024_25.h5from policyengine-uk-data 1.58.1 (sha256056e80fd8e91…, the filetests/certified_dataset.yamlpins);The other benefits:
2026, step by step. Each run adds one part of the change to the last. We ran interest and dividends as separate steps and show them together, because the interest step alone rests on fewer than ten records.
Why step 4 is so large. On main, Pension Credit income counts neither dividends nor interest (its list in
guarantee_credit/income.yaml), but it deducts the person's whole income tax, including the tax on them. So main cuts the Pension Credit income of a pensioner with large dividends by tax on income it never counted. Among the 66 FRS and 2 SPI records that lose:Their weighted-mean dividends reach £44,078 (median £2,149), against £14,443 of Pension Credit capital. Those dividends come from policyengine-uk-data's imputation: 1.58.1's
impute_income()replaces every FRS respondent'sdividend_incomewith an SPI-trained QRF draw (impute_over_incomes(dataset, model, ["dividend_income"])). So the size of step 4 rests on imputed dividends that the recorded capital does not support. Its direction follows from the law.Why step 1 changes nothing here. In 2026, 292 benefit-unit records receiving CTR have land or other property:
council_tax_benefitkeeps their reported awards.Step 2 moves little for the same reason: tariff income changes awards only where the model simulates a national scheme.
Checks between runs, all passing:
Most of the gross flow comes from the data, not the rule
The dataset lacks let-property capital.
In law those units hold the let property as capital, which yields deemed income or ends entitlement. Here the model takes the rent out and finds no capital behind it. So step 3's gain overstates what the change does to a household whose capital the data records.
uk-data imputes dividends (step 4 above).
Two sensitivities (runs of main and this branch, 2026):
corporate_wealthand let-property value where needed to at least interest / 4%, dividends / 4% and rent / 7%. We assumed those yields; the survey didn't report this capital. With the capital recorded, Pension Credit gainers fall from 7.2k to 3.4k units, and CTR falls (−£8.0m): main's CTR capital holds savings only, so the raised property values reach only this branch's capital and tariff income.For any one household, the direction of the change follows from the law. The net total depends on how the data record capital and dividends.
Not counted here, though the law counts it:
property_incomeuntil Move SUBRENT from property_income to sublet_income, after policyengine-uk reads sublet_income policyengine-uk-data#508;Overlaps
boarder-lodger-rent). It extendslegacy_benefits_home_letting_incomewith lodger and boarder receipts, and counts rent-a-room tax as tax on counted income inlegacy_means_test_income_tax. Once this lands, Model rent from boarders, lodgers and sub-tenants for the householder #2002 must:home_letting_income, the functionlegacy_benefits_home_letting_incomeandcouncil_tax_reduction_home_letting_incomenow share, so that council tax reduction counts its applicant's receipts too;rent_a_room_income_tax_without_earnings, where council tax reduction does not count the rent, inside the floored sum oflegacy_means_test_deducted_income_tax, so that the floor still keeps the deduction from turning negative (git merge-treeshowscouncil_tax_reduction_applicable_income.pyconflicting).legacy_means_test_income_taxon the branch without earnings, where rent-a-room income stays, so counting its tax there keeps it deducted in all four tests.housing_benefit_applicable_income's documentation, where we kept main's sentence on the separate Working Tax Credit disregard before this PR's sentences on income derived from capital. Disregard WTC before the Housing Benefit childcare deduction #2776 addshousing_benefit_wtc_disregardtohousing_benefit_applicable_income's formula afterhb_income_before_deductions, and changes no variable this PR reads.legacy_benefits_home_letting_incomecounts only the claimant's and partner's rent, andcouncil_tax_reduction_home_letting_incomeonly the CTR applicant's and partner's.employed_earnings_sources(...)andself_employment_income, with the same any-earnings shortcut. It runs throughperson_variable_with_inputs_zeroed, notincome_tax_with_inputs_zeroed, sotest_pension_credit_tax_branch_reuse.py's two expected counterfactuals stay the same. Apply the Pension Credit earnings disregards (SPC Regs 2002 Sch VI) #2019's no-earnings tax now sets the deduction in all four tests throughlegacy_means_test_uncounted_income_tax_without_earnings; Pension Credit's figure stays the same, except where the floor on the counted members' sum, less the tax on self-employed profit, binds.hb_income_before_deductions. This PR's two changes sit inside it:legacy_benefits_home_letting_incomereplacesproperty_income_after_finance_costs, andlegacy_means_test_deducted_income_tax, summed over the counted members and floored at nil, replaces the pre-charge income tax. Exclude disregarded income from Housing Benefit assessment #2211's Child Benefit and SSMG exclusions, the claimant and partner mask, the scoped JSA and ESA readers, the counted disability benefits, maintenance, widowed parent's allowance and the pension-deduction parameter stay as they were.income_tax_before_winter_fuel_payment_charge; this PR's measure starts from it, so the charge stays out. Main's landlord election cases now carry wages, so that the election still reaches Pension Credit (see above).applicant_has_universal_credithelper now reads this PR'scouncil_tax_reduction_universal_credit_award, so the local schemes, Oxford and this PR's routes share one award test. Its passport already gives a household head who applies alone none of the benefit unit's awards. This PR keeps her tariff income when the benefit unit's claimant and partner receive Pension Credit, and its home-letting income counts her own rent, not the benefit unit's claimant's and partner's.claude/ctr-deductions-review-kpqmyj, at c10280e when we checked). Deduct only the tax Council Tax Reduction schemes allow #2834 addscouncil_tax_reduction_tax_deduction, a BenUnit variablecouncil_tax_reduction_applicable_incomededucts. Its formula prices Scotland's working-age scheme and every pensioner scheme, and leaves the other schemes (England and Wales at working age, and Northern Ireland) on the whole liability. Whichever lands second combines them so:council_tax_reduction_tax_deduction;legacy_means_test_deducted_income_taxfor the applicant and partner, summed and floored at nil, with their National Insurance, in its place;property_income) joins that list, and zeroing it removes the finance-cost reduction with it, becauseproperty_finance_cost_reliefcan't exceed the property profits.git merge-treeagainst Deduct only the tax Council Tax Reduction schemes allow #2834's head c10280e conflicts incouncil_tax_reduction_applicable_income.pyandtest_council_tax_reduction_pension_credit_properties.py.council_tax_reduction_assessable_capitalandcouncil_tax_reduction_universal_credit_award, so that the property, the secured debt and the passported disregards carry over.Related gaps (not changed here)
council_tax_reduction_pensionertreats a family entitled to UC as having an award whether or not it claims (main's approximation). The UC routes here require a claim.sublet_income.axiom: TheAxiomFoundation/rulespec-uk#483 queued. It covers:
Reproducer:
policyengine_uk/tests/policy/baseline/gov/dwp/legacy_means_tests/income_from_capital.yaml::Pension Credit ignores rent from a let property,policyengine_uk/tests/policy/baseline/gov/dwp/legacy_means_tests/income_from_capital.yaml::Welsh working-age tariff income on a let property below the capital limit,policyengine_uk/tests/policy/baseline/gov/dwp/legacy_means_tests/income_from_capital.yaml::Wales uses the Universal Credit capital assessment when the benefit cap reduces the award to nil,policyengine_uk/tests/policy/baseline/gov/dwp/legacy_means_tests/income_from_capital.yaml::A household head applying alone counts her own rent from letting part of the home in council tax reduction,policyengine_uk/tests/test_legacy_means_test_income_from_capital_properties.py::test_income_from_capital_changes_no_legacy_means_test🤖 Generated with Claude Code