From ce44f0bc1bb34caf7c328c4d43860c653614622d Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sat, 31 Oct 2026 21:48:32 -0400 Subject: [PATCH 1/7] Apply the prescribed accommodation capital threshold to legacy tariff income MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit IS Regs 1987 reg 53(1A)-(1C), JSA Regs 1996 reg 116(1A)-(1C) and ESA Regs 2008 reg 118(2)-(4), and their Northern Ireland equivalents, set a £10,000 lower capital threshold where the claimant lives permanently in a care home, independent hospital, Abbeyfield Home or (GB) Polish Resettlement Act accommodation with care. The three tariff income formulas read a new pre-assessed input, legacy_prescribed_accommodation_capital_exception, which defaults to false. Housing Benefit's circumstances differ (HB Regs 2006 reg 52(4), (8)), so the existing HB input stays separate. Fixes #1758. Co-Authored-By: Claude Opus 5.5 --- changelog.d/1758.fixed.md | 1 + .../income/capital/residential_threshold.yaml | 13 ++ .../income/capital/residential_threshold.yaml | 15 ++ .../capital/residential_threshold.yaml | 15 ++ .../esa_income_residential_capital.yaml | 149 ++++++++++++++++++ .../income_support_residential_capital.yaml | 141 +++++++++++++++++ .../jsa_income_residential_capital.yaml | 126 +++++++++++++++ .../gov/dwp/esa_income_tariff_income.py | 15 +- .../gov/dwp/income_support_tariff_income.py | 16 +- .../gov/dwp/jsa_income_tariff_income.py | 16 +- ...scribed_accommodation_capital_exception.py | 41 +++++ 11 files changed, 545 insertions(+), 3 deletions(-) create mode 100644 changelog.d/1758.fixed.md create mode 100644 policyengine_uk/parameters/gov/dwp/ESA/income/capital/residential_threshold.yaml create mode 100644 policyengine_uk/parameters/gov/dwp/JSA/income/capital/residential_threshold.yaml create mode 100644 policyengine_uk/parameters/gov/dwp/income_support/means_test/capital/residential_threshold.yaml create mode 100644 policyengine_uk/tests/policy/baseline/finance/benefit/family/esa_income/esa_income_residential_capital.yaml create mode 100644 policyengine_uk/tests/policy/baseline/finance/benefit/family/income_support/income_support_residential_capital.yaml create mode 100644 policyengine_uk/tests/policy/baseline/finance/benefit/family/jsa_income/jsa_income_residential_capital.yaml create mode 100644 policyengine_uk/variables/gov/dwp/legacy_prescribed_accommodation_capital_exception.py diff --git a/changelog.d/1758.fixed.md b/changelog.d/1758.fixed.md new file mode 100644 index 0000000000..9b1f51c48a --- /dev/null +++ b/changelog.d/1758.fixed.md @@ -0,0 +1 @@ +Income Support, income-based JSA and income-related ESA now apply the £10,000 lower capital threshold for tariff income where the claimant lives permanently in prescribed accommodation, read from the new legacy_prescribed_accommodation_capital_exception input, which defaults to false. diff --git a/policyengine_uk/parameters/gov/dwp/ESA/income/capital/residential_threshold.yaml b/policyengine_uk/parameters/gov/dwp/ESA/income/capital/residential_threshold.yaml new file mode 100644 index 0000000000..c0e6c3b43f --- /dev/null +++ b/policyengine_uk/parameters/gov/dwp/ESA/income/capital/residential_threshold.yaml @@ -0,0 +1,13 @@ +description: Income-related Employment and Support Allowance capital tariff threshold where the claimant lives permanently in prescribed accommodation. +metadata: + label: Income-related ESA prescribed accommodation capital tariff threshold + unit: currency-GBP + period: year +values: + 2008-10-27: + value: 10000 + reference: + - title: The Employment and Support Allowance Regulations 2008, regulation 118(2)-(4) + href: https://www.legislation.gov.uk/uksi/2008/794/regulation/118 + - title: The Employment and Support Allowance Regulations (Northern Ireland) 2008, regulation 118(2)-(4) + href: https://www.legislation.gov.uk/nisr/2008/280/regulation/118 diff --git a/policyengine_uk/parameters/gov/dwp/JSA/income/capital/residential_threshold.yaml b/policyengine_uk/parameters/gov/dwp/JSA/income/capital/residential_threshold.yaml new file mode 100644 index 0000000000..34fe82b273 --- /dev/null +++ b/policyengine_uk/parameters/gov/dwp/JSA/income/capital/residential_threshold.yaml @@ -0,0 +1,15 @@ +description: Income-based Jobseeker's Allowance capital tariff threshold where the claimant lives permanently in prescribed accommodation. +metadata: + label: Income-based JSA prescribed accommodation capital tariff threshold + unit: currency-GBP + period: year +values: + 1996-10-07: + value: 10000 + reference: + - title: The Jobseeker's Allowance Regulations 1996, regulation 116(1A)-(1C) + href: https://www.legislation.gov.uk/uksi/1996/207/regulation/116 + - title: The Jobseeker's Allowance (Amendment) Regulations 1996, regulation 12(b) + href: https://www.legislation.gov.uk/uksi/1996/1516/regulation/12 + - title: The Jobseeker's Allowance (Amendment) Regulations (Northern Ireland) 1996, regulation 10 + href: https://www.legislation.gov.uk/nisr/1996/356/regulation/10/made diff --git a/policyengine_uk/parameters/gov/dwp/income_support/means_test/capital/residential_threshold.yaml b/policyengine_uk/parameters/gov/dwp/income_support/means_test/capital/residential_threshold.yaml new file mode 100644 index 0000000000..cfe72800bd --- /dev/null +++ b/policyengine_uk/parameters/gov/dwp/income_support/means_test/capital/residential_threshold.yaml @@ -0,0 +1,15 @@ +description: Income Support capital tariff threshold where the claimant lives permanently in prescribed accommodation. +metadata: + label: Income Support prescribed accommodation capital tariff threshold + unit: currency-GBP + period: year +values: + 1996-04-08: + value: 10000 + reference: + - title: The Income Support (General) Regulations 1987, regulation 53(1A)-(1C) + href: https://www.legislation.gov.uk/uksi/1987/1967/regulation/53 + - title: The Income-related Benefits Schemes (Miscellaneous Amendments) Regulations 1996, regulation 12(1)(b) + href: https://www.legislation.gov.uk/uksi/1996/462/regulation/12 + - title: The Income-Related Benefits (Miscellaneous Amendments) Regulations (Northern Ireland) 1996, regulation 11(1)(b) + href: https://www.legislation.gov.uk/nisr/1996/93/regulation/11/made diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/esa_income/esa_income_residential_capital.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/esa_income/esa_income_residential_capital.yaml new file mode 100644 index 0000000000..bedfe43f0f --- /dev/null +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/esa_income/esa_income_residential_capital.yaml @@ -0,0 +1,149 @@ +# Income-related ESA tariff income from capital: ESA Regs 2008 reg 118 (NI: +# SR 2008/280 reg 118). Capital above the lower threshold counts as £1 a week +# for each complete £250 (reg 118(1)-(2)) and £1 for any part of £250 left +# over (reg 118(5)), over 52 weeks. The lower threshold is £6,000 (reg +# 118(1)), or £10,000 where the claimant lives permanently in prescribed +# accommodation (reg 118(2)-(4)). Capital above £16,000 bars the award +# (reg 110), so the figures at £16,001 continue the formula past the limit. +# No award turns on them: the last case shows the award is nil there. + +- name: Income-related ESA ordinary capital threshold and partial steps + period: 2025 + absolute_error_margin: 0.01 + input: + esa_income_assessable_capital: [6000, 6001, 10000, 10001, 10251, 16000, 16001] + legacy_prescribed_accommodation_capital_exception: [false, false, false, false, false, false, false] + output: + # Excess over £6,000: 0, 1, 4,000, 4,001, 4,251, 10,000, 10,001. + # Steps of £250 or part: 0, 1, 16, 17, 18, 40, 41; each £1 x 52. + esa_income_tariff_income: [0, 52, 832, 884, 936, 2080, 2132] + +- name: Income-related ESA prescribed accommodation capital threshold and partial steps + period: 2025 + absolute_error_margin: 0.01 + input: + esa_income_assessable_capital: [6000, 6001, 10000, 10001, 10251, 16000, 16001] + legacy_prescribed_accommodation_capital_exception: [true, true, true, true, true, true, true] + output: + # Excess over £10,000: 0, 0, 0, 1, 251, 6,000, 6,001. + # Steps of £250 or part: 0, 0, 0, 1, 2, 24, 25; each £1 x 52. + esa_income_tariff_income: [0, 0, 0, 52, 104, 1248, 1300] + +- name: Omitted prescribed accommodation status retains the ordinary ESA threshold + period: 2025 + absolute_error_margin: 0.01 + input: + esa_income_assessable_capital: 10001 + output: + legacy_prescribed_accommodation_capital_exception: false + esa_income_tariff_income: 884 + +- name: Housing Benefit's residential exception does not set the ESA threshold + period: 2025 + absolute_error_margin: 0.01 + input: + esa_income_assessable_capital: 10001 + housing_benefit_assessable_capital: 10001 + housing_benefit_residential_capital_exception: true + housing_benefit_pension_age_regulations_apply: false + output: + legacy_prescribed_accommodation_capital_exception: false + # HB Regs 2006 reg 52(3): £10,001 is £1 over £10,000, one step. + housing_benefit_tariff_income: 52 + # ESA Regs 2008 reg 118(1): £4,001 over £6,000, 17 steps. + esa_income_tariff_income: 884 + +- name: The ESA prescribed accommodation exception does not set Housing Benefit's threshold + period: 2025 + absolute_error_margin: 0.01 + input: + esa_income_assessable_capital: 10001 + legacy_prescribed_accommodation_capital_exception: true + housing_benefit_assessable_capital: 10001 + housing_benefit_pension_age_regulations_apply: false + output: + housing_benefit_residential_capital_exception: false + # HB Regs 2006 reg 52(1): £4,001 over £6,000, 17 steps. + housing_benefit_tariff_income: 884 + # ESA Regs 2008 reg 118(2): £1 over £10,000, one step. + esa_income_tariff_income: 52 + +- name: Income-related ESA in prescribed accommodation is paid at the upper capital limit + period: 2025 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 40 + esa_income_reported: 3_000 + benunits: + benunit: + members: claimant + would_claim_uc: false + esa_income_assessable_capital: 16_000 + legacy_prescribed_accommodation_capital_exception: true + households: + household: + members: claimant + output: + esa_income_eligible: true + # Reg 118(2): £6,000 over £10,000 is 24 steps, £24 x 52 = £1,248. + esa_income_tariff_income: 1_248 + # £3,000 reported award less £1,248 tariff income. + esa_income: 1_752 + +- name: Income-related ESA outside prescribed accommodation is paid at the upper capital limit + period: 2025 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 40 + esa_income_reported: 3_000 + benunits: + benunit: + members: claimant + would_claim_uc: false + esa_income_assessable_capital: 16_000 + legacy_prescribed_accommodation_capital_exception: false + households: + household: + members: claimant + output: + esa_income_eligible: true + # Reg 118(1): £10,000 over £6,000 is 40 steps, £40 x 52 = £2,080. + esa_income_tariff_income: 2_080 + # £3,000 reported award less £2,080 tariff income. + esa_income: 920 + +- name: Income-related ESA is not paid above the capital limit with or without the exception + period: 2025 + absolute_error_margin: 0.01 + input: + people: + claimant_1: + age: 40 + esa_income_reported: 3_000 + claimant_2: + age: 40 + esa_income_reported: 3_000 + benunits: + benunit_1: + members: claimant_1 + would_claim_uc: false + esa_income_assessable_capital: 16_001 + legacy_prescribed_accommodation_capital_exception: true + benunit_2: + members: claimant_2 + would_claim_uc: false + esa_income_assessable_capital: 16_001 + legacy_prescribed_accommodation_capital_exception: false + households: + household_1: + members: claimant_1 + household_2: + members: claimant_2 + output: + # Reg 110: capital above £16,000 bars the award. + esa_income_eligible: [false, false] + esa_income: [0, 0] diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/income_support/income_support_residential_capital.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/income_support/income_support_residential_capital.yaml new file mode 100644 index 0000000000..a7d077871e --- /dev/null +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/income_support/income_support_residential_capital.yaml @@ -0,0 +1,141 @@ +# Income Support tariff income from capital: IS Regs 1987 reg 53 (NI: SR +# 1987/459 reg 53). Capital above the lower threshold counts as £1 a week for +# each complete £250 (reg 53(1)-(1A)) and £1 for any part of £250 left over +# (reg 53(2)), over 52 weeks. The lower threshold is £6,000 (reg 53(1)), or +# £10,000 where the claimant lives permanently in prescribed accommodation +# (reg 53(1A)-(1C)). Capital above £16,000 bars the award (reg 45), so the +# figures at £16,001 continue the formula past the limit. No award turns on +# them: the last case shows the award is nil there. + +- name: Income Support ordinary capital threshold and partial steps + period: 2025 + absolute_error_margin: 0.01 + input: + income_support_assessable_capital: [6000, 6001, 10000, 10001, 10251, 16000, 16001] + legacy_prescribed_accommodation_capital_exception: [false, false, false, false, false, false, false] + output: + # Excess over £6,000: 0, 1, 4,000, 4,001, 4,251, 10,000, 10,001. + # Steps of £250 or part: 0, 1, 16, 17, 18, 40, 41; each £1 x 52. + income_support_tariff_income: [0, 52, 832, 884, 936, 2080, 2132] + +- name: Income Support prescribed accommodation capital threshold and partial steps + period: 2025 + absolute_error_margin: 0.01 + input: + income_support_assessable_capital: [6000, 6001, 10000, 10001, 10251, 16000, 16001] + legacy_prescribed_accommodation_capital_exception: [true, true, true, true, true, true, true] + output: + # Excess over £10,000: 0, 0, 0, 1, 251, 6,000, 6,001. + # Steps of £250 or part: 0, 0, 0, 1, 2, 24, 25; each £1 x 52. + income_support_tariff_income: [0, 0, 0, 52, 104, 1248, 1300] + +- name: Omitted prescribed accommodation status retains the ordinary Income Support threshold + period: 2025 + absolute_error_margin: 0.01 + input: + income_support_assessable_capital: 10001 + output: + legacy_prescribed_accommodation_capital_exception: false + income_support_tariff_income: 884 + +- name: Housing Benefit's residential exception does not set the Income Support threshold + period: 2025 + absolute_error_margin: 0.01 + input: + income_support_assessable_capital: 10001 + housing_benefit_residential_capital_exception: true + output: + legacy_prescribed_accommodation_capital_exception: false + # Reg 53(1): £4,001 over £6,000, 17 steps. + income_support_tariff_income: 884 + +- name: Income Support in prescribed accommodation is paid at the upper capital limit + period: 2021 + absolute_error_margin: 0.01 + input: + people: + p1: + age: 26 + income_support_reported: true + c1: + age: 4 + benunits: + b1: + members: [p1, c1] + would_claim_IS: true + income_support_assessable_capital: 16_000 + legacy_prescribed_accommodation_capital_exception: true + households: + household: + members: [p1, c1] + output: + income_support_eligible: true + # Reg 53(1A): £6,000 over £10,000 is 24 steps, £24 x 52 = £1,248. + income_support_tariff_income: 1_248 + income_support_applicable_income: 1_248 + # £74.70 x 52 = £3,884.40 (2021-22 lone parent aged 18 or over), less + # £1,248 tariff income. + income_support: 2_636.40 + +- name: Income Support outside prescribed accommodation is paid at the upper capital limit + period: 2021 + absolute_error_margin: 0.01 + input: + people: + p1: + age: 26 + income_support_reported: true + c1: + age: 4 + benunits: + b1: + members: [p1, c1] + would_claim_IS: true + income_support_assessable_capital: 16_000 + legacy_prescribed_accommodation_capital_exception: false + households: + household: + members: [p1, c1] + output: + income_support_eligible: true + # Reg 53(1): £10,000 over £6,000 is 40 steps, £40 x 52 = £2,080. + income_support_tariff_income: 2_080 + income_support_applicable_income: 2_080 + # £3,884.40 less £2,080 tariff income. + income_support: 1_804.40 + +- name: Income Support is not paid above the capital limit with or without the exception + period: 2021 + absolute_error_margin: 0.01 + input: + people: + p1: + age: 26 + income_support_reported: true + c1: + age: 4 + p2: + age: 26 + income_support_reported: true + c2: + age: 4 + benunits: + b1: + members: [p1, c1] + would_claim_IS: true + income_support_assessable_capital: 16_001 + legacy_prescribed_accommodation_capital_exception: true + b2: + members: [p2, c2] + would_claim_IS: true + income_support_assessable_capital: 16_001 + legacy_prescribed_accommodation_capital_exception: false + households: + household_1: + members: [p1, c1] + household_2: + members: [p2, c2] + output: + # Reg 45: capital above £16,000 bars the award. + income_support_eligible: [false, false] + income_support: [0, 0] diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/jsa_income/jsa_income_residential_capital.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/jsa_income/jsa_income_residential_capital.yaml new file mode 100644 index 0000000000..ca624e0129 --- /dev/null +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/jsa_income/jsa_income_residential_capital.yaml @@ -0,0 +1,126 @@ +# Income-based JSA tariff income from capital: JSA Regs 1996 reg 116 (NI: SR +# 1996/198 reg 116). Capital above the lower threshold counts as £1 a week for +# each complete £250 (reg 116(1)-(1A)) and £1 for any part of £250 left over +# (reg 116(2)), over 52 weeks. The lower threshold is £6,000 (reg 116(1)), or +# £10,000 where the claimant lives permanently in prescribed accommodation +# (reg 116(1A)-(1C)). Capital above £16,000 bars the award (reg 107), so the +# figures at £16,001 continue the formula past the limit. No award turns on +# them: the last case shows the award is nil there. + +- name: Income-based JSA ordinary capital threshold and partial steps + period: 2025 + absolute_error_margin: 0.01 + input: + jsa_income_assessable_capital: [6000, 6001, 10000, 10001, 10251, 16000, 16001] + legacy_prescribed_accommodation_capital_exception: [false, false, false, false, false, false, false] + output: + # Excess over £6,000: 0, 1, 4,000, 4,001, 4,251, 10,000, 10,001. + # Steps of £250 or part: 0, 1, 16, 17, 18, 40, 41; each £1 x 52. + jsa_income_tariff_income: [0, 52, 832, 884, 936, 2080, 2132] + +- name: Income-based JSA prescribed accommodation capital threshold and partial steps + period: 2025 + absolute_error_margin: 0.01 + input: + jsa_income_assessable_capital: [6000, 6001, 10000, 10001, 10251, 16000, 16001] + legacy_prescribed_accommodation_capital_exception: [true, true, true, true, true, true, true] + output: + # Excess over £10,000: 0, 0, 0, 1, 251, 6,000, 6,001. + # Steps of £250 or part: 0, 0, 0, 1, 2, 24, 25; each £1 x 52. + jsa_income_tariff_income: [0, 0, 0, 52, 104, 1248, 1300] + +- name: Omitted prescribed accommodation status retains the ordinary JSA threshold + period: 2025 + absolute_error_margin: 0.01 + input: + jsa_income_assessable_capital: 10001 + output: + legacy_prescribed_accommodation_capital_exception: false + jsa_income_tariff_income: 884 + +- name: Housing Benefit's residential exception does not set the JSA threshold + period: 2025 + absolute_error_margin: 0.01 + input: + jsa_income_assessable_capital: 10001 + housing_benefit_residential_capital_exception: true + output: + legacy_prescribed_accommodation_capital_exception: false + # Reg 116(1): £4,001 over £6,000, 17 steps. + jsa_income_tariff_income: 884 + +- name: Income-based JSA in prescribed accommodation is paid at the upper capital limit + period: 2025 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 30 + jsa_income_reported: 3_000 + benunits: + benunit: + members: claimant + jsa_income_assessable_capital: 16_000 + legacy_prescribed_accommodation_capital_exception: true + households: + household: + members: claimant + output: + jsa_income_eligible: true + # Reg 116(1A): £6,000 over £10,000 is 24 steps, £24 x 52 = £1,248. + jsa_income_tariff_income: 1_248 + # £3,000 reported award less £1,248 tariff income. + jsa_income: 1_752 + +- name: Income-based JSA outside prescribed accommodation is paid at the upper capital limit + period: 2025 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 30 + jsa_income_reported: 3_000 + benunits: + benunit: + members: claimant + jsa_income_assessable_capital: 16_000 + legacy_prescribed_accommodation_capital_exception: false + households: + household: + members: claimant + output: + jsa_income_eligible: true + # Reg 116(1): £10,000 over £6,000 is 40 steps, £40 x 52 = £2,080. + jsa_income_tariff_income: 2_080 + # £3,000 reported award less £2,080 tariff income. + jsa_income: 920 + +- name: Income-based JSA is not paid above the capital limit with or without the exception + period: 2025 + absolute_error_margin: 0.01 + input: + people: + claimant_1: + age: 30 + jsa_income_reported: 3_000 + claimant_2: + age: 30 + jsa_income_reported: 3_000 + benunits: + benunit_1: + members: claimant_1 + jsa_income_assessable_capital: 16_001 + legacy_prescribed_accommodation_capital_exception: true + benunit_2: + members: claimant_2 + jsa_income_assessable_capital: 16_001 + legacy_prescribed_accommodation_capital_exception: false + households: + household_1: + members: claimant_1 + household_2: + members: claimant_2 + output: + # Reg 107: capital above £16,000 bars the award. + jsa_income_eligible: [false, false] + jsa_income: [0, 0] diff --git a/policyengine_uk/variables/gov/dwp/esa_income_tariff_income.py b/policyengine_uk/variables/gov/dwp/esa_income_tariff_income.py index ea97f52279..9876916f6a 100644 --- a/policyengine_uk/variables/gov/dwp/esa_income_tariff_income.py +++ b/policyengine_uk/variables/gov/dwp/esa_income_tariff_income.py @@ -8,11 +8,24 @@ class esa_income_tariff_income(Variable): label = "Income-related ESA tariff income from capital" definition_period = YEAR unit = GBP + reference = ( + "https://www.legislation.gov.uk/uksi/2008/794/regulation/118", + "https://www.legislation.gov.uk/nisr/2008/280/regulation/118", + ) def formula(benunit, period, parameters): ESA = parameters(period).gov.dwp.ESA.income capital = benunit("esa_income_assessable_capital", period) mt = ESA.capital - excess_capital = max_(0, capital - mt.tariff_income.threshold) + # ESA Regs 2008 reg 118(2)-(4) (NI: SR 2008/280 reg 118(2)-(4)): a + # claimant who lives permanently in prescribed accommodation has the + # higher lower threshold. The capital limit (reg 110) bars the award + # above the upper limit, so no award turns on tariff income there. + threshold = where( + benunit("legacy_prescribed_accommodation_capital_exception", period), + mt.residential_threshold, + mt.tariff_income.threshold, + ) + excess_capital = max_(0, capital - threshold) tariff_units = np.ceil(excess_capital / mt.tariff_income.step) return tariff_units * mt.tariff_income.amount * WEEKS_IN_YEAR diff --git a/policyengine_uk/variables/gov/dwp/income_support_tariff_income.py b/policyengine_uk/variables/gov/dwp/income_support_tariff_income.py index 05ce115632..c74c690225 100644 --- a/policyengine_uk/variables/gov/dwp/income_support_tariff_income.py +++ b/policyengine_uk/variables/gov/dwp/income_support_tariff_income.py @@ -8,11 +8,25 @@ class income_support_tariff_income(Variable): label = "Income Support tariff income from capital" definition_period = YEAR unit = GBP + reference = ( + "https://www.legislation.gov.uk/uksi/1987/1967/regulation/53", + "https://www.legislation.gov.uk/nisr/1996/93/regulation/11/made", + ) def formula(benunit, period, parameters): IS = parameters(period).gov.dwp.income_support capital = benunit("income_support_assessable_capital", period) mt = IS.means_test.capital - excess_capital = max_(0, capital - mt.tariff_income.threshold) + # IS Regs 1987 reg 53(1A)-(1C) (NI: the same regulation of SR + # 1987/459): a claimant who lives permanently in prescribed + # accommodation has the higher lower threshold. The capital limit + # (reg 45) bars the award above the upper limit, so no award turns + # on tariff income there. + threshold = where( + benunit("legacy_prescribed_accommodation_capital_exception", period), + mt.residential_threshold, + mt.tariff_income.threshold, + ) + excess_capital = max_(0, capital - threshold) tariff_units = np.ceil(excess_capital / mt.tariff_income.step) return tariff_units * mt.tariff_income.amount * WEEKS_IN_YEAR diff --git a/policyengine_uk/variables/gov/dwp/jsa_income_tariff_income.py b/policyengine_uk/variables/gov/dwp/jsa_income_tariff_income.py index bc818cfba6..76a7a6c507 100644 --- a/policyengine_uk/variables/gov/dwp/jsa_income_tariff_income.py +++ b/policyengine_uk/variables/gov/dwp/jsa_income_tariff_income.py @@ -8,11 +8,25 @@ class jsa_income_tariff_income(Variable): label = "Income-based JSA tariff income from capital" definition_period = YEAR unit = GBP + reference = ( + "https://www.legislation.gov.uk/uksi/1996/207/regulation/116", + "https://www.legislation.gov.uk/nisr/1996/356/regulation/10/made", + ) def formula(benunit, period, parameters): JSA = parameters(period).gov.dwp.JSA.income capital = benunit("jsa_income_assessable_capital", period) mt = JSA.capital - excess_capital = max_(0, capital - mt.tariff_income.threshold) + # JSA Regs 1996 reg 116(1A)-(1C) (NI: the same regulation of SR + # 1996/198): a claimant who lives permanently in prescribed + # accommodation has the higher lower threshold. The capital limit + # (reg 107) bars the award above the upper limit, so no award turns + # on tariff income there. + threshold = where( + benunit("legacy_prescribed_accommodation_capital_exception", period), + mt.residential_threshold, + mt.tariff_income.threshold, + ) + excess_capital = max_(0, capital - threshold) tariff_units = np.ceil(excess_capital / mt.tariff_income.step) return tariff_units * mt.tariff_income.amount * WEEKS_IN_YEAR diff --git a/policyengine_uk/variables/gov/dwp/legacy_prescribed_accommodation_capital_exception.py b/policyengine_uk/variables/gov/dwp/legacy_prescribed_accommodation_capital_exception.py new file mode 100644 index 0000000000..affe229c91 --- /dev/null +++ b/policyengine_uk/variables/gov/dwp/legacy_prescribed_accommodation_capital_exception.py @@ -0,0 +1,41 @@ +from policyengine_uk.model_api import * + + +class legacy_prescribed_accommodation_capital_exception(Variable): + value_type = bool + entity = BenUnit + label = "Legacy benefit prescribed accommodation capital tariff exception" + definition_period = YEAR + default_value = False + documentation = ( + "Pre-assessed qualification for the higher capital tariff threshold of " + "Income Support, income-based JSA and income-related ESA: the claimant " + "lives permanently in prescribed accommodation (GB: IS Regs 1987 reg " + "53(1B)-(1C), JSA Regs 1996 reg 116(1B)-(1C), ESA Regs 2008 reg " + "118(3)-(4); NI: the same regulations of the Northern Ireland rules). " + "In Great Britain that is a care home, an independent hospital, an " + "Abbeyfield Home, or Polish Resettlement Act accommodation where the " + "claimant needs and receives personal care; in Northern Ireland it is a " + "residential care home, a nursing home, an independent hospital or an " + "Abbeyfield Home. That includes the absences each regulation treats as " + "living there permanently. Supply true only after assessing the " + "applicable jurisdiction's rules. Housing Benefit prescribes different " + "circumstances (housing_benefit_residential_capital_exception), and " + "neither input sets the other. Other members who claim in their own " + "right share the benefit unit's tariff income under the existing " + "proxy. No dataset mapping has been verified. Missing input defaults " + "to false and retains the ordinary threshold; that approximation may " + "understate these benefits for qualifying residents. This does not " + "change eligibility, capital ownership/valuation or the upper capital " + "limit." + ) + reference = ( + "https://www.legislation.gov.uk/uksi/1987/1967/regulation/53", + "https://www.legislation.gov.uk/uksi/1996/207/regulation/116", + "https://www.legislation.gov.uk/uksi/2008/794/regulation/118", + "https://www.legislation.gov.uk/nisr/1996/93/regulation/11/made", + "https://www.legislation.gov.uk/nisr/1996/356/regulation/10/made", + "https://www.legislation.gov.uk/nisr/2005/458/schedule/1/made", + "https://www.legislation.gov.uk/nisr/2005/458/schedule/3/made", + "https://www.legislation.gov.uk/nisr/2008/280/regulation/118", + ) From 3184b47fc970852a50da435f9e7dc161e6a7da25 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sat, 31 Oct 2026 22:49:18 -0400 Subject: [PATCH 2/7] Test legacy capital tariff income against the regulations Hypothesis checks, for capital on a penny grid with and without the exception, that each of the IS, income-based JSA, income-related ESA and working-age Housing Benefit tariffs matches the regulations' formula, never falls as capital rises and never exceeds the ordinary tariff, and that programmes with the same tariff figures agree. Co-Authored-By: Claude Opus 5.5 --- .../test_legacy_capital_tariff_properties.py | 216 ++++++++++++++++++ 1 file changed, 216 insertions(+) create mode 100644 policyengine_uk/tests/test_legacy_capital_tariff_properties.py diff --git a/policyengine_uk/tests/test_legacy_capital_tariff_properties.py b/policyengine_uk/tests/test_legacy_capital_tariff_properties.py new file mode 100644 index 0000000000..a326345c61 --- /dev/null +++ b/policyengine_uk/tests/test_legacy_capital_tariff_properties.py @@ -0,0 +1,216 @@ +"""Property tests for tariff income from capital in the legacy means tests. + +Income Support (IS Regs 1987 reg 53), income-based JSA (JSA Regs 1996 reg +116) and income-related ESA (ESA Regs 2008 reg 118), and the same regulations +of the Northern Ireland rules, treat capital above a lower threshold as a +weekly income of £1 for each complete £250 and £1 for any part of £250 left +over. The lower threshold is £6,000, or £10,000 where the claimant lives +permanently in prescribed accommodation (IS reg 53(1A)-(1C), JSA reg +116(1A)-(1C), ESA reg 118(2)-(4)). Working-age Housing Benefit (HB Regs 2006 +reg 52) uses the same figures, with its own residential circumstances. + +Invariants, for any capital on a penny grid and either exception status: + +1. Reference: each programme's tariff income is 52 x ceil(max(0, capital - + threshold) / 250) x £1, with the threshold the regulation gives. +2. Monotone: tariff income never falls as capital rises. +3. Bounded: tariff income with the prescribed accommodation exception never + exceeds the ordinary tariff income on the same capital. +4. Differential: the four programmes whose tariff parameters match give the + same tariff income at every capital level (Housing Benefit with its own + exception set the same way). +""" + +from math import ceil +from pathlib import Path + +import numpy as np +import pytest +import yaml +from hypothesis import HealthCheck, given, settings +from hypothesis import strategies as st + +from policyengine_uk import Simulation + +WEEKS_IN_YEAR = 52 +PROPERTY_SETTINGS = settings( + max_examples=25, + deadline=None, + derandomize=True, + suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large], +) +PARAMETERS = Path(__file__).resolve().parents[1] / "parameters/gov/dwp" + +# Each programme: its tariff income variable, the capital it reads, the +# exception input that selects the higher threshold, and its parameters. +PROGRAMMES = { + "esa_income": ( + "esa_income_tariff_income", + "esa_income_assessable_capital", + "legacy_prescribed_accommodation_capital_exception", + lambda p: p.gov.dwp.ESA.income.capital, + ), + "jsa_income": ( + "jsa_income_tariff_income", + "jsa_income_assessable_capital", + "legacy_prescribed_accommodation_capital_exception", + lambda p: p.gov.dwp.JSA.income.capital, + ), + "income_support": ( + "income_support_tariff_income", + "income_support_assessable_capital", + "legacy_prescribed_accommodation_capital_exception", + lambda p: p.gov.dwp.income_support.means_test.capital, + ), + "housing_benefit": ( + "housing_benefit_tariff_income", + "housing_benefit_assessable_capital", + "housing_benefit_residential_capital_exception", + lambda p: p.gov.dwp.housing_benefit.means_test.capital.working_age, + ), +} +LEGACY = ("esa_income", "jsa_income", "income_support") + +# The statutory figures, read from the regulations rather than the model. +ORDINARY_THRESHOLD = 6_000 +PRESCRIBED_ACCOMMODATION_THRESHOLD = 10_000 +STEP = 250 +WEEKLY_AMOUNT = 1 + +# Capital in pence, so every value is a sum of money; biased towards the +# thresholds and the upper capital limit, where the branches meet. +BOUNDARIES = [0, 6_000, 6_250, 10_000, 10_250, 16_000] +PENCE = st.one_of( + st.integers(0, 4_000_000), + st.builds( + lambda pounds, offset: max(0, pounds * 100 + offset), + st.sampled_from(BOUNDARIES), + st.integers(-300, 300), + ), +) +YEARS = st.sampled_from([2015, 2020, 2024, 2025, 2026, 2029]) + + +def statutory_tariff(capital, exception): + """Annual tariff income the regulations give for one capital amount.""" + threshold = PRESCRIBED_ACCOMMODATION_THRESHOLD if exception else ORDINARY_THRESHOLD + return WEEKS_IN_YEAR * WEEKLY_AMOUNT * ceil(max(0, capital - threshold) / STEP) + + +# One simulation serves every example: each example enters its capital +# amounts as inputs and recalculates tariff income, which reads only inputs. +SLOTS = 64 + + +@pytest.fixture(scope="module") +def simulation(cloned_uk_tax_benefit_system): + people = {f"person_{i}": {"age": {2025: 40}} for i in range(SLOTS)} + benunits = {f"benunit_{i}": {"members": [f"person_{i}"]} for i in range(SLOTS)} + households = {f"household_{i}": {"members": [f"person_{i}"]} for i in range(SLOTS)} + return Simulation( + situation={"people": people, "benunits": benunits, "households": households} + ) + + +def tariffs(simulation, capitals, exceptions, year): + """Each programme's tariff income for one benefit unit per capital.""" + n = len(capitals) + capital = np.zeros(SLOTS) + capital[:n] = capitals + exception = np.zeros(SLOTS, dtype=bool) + exception[:n] = exceptions + for _, capital_variable, exception_variable, _ in PROGRAMMES.values(): + simulation.set_input(capital_variable, year, capital) + simulation.set_input(exception_variable, year, exception) + # Working-age Housing Benefit rules, no Pension Credit passport. + for variable in ( + "housing_benefit_pension_age_regulations_apply", + "in_receipt_of_guarantee_credit", + "in_receipt_of_savings_credit_only", + ): + simulation.set_input(variable, year, np.zeros(SLOTS, dtype=bool)) + result = {} + for name, (variable, _, _, _) in PROGRAMMES.items(): + simulation.delete_arrays(variable) + result[name] = simulation.calculate(variable, year)[:n] + return result + + +def parameter_key(simulation, name, year): + """The tariff figures a programme uses in a year.""" + parameters = simulation.tax_benefit_system.parameters(f"{year}-01-01") + p = PROGRAMMES[name][3](parameters) + return ( + p.tariff_income.threshold, + p.residential_threshold, + p.tariff_income.step, + p.tariff_income.amount, + ) + + +def test_prescribed_accommodation_uses_higher_threshold(simulation): + # £10,001 is £4,001 over £6,000 (17 steps) but £1 over £10,000 (1 step). + result = tariffs(simulation, [10_001, 10_001], [False, True], 2025) + for name in LEGACY: + assert result[name].tolist() == [884, 52], name + + +@pytest.mark.parametrize( + "path, start", + [ + # IS Regs 1987 reg 53(1A), inserted by SI 1996/462 reg 12(1)(b). + ("income_support/means_test/capital/residential_threshold.yaml", "1996-04-08"), + # JSA Regs 1996 reg 116(1A), inserted by SI 1996/1516 reg 12(b). + ("JSA/income/capital/residential_threshold.yaml", "1996-10-07"), + # ESA Regs 2008 reg 118(2), in force with the regulations. + ("ESA/income/capital/residential_threshold.yaml", "2008-10-27"), + ], +) +def test_prescribed_accommodation_threshold_starts_with_the_regulation(path, start): + data = yaml.safe_load((PARAMETERS / path).read_text()) + values = {str(date): entry["value"] for date, entry in data["values"].items()} + assert values == {start: PRESCRIBED_ACCOMMODATION_THRESHOLD} + + +@PROPERTY_SETTINGS +@given( + pence=st.lists(PENCE, min_size=1, max_size=8), + increments=st.lists(st.integers(0, 1_500_000), min_size=8, max_size=8), + year=YEARS, +) +def test_tariff_income_properties(simulation, pence, increments, year): + lower = [p / 100 for p in pence] + higher = [(p + d) / 100 for p, d in zip(pence, increments)] + capitals = lower + lower + higher + higher + exceptions = [False] * len(lower) + [True] * len(lower) + exceptions = exceptions + exceptions + result = tariffs(simulation, capitals, exceptions, year) + n = len(lower) + # The model stores capital as float32; the reference reads the same value. + stored = np.asarray(capitals, dtype=np.float32).astype(float) + expected = [statutory_tariff(c, e) for c, e in zip(stored, exceptions)] + + for name in PROGRAMMES: + tariff = result[name] + # 1. Reference: the regulation's formula. + np.testing.assert_allclose(tariff, expected, atol=0.01, err_msg=name) + ordinary_low, excepted_low = tariff[:n], tariff[n : 2 * n] + ordinary_high, excepted_high = tariff[2 * n : 3 * n], tariff[3 * n :] + # 2. Monotone: more capital never lowers tariff income. + assert np.all(ordinary_high >= ordinary_low), name + assert np.all(excepted_high >= excepted_low), name + # 3. Bounded: the exception never raises tariff income. + assert np.all(excepted_low <= ordinary_low), name + assert np.all(excepted_high <= ordinary_high), name + + # 4. Differential: programmes with the same figures agree everywhere. + groups = {} + for name in PROGRAMMES: + groups.setdefault(parameter_key(simulation, name, year), []).append(name) + shared = [names for names in groups.values() if len(names) > 1] + assert shared, f"no two programmes share tariff parameters in {year}" + for names in shared: + for name in names[1:]: + np.testing.assert_array_equal( + result[name], result[names[0]], err_msg=f"{name} vs {names[0]}" + ) From 7e90c10dcb35299a486aaf9853b905ea13555781 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sun, 1 Nov 2026 00:43:26 -0400 Subject: [PATCH 3/7] Name the thresholds in the tariff income comments Co-Authored-By: Claude Opus 5.5 --- .../variables/gov/dwp/esa_income_tariff_income.py | 9 +++++---- .../variables/gov/dwp/income_support_tariff_income.py | 8 ++++---- .../variables/gov/dwp/jsa_income_tariff_income.py | 8 ++++---- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/policyengine_uk/variables/gov/dwp/esa_income_tariff_income.py b/policyengine_uk/variables/gov/dwp/esa_income_tariff_income.py index 9876916f6a..a92d4dda4e 100644 --- a/policyengine_uk/variables/gov/dwp/esa_income_tariff_income.py +++ b/policyengine_uk/variables/gov/dwp/esa_income_tariff_income.py @@ -17,10 +17,11 @@ def formula(benunit, period, parameters): ESA = parameters(period).gov.dwp.ESA.income capital = benunit("esa_income_assessable_capital", period) mt = ESA.capital - # ESA Regs 2008 reg 118(2)-(4) (NI: SR 2008/280 reg 118(2)-(4)): a - # claimant who lives permanently in prescribed accommodation has the - # higher lower threshold. The capital limit (reg 110) bars the award - # above the upper limit, so no award turns on tariff income there. + # ESA Regs 2008 reg 118(2)-(4) (NI: SR 2008/280 reg 118(2)-(4)): for a + # claimant who lives permanently in prescribed accommodation the lower + # threshold is £10,000, not £6,000. The capital limit (reg 110) bars + # the award above the upper limit, so no award turns on tariff income + # there. threshold = where( benunit("legacy_prescribed_accommodation_capital_exception", period), mt.residential_threshold, diff --git a/policyengine_uk/variables/gov/dwp/income_support_tariff_income.py b/policyengine_uk/variables/gov/dwp/income_support_tariff_income.py index c74c690225..ee0ff9e5f6 100644 --- a/policyengine_uk/variables/gov/dwp/income_support_tariff_income.py +++ b/policyengine_uk/variables/gov/dwp/income_support_tariff_income.py @@ -18,10 +18,10 @@ def formula(benunit, period, parameters): capital = benunit("income_support_assessable_capital", period) mt = IS.means_test.capital # IS Regs 1987 reg 53(1A)-(1C) (NI: the same regulation of SR - # 1987/459): a claimant who lives permanently in prescribed - # accommodation has the higher lower threshold. The capital limit - # (reg 45) bars the award above the upper limit, so no award turns - # on tariff income there. + # 1987/459): for a claimant who lives permanently in prescribed + # accommodation the lower threshold is £10,000, not £6,000. The + # capital limit (reg 45) bars the award above the upper limit, so no + # award turns on tariff income there. threshold = where( benunit("legacy_prescribed_accommodation_capital_exception", period), mt.residential_threshold, diff --git a/policyengine_uk/variables/gov/dwp/jsa_income_tariff_income.py b/policyengine_uk/variables/gov/dwp/jsa_income_tariff_income.py index 76a7a6c507..35970ee224 100644 --- a/policyengine_uk/variables/gov/dwp/jsa_income_tariff_income.py +++ b/policyengine_uk/variables/gov/dwp/jsa_income_tariff_income.py @@ -18,10 +18,10 @@ def formula(benunit, period, parameters): capital = benunit("jsa_income_assessable_capital", period) mt = JSA.capital # JSA Regs 1996 reg 116(1A)-(1C) (NI: the same regulation of SR - # 1996/198): a claimant who lives permanently in prescribed - # accommodation has the higher lower threshold. The capital limit - # (reg 107) bars the award above the upper limit, so no award turns - # on tariff income there. + # 1996/198): for a claimant who lives permanently in prescribed + # accommodation the lower threshold is £10,000, not £6,000. The + # capital limit (reg 107) bars the award above the upper limit, so no + # award turns on tariff income there. threshold = where( benunit("legacy_prescribed_accommodation_capital_exception", period), mt.residential_threshold, From 788c7163312ce95c210bbe0740f153ef44a5136c Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sun, 1 Nov 2026 01:42:48 -0400 Subject: [PATCH 4/7] Write the input documentation and changelog in active voice Co-Authored-By: Claude Opus 5.5 --- changelog.d/1758.fixed.md | 2 +- .../dwp/legacy_prescribed_accommodation_capital_exception.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/changelog.d/1758.fixed.md b/changelog.d/1758.fixed.md index 9b1f51c48a..8d99df1463 100644 --- a/changelog.d/1758.fixed.md +++ b/changelog.d/1758.fixed.md @@ -1 +1 @@ -Income Support, income-based JSA and income-related ESA now apply the £10,000 lower capital threshold for tariff income where the claimant lives permanently in prescribed accommodation, read from the new legacy_prescribed_accommodation_capital_exception input, which defaults to false. +Income Support, income-based JSA and income-related ESA now apply the £10,000 lower capital threshold for tariff income where the claimant lives permanently in prescribed accommodation, which the new legacy_prescribed_accommodation_capital_exception input marks; it defaults to false. diff --git a/policyengine_uk/variables/gov/dwp/legacy_prescribed_accommodation_capital_exception.py b/policyengine_uk/variables/gov/dwp/legacy_prescribed_accommodation_capital_exception.py index affe229c91..72e1132282 100644 --- a/policyengine_uk/variables/gov/dwp/legacy_prescribed_accommodation_capital_exception.py +++ b/policyengine_uk/variables/gov/dwp/legacy_prescribed_accommodation_capital_exception.py @@ -23,8 +23,8 @@ class legacy_prescribed_accommodation_capital_exception(Variable): "circumstances (housing_benefit_residential_capital_exception), and " "neither input sets the other. Other members who claim in their own " "right share the benefit unit's tariff income under the existing " - "proxy. No dataset mapping has been verified. Missing input defaults " - "to false and retains the ordinary threshold; that approximation may " + "proxy. No dataset supplies this input. Missing input defaults to " + "false and retains the ordinary threshold; that approximation may " "understate these benefits for qualifying residents. This does not " "change eligibility, capital ownership/valuation or the upper capital " "limit." From 227fa1d1b7feb7202e62e0e26117c22f0543032f Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sun, 1 Nov 2026 05:10:54 -0500 Subject: [PATCH 5/7] Subtract each tariff threshold from capital before choosing a branch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The three legacy tariff formulas built a threshold array with where(), which promoted the float32 capital subtraction to float64. Under a fractional threshold reform, capital equal to the threshold then counted one £250 step. Each branch now subtracts its own scalar threshold, as the ordinary branch did before, and where() picks between the two excesses. Adds regression cases for each programme with the input omitted, false and true under fractional thresholds, a property test that gives each programme its own six thresholds in pence, and documentation that keeps the statutory range up to £16,000 apart from the arithmetic the variables continue above it. Co-Authored-By: Claude Opus 5.5 --- .../esa_income_residential_capital.yaml | 45 ++++++- .../income_support_residential_capital.yaml | 45 ++++++- .../jsa_income_residential_capital.yaml | 45 ++++++- .../test_legacy_capital_tariff_properties.py | 123 +++++++++++++++--- .../gov/dwp/esa_income_tariff_income.py | 31 +++-- .../gov/dwp/income_support_tariff_income.py | 32 +++-- .../gov/dwp/jsa_income_tariff_income.py | 31 +++-- ...scribed_accommodation_capital_exception.py | 52 +++++--- 8 files changed, 328 insertions(+), 76 deletions(-) diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/esa_income/esa_income_residential_capital.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/esa_income/esa_income_residential_capital.yaml index bedfe43f0f..a810fb82d4 100644 --- a/policyengine_uk/tests/policy/baseline/finance/benefit/family/esa_income/esa_income_residential_capital.yaml +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/esa_income/esa_income_residential_capital.yaml @@ -3,9 +3,12 @@ # for each complete £250 (reg 118(1)-(2)) and £1 for any part of £250 left # over (reg 118(5)), over 52 weeks. The lower threshold is £6,000 (reg # 118(1)), or £10,000 where the claimant lives permanently in prescribed -# accommodation (reg 118(2)-(4)). Capital above £16,000 bars the award -# (reg 110), so the figures at £16,001 continue the formula past the limit. -# No award turns on them: the last case shows the award is nil there. +# accommodation (reg 118(2)-(4)). +# The regulation counts capital only up to £16,000, and esa_income pays +# nothing above the £16,000 capital limit (reg 110). The figures at £16,001 +# show the model continuing the arithmetic, which the regulation does not +# prescribe; the award case at £16,001 shows a nil award at the configured +# limit. - name: Income-related ESA ordinary capital threshold and partial steps period: 2025 @@ -144,6 +147,40 @@ household_2: members: claimant_2 output: - # Reg 110: capital above £16,000 bars the award. + # Reg 110: no award where capital exceeds £16,000. esa_income_eligible: [false, false] esa_income: [0, 0] + +- name: Income-related ESA keeps a fractional ordinary threshold when the exception input is omitted + period: 2025 + absolute_error_margin: 0.01 + input: + gov.dwp.ESA.income.capital.tariff_income.threshold: 6_000.02 + esa_income_assessable_capital: [6_000.02, 6_000.03, 6_250.02] + output: + # Reg 118(1) with the threshold reformed to £6,000.02: excess 0, 0.01 + # and 250; steps of £250 or part 0, 1 and 1; each £1 x 52. + esa_income_tariff_income: [0, 52, 52] + +- name: Income-related ESA keeps a fractional ordinary threshold when the exception input is false + period: 2025 + absolute_error_margin: 0.01 + input: + gov.dwp.ESA.income.capital.tariff_income.threshold: 6_000.02 + esa_income_assessable_capital: [6_000.02, 6_000.03, 6_250.02] + legacy_prescribed_accommodation_capital_exception: [false, false, false] + output: + # Reg 118(1): excess over £6,000.02 of 0, 0.01 and 250; 0, 1 and 1 steps. + esa_income_tariff_income: [0, 52, 52] + +- name: Income-related ESA applies a fractional prescribed accommodation threshold exactly + period: 2025 + absolute_error_margin: 0.01 + input: + gov.dwp.ESA.income.capital.residential_threshold: 10_000.07 + esa_income_assessable_capital: [10_000.07, 10_000.08, 10_250.07] + legacy_prescribed_accommodation_capital_exception: [true, true, true] + output: + # Reg 118(2)-(4) with the threshold reformed to £10,000.07: excess 0, + # 0.01 and 250; 0, 1 and 1 steps; each £1 x 52. + esa_income_tariff_income: [0, 52, 52] diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/income_support/income_support_residential_capital.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/income_support/income_support_residential_capital.yaml index a7d077871e..7f9d4c07a6 100644 --- a/policyengine_uk/tests/policy/baseline/finance/benefit/family/income_support/income_support_residential_capital.yaml +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/income_support/income_support_residential_capital.yaml @@ -3,9 +3,12 @@ # each complete £250 (reg 53(1)-(1A)) and £1 for any part of £250 left over # (reg 53(2)), over 52 weeks. The lower threshold is £6,000 (reg 53(1)), or # £10,000 where the claimant lives permanently in prescribed accommodation -# (reg 53(1A)-(1C)). Capital above £16,000 bars the award (reg 45), so the -# figures at £16,001 continue the formula past the limit. No award turns on -# them: the last case shows the award is nil there. +# (reg 53(1A)-(1C)). +# The regulation counts capital only up to £16,000, and income_support pays +# nothing above the £16,000 capital limit (reg 45). The figures at £16,001 +# show the model continuing the arithmetic, which the regulation does not +# prescribe; the award case at £16,001 shows a nil award at the configured +# limit. - name: Income Support ordinary capital threshold and partial steps period: 2025 @@ -136,6 +139,40 @@ household_2: members: [p2, c2] output: - # Reg 45: capital above £16,000 bars the award. + # Reg 45: no award where capital exceeds £16,000. income_support_eligible: [false, false] income_support: [0, 0] + +- name: Income Support keeps a fractional ordinary threshold when the exception input is omitted + period: 2025 + absolute_error_margin: 0.01 + input: + gov.dwp.income_support.means_test.capital.tariff_income.threshold: 6_000.02 + income_support_assessable_capital: [6_000.02, 6_000.03, 6_250.02] + output: + # Reg 53(1) with the threshold reformed to £6,000.02: excess 0, 0.01 + # and 250; steps of £250 or part 0, 1 and 1; each £1 x 52. + income_support_tariff_income: [0, 52, 52] + +- name: Income Support keeps a fractional ordinary threshold when the exception input is false + period: 2025 + absolute_error_margin: 0.01 + input: + gov.dwp.income_support.means_test.capital.tariff_income.threshold: 6_000.02 + income_support_assessable_capital: [6_000.02, 6_000.03, 6_250.02] + legacy_prescribed_accommodation_capital_exception: [false, false, false] + output: + # Reg 53(1): excess over £6,000.02 of 0, 0.01 and 250; 0, 1 and 1 steps. + income_support_tariff_income: [0, 52, 52] + +- name: Income Support applies a fractional prescribed accommodation threshold exactly + period: 2025 + absolute_error_margin: 0.01 + input: + gov.dwp.income_support.means_test.capital.residential_threshold: 10_000.07 + income_support_assessable_capital: [10_000.07, 10_000.08, 10_250.07] + legacy_prescribed_accommodation_capital_exception: [true, true, true] + output: + # Reg 53(1A)-(1C) with the threshold reformed to £10,000.07: excess 0, + # 0.01 and 250; 0, 1 and 1 steps; each £1 x 52. + income_support_tariff_income: [0, 52, 52] diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/jsa_income/jsa_income_residential_capital.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/jsa_income/jsa_income_residential_capital.yaml index ca624e0129..27926f6ba9 100644 --- a/policyengine_uk/tests/policy/baseline/finance/benefit/family/jsa_income/jsa_income_residential_capital.yaml +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/jsa_income/jsa_income_residential_capital.yaml @@ -3,9 +3,12 @@ # each complete £250 (reg 116(1)-(1A)) and £1 for any part of £250 left over # (reg 116(2)), over 52 weeks. The lower threshold is £6,000 (reg 116(1)), or # £10,000 where the claimant lives permanently in prescribed accommodation -# (reg 116(1A)-(1C)). Capital above £16,000 bars the award (reg 107), so the -# figures at £16,001 continue the formula past the limit. No award turns on -# them: the last case shows the award is nil there. +# (reg 116(1A)-(1C)). +# The regulation counts capital only up to £16,000, and jsa_income pays +# nothing above the £16,000 capital limit (reg 107). The figures at £16,001 +# show the model continuing the arithmetic, which the regulation does not +# prescribe; the award case at £16,001 shows a nil award at the configured +# limit. - name: Income-based JSA ordinary capital threshold and partial steps period: 2025 @@ -121,6 +124,40 @@ household_2: members: claimant_2 output: - # Reg 107: capital above £16,000 bars the award. + # Reg 107: no award where capital exceeds £16,000. jsa_income_eligible: [false, false] jsa_income: [0, 0] + +- name: Income-based JSA keeps a fractional ordinary threshold when the exception input is omitted + period: 2025 + absolute_error_margin: 0.01 + input: + gov.dwp.JSA.income.capital.tariff_income.threshold: 6_000.02 + jsa_income_assessable_capital: [6_000.02, 6_000.03, 6_250.02] + output: + # Reg 116(1) with the threshold reformed to £6,000.02: excess 0, 0.01 + # and 250; steps of £250 or part 0, 1 and 1; each £1 x 52. + jsa_income_tariff_income: [0, 52, 52] + +- name: Income-based JSA keeps a fractional ordinary threshold when the exception input is false + period: 2025 + absolute_error_margin: 0.01 + input: + gov.dwp.JSA.income.capital.tariff_income.threshold: 6_000.02 + jsa_income_assessable_capital: [6_000.02, 6_000.03, 6_250.02] + legacy_prescribed_accommodation_capital_exception: [false, false, false] + output: + # Reg 116(1): excess over £6,000.02 of 0, 0.01 and 250; 0, 1 and 1 steps. + jsa_income_tariff_income: [0, 52, 52] + +- name: Income-based JSA applies a fractional prescribed accommodation threshold exactly + period: 2025 + absolute_error_margin: 0.01 + input: + gov.dwp.JSA.income.capital.residential_threshold: 10_000.07 + jsa_income_assessable_capital: [10_000.07, 10_000.08, 10_250.07] + legacy_prescribed_accommodation_capital_exception: [true, true, true] + output: + # Reg 116(1A)-(1C) with the threshold reformed to £10,000.07: excess 0, + # 0.01 and 250; 0, 1 and 1 steps; each £1 x 52. + jsa_income_tariff_income: [0, 52, 52] diff --git a/policyengine_uk/tests/test_legacy_capital_tariff_properties.py b/policyengine_uk/tests/test_legacy_capital_tariff_properties.py index a326345c61..9f8618df65 100644 --- a/policyengine_uk/tests/test_legacy_capital_tariff_properties.py +++ b/policyengine_uk/tests/test_legacy_capital_tariff_properties.py @@ -4,19 +4,30 @@ 116) and income-related ESA (ESA Regs 2008 reg 118), and the same regulations of the Northern Ireland rules, treat capital above a lower threshold as a weekly income of £1 for each complete £250 and £1 for any part of £250 left -over. The lower threshold is £6,000, or £10,000 where the claimant lives -permanently in prescribed accommodation (IS reg 53(1A)-(1C), JSA reg -116(1A)-(1C), ESA reg 118(2)-(4)). Working-age Housing Benefit (HB Regs 2006 -reg 52) uses the same figures, with its own residential circumstances. +over, counting capital up to £16,000. The lower threshold is £6,000, or +£10,000 where the claimant lives permanently in prescribed accommodation (IS +reg 53(1A)-(1C), JSA reg 116(1A)-(1C), ESA reg 118(2)-(4)). Working-age +Housing Benefit (HB Regs 2006 reg 52) uses the same figures, with its own +residential circumstances. -Invariants, for any capital on a penny grid and either exception status: +Statutory invariants, for any capital on a penny grid up to £16,000 and +either exception status: 1. Reference: each programme's tariff income is 52 x ceil(max(0, capital - threshold) / 250) x £1, with the threshold the regulation gives. -2. Monotone: tariff income never falls as capital rises. -3. Bounded: tariff income with the prescribed accommodation exception never +2. Own parameters: each programme reads its own ordinary and prescribed + accommodation thresholds. With all six set to different amounts in pence, + each programme's tariff income follows its own two. + +Model invariants, for any capital and either exception status: + +3. Continuation: above £16,000 the regulations prescribe no tariff income. + The variables continue the same arithmetic there, and the award formulas + pay nothing above the configured £16,000 capital limit. +4. Monotone: tariff income never falls as capital rises. +5. Bounded: tariff income with the prescribed accommodation exception never exceeds the ordinary tariff income on the same capital. -4. Differential: the four programmes whose tariff parameters match give the +6. Differential: the four programmes whose tariff parameters match give the same tariff income at every capital level (Housing Benefit with its own exception set the same way). """ @@ -27,7 +38,7 @@ import numpy as np import pytest import yaml -from hypothesis import HealthCheck, given, settings +from hypothesis import HealthCheck, assume, given, settings from hypothesis import strategies as st from policyengine_uk import Simulation @@ -74,6 +85,7 @@ # The statutory figures, read from the regulations rather than the model. ORDINARY_THRESHOLD = 6_000 PRESCRIBED_ACCOMMODATION_THRESHOLD = 10_000 +UPPER_LIMIT = 16_000 STEP = 250 WEEKLY_AMOUNT = 1 @@ -91,8 +103,12 @@ YEARS = st.sampled_from([2015, 2020, 2024, 2025, 2026, 2029]) -def statutory_tariff(capital, exception): - """Annual tariff income the regulations give for one capital amount.""" +def tariff_arithmetic(capital, exception): + """52 x £1 for each complete or part £250 of capital above the threshold. + + Up to UPPER_LIMIT this is the annual tariff income the regulations give. + Above it the regulations give none, and this is the model's continuation. + """ threshold = PRESCRIBED_ACCOMMODATION_THRESHOLD if exception else ORDINARY_THRESHOLD return WEEKS_IN_YEAR * WEEKLY_AMOUNT * ceil(max(0, capital - threshold) / STEP) @@ -188,22 +204,29 @@ def test_tariff_income_properties(simulation, pence, increments, year): n = len(lower) # The model stores capital as float32; the reference reads the same value. stored = np.asarray(capitals, dtype=np.float32).astype(float) - expected = [statutory_tariff(c, e) for c, e in zip(stored, exceptions)] + expected = np.array([tariff_arithmetic(c, e) for c, e in zip(stored, exceptions)]) + statutory = stored <= UPPER_LIMIT for name in PROGRAMMES: tariff = result[name] - # 1. Reference: the regulation's formula. - np.testing.assert_allclose(tariff, expected, atol=0.01, err_msg=name) + # 1. Reference: the regulation's formula, up to £16,000. + np.testing.assert_allclose( + tariff[statutory], expected[statutory], atol=0.01, err_msg=name + ) + # 3. Continuation: the same arithmetic above £16,000. + np.testing.assert_allclose( + tariff[~statutory], expected[~statutory], atol=0.01, err_msg=name + ) ordinary_low, excepted_low = tariff[:n], tariff[n : 2 * n] ordinary_high, excepted_high = tariff[2 * n : 3 * n], tariff[3 * n :] - # 2. Monotone: more capital never lowers tariff income. + # 4. Monotone: more capital never lowers tariff income. assert np.all(ordinary_high >= ordinary_low), name assert np.all(excepted_high >= excepted_low), name - # 3. Bounded: the exception never raises tariff income. + # 5. Bounded: the exception never raises tariff income. assert np.all(excepted_low <= ordinary_low), name assert np.all(excepted_high <= ordinary_high), name - # 4. Differential: programmes with the same figures agree everywhere. + # 6. Differential: programmes with the same figures agree everywhere. groups = {} for name in PROGRAMMES: groups.setdefault(parameter_key(simulation, name, year), []).append(name) @@ -214,3 +237,69 @@ def test_tariff_income_properties(simulation, pence, increments, year): np.testing.assert_array_equal( result[name], result[names[0]], err_msg=f"{name} vs {names[0]}" ) + + +# Each legacy programme's own capital parameters. +CAPITAL_PARAMETERS = { + "esa_income": "gov.dwp.ESA.income.capital", + "jsa_income": "gov.dwp.JSA.income.capital", + "income_support": "gov.dwp.income_support.means_test.capital", +} +THRESHOLD_PENCE = st.integers(100_000, 1_500_000) + + +def set_parameters(simulation, year, values): + """Set parameters for one year in place; return the values they replace.""" + parameters = simulation.tax_benefit_system.parameters + replaced = {} + for path, value in values.items(): + parameter = parameters.get_child(path) + replaced[path] = parameter(f"{year}-01-01") + parameter.update(period=str(year), value=value) + simulation.tax_benefit_system.reset_parameter_caches() + return replaced + + +@PROPERTY_SETTINGS +@given( + ordinary=st.lists(THRESHOLD_PENCE, min_size=3, max_size=3), + residential=st.lists(THRESHOLD_PENCE, min_size=3, max_size=3), + pence=st.lists(st.integers(0, UPPER_LIMIT * 100), max_size=10), + year=YEARS, +) +def test_each_programme_reads_its_own_thresholds( + simulation, ordinary, residential, pence, year +): + # 2. Own parameters. Six different thresholds, each with capital a penny + # either side and at equality, so a programme that read another's + # threshold would return a different tariff next to its own. + assume(len(set(ordinary + residential)) == 6) + values = {} + for name, own_ordinary, own_residential in zip(LEGACY, ordinary, residential): + path = CAPITAL_PARAMETERS[name] + values[f"{path}.tariff_income.threshold"] = own_ordinary / 100 + values[f"{path}.residential_threshold"] = own_residential / 100 + points = {t + d for t in ordinary + residential for d in (-1, 0, 1)} + points = sorted(points | set(pence)) + capitals = points + points + exceptions = [False] * len(points) + [True] * len(points) + replaced = set_parameters(simulation, year, values) + try: + result = tariffs(simulation, [c / 100 for c in capitals], exceptions, year) + finally: + set_parameters(simulation, year, replaced) + + for name, own_ordinary, own_residential in zip(LEGACY, ordinary, residential): + for capital, exception, tariff in zip(capitals, exceptions, result[name]): + excess = capital - (own_residential if exception else own_ordinary) + # Most amounts in pence have no exact float32 value. Where capital + # is a whole number of £250 steps above the threshold, the stored + # figures can fall either side of the step, so the test skips it. + # Anywhere else the excess is at least a penny from a step, far + # more than that rounding. + if excess > 0 and excess % (STEP * 100) == 0: + continue + expected = ( + WEEKS_IN_YEAR * WEEKLY_AMOUNT * ceil(max(0, excess) / (STEP * 100)) + ) + assert tariff == expected, (name, capital, exception, tariff, expected) diff --git a/policyengine_uk/variables/gov/dwp/esa_income_tariff_income.py b/policyengine_uk/variables/gov/dwp/esa_income_tariff_income.py index a92d4dda4e..b4b503a5c6 100644 --- a/policyengine_uk/variables/gov/dwp/esa_income_tariff_income.py +++ b/policyengine_uk/variables/gov/dwp/esa_income_tariff_income.py @@ -6,6 +6,19 @@ class esa_income_tariff_income(Variable): value_type = float entity = BenUnit label = "Income-related ESA tariff income from capital" + documentation = ( + "Weekly tariff income from capital under ESA Regs 2008 reg 118 (NI: SR " + "2008/280 reg 118), over 52 weeks: £1 for each complete £250 of " + "capital above the lower threshold and £1 for any part left over. The " + "lower threshold is £6,000, or £10,000 where " + "legacy_prescribed_accommodation_capital_exception is true. The " + "regulation counts capital only up to £16,000. Above that amount this " + "variable continues the same arithmetic, which the regulation does " + "not prescribe. esa_income_eligible and the own-right claims in " + "esa_income pay nothing while capital exceeds the configured capital " + "limit (reg 110, £16,000), so at that limit no award depends on the " + "continuation. A reform that raises the limit lets awards read it." + ) definition_period = YEAR unit = GBP reference = ( @@ -17,16 +30,16 @@ def formula(benunit, period, parameters): ESA = parameters(period).gov.dwp.ESA.income capital = benunit("esa_income_assessable_capital", period) mt = ESA.capital - # ESA Regs 2008 reg 118(2)-(4) (NI: SR 2008/280 reg 118(2)-(4)): for a - # claimant who lives permanently in prescribed accommodation the lower - # threshold is £10,000, not £6,000. The capital limit (reg 110) bars - # the award above the upper limit, so no award turns on tariff income - # there. - threshold = where( + # Reg 118(1)-(4): the lower threshold is £6,000, or £10,000 for a + # claimant who lives permanently in prescribed accommodation. Each + # branch subtracts its own scalar threshold, so both keep the stored + # capital's float32 precision. + ordinary_excess = max_(0, capital - mt.tariff_income.threshold) + residential_excess = max_(0, capital - mt.residential_threshold) + excess_capital = where( benunit("legacy_prescribed_accommodation_capital_exception", period), - mt.residential_threshold, - mt.tariff_income.threshold, + residential_excess, + ordinary_excess, ) - excess_capital = max_(0, capital - threshold) tariff_units = np.ceil(excess_capital / mt.tariff_income.step) return tariff_units * mt.tariff_income.amount * WEEKS_IN_YEAR diff --git a/policyengine_uk/variables/gov/dwp/income_support_tariff_income.py b/policyengine_uk/variables/gov/dwp/income_support_tariff_income.py index ee0ff9e5f6..aba5881cee 100644 --- a/policyengine_uk/variables/gov/dwp/income_support_tariff_income.py +++ b/policyengine_uk/variables/gov/dwp/income_support_tariff_income.py @@ -6,6 +6,20 @@ class income_support_tariff_income(Variable): value_type = float entity = BenUnit label = "Income Support tariff income from capital" + documentation = ( + "Weekly tariff income from capital under IS Regs 1987 reg 53 (NI: SR " + "1987/459 reg 53), over 52 weeks: £1 for each complete £250 of " + "capital above the lower threshold and £1 for any part left over. The " + "lower threshold is £6,000, or £10,000 where " + "legacy_prescribed_accommodation_capital_exception is true. The " + "regulation counts capital only up to £16,000. Above that amount this " + "variable continues the same arithmetic, which the regulation does " + "not prescribe. income_support_applicable_income adds it in full, and " + "income_support_entitlement pays nothing while income_support_eligible " + "finds capital above the configured capital limit (reg 45, £16,000), " + "so at that limit no award depends on the continuation. A reform that " + "raises the limit lets awards read it." + ) definition_period = YEAR unit = GBP reference = ( @@ -17,16 +31,16 @@ def formula(benunit, period, parameters): IS = parameters(period).gov.dwp.income_support capital = benunit("income_support_assessable_capital", period) mt = IS.means_test.capital - # IS Regs 1987 reg 53(1A)-(1C) (NI: the same regulation of SR - # 1987/459): for a claimant who lives permanently in prescribed - # accommodation the lower threshold is £10,000, not £6,000. The - # capital limit (reg 45) bars the award above the upper limit, so no - # award turns on tariff income there. - threshold = where( + # Reg 53(1)-(1C): the lower threshold is £6,000, or £10,000 for a + # claimant who lives permanently in prescribed accommodation. Each + # branch subtracts its own scalar threshold, so both keep the stored + # capital's float32 precision. + ordinary_excess = max_(0, capital - mt.tariff_income.threshold) + residential_excess = max_(0, capital - mt.residential_threshold) + excess_capital = where( benunit("legacy_prescribed_accommodation_capital_exception", period), - mt.residential_threshold, - mt.tariff_income.threshold, + residential_excess, + ordinary_excess, ) - excess_capital = max_(0, capital - threshold) tariff_units = np.ceil(excess_capital / mt.tariff_income.step) return tariff_units * mt.tariff_income.amount * WEEKS_IN_YEAR diff --git a/policyengine_uk/variables/gov/dwp/jsa_income_tariff_income.py b/policyengine_uk/variables/gov/dwp/jsa_income_tariff_income.py index 35970ee224..943e09e012 100644 --- a/policyengine_uk/variables/gov/dwp/jsa_income_tariff_income.py +++ b/policyengine_uk/variables/gov/dwp/jsa_income_tariff_income.py @@ -6,6 +6,19 @@ class jsa_income_tariff_income(Variable): value_type = float entity = BenUnit label = "Income-based JSA tariff income from capital" + documentation = ( + "Weekly tariff income from capital under JSA Regs 1996 reg 116 (NI: SR " + "1996/198 reg 116), over 52 weeks: £1 for each complete £250 of " + "capital above the lower threshold and £1 for any part left over. The " + "lower threshold is £6,000, or £10,000 where " + "legacy_prescribed_accommodation_capital_exception is true. The " + "regulation counts capital only up to £16,000. Above that amount this " + "variable continues the same arithmetic, which the regulation does " + "not prescribe. jsa_income_eligible and the own-right claims in " + "jsa_income pay nothing while capital exceeds the configured capital " + "limit (reg 107, £16,000), so at that limit no award depends on the " + "continuation. A reform that raises the limit lets awards read it." + ) definition_period = YEAR unit = GBP reference = ( @@ -17,16 +30,16 @@ def formula(benunit, period, parameters): JSA = parameters(period).gov.dwp.JSA.income capital = benunit("jsa_income_assessable_capital", period) mt = JSA.capital - # JSA Regs 1996 reg 116(1A)-(1C) (NI: the same regulation of SR - # 1996/198): for a claimant who lives permanently in prescribed - # accommodation the lower threshold is £10,000, not £6,000. The - # capital limit (reg 107) bars the award above the upper limit, so no - # award turns on tariff income there. - threshold = where( + # Reg 116(1)-(1C): the lower threshold is £6,000, or £10,000 for a + # claimant who lives permanently in prescribed accommodation. Each + # branch subtracts its own scalar threshold, so both keep the stored + # capital's float32 precision. + ordinary_excess = max_(0, capital - mt.tariff_income.threshold) + residential_excess = max_(0, capital - mt.residential_threshold) + excess_capital = where( benunit("legacy_prescribed_accommodation_capital_exception", period), - mt.residential_threshold, - mt.tariff_income.threshold, + residential_excess, + ordinary_excess, ) - excess_capital = max_(0, capital - threshold) tariff_units = np.ceil(excess_capital / mt.tariff_income.step) return tariff_units * mt.tariff_income.amount * WEEKS_IN_YEAR diff --git a/policyengine_uk/variables/gov/dwp/legacy_prescribed_accommodation_capital_exception.py b/policyengine_uk/variables/gov/dwp/legacy_prescribed_accommodation_capital_exception.py index 72e1132282..bcf3847a73 100644 --- a/policyengine_uk/variables/gov/dwp/legacy_prescribed_accommodation_capital_exception.py +++ b/policyengine_uk/variables/gov/dwp/legacy_prescribed_accommodation_capital_exception.py @@ -8,26 +8,38 @@ class legacy_prescribed_accommodation_capital_exception(Variable): definition_period = YEAR default_value = False documentation = ( - "Pre-assessed qualification for the higher capital tariff threshold of " - "Income Support, income-based JSA and income-related ESA: the claimant " - "lives permanently in prescribed accommodation (GB: IS Regs 1987 reg " - "53(1B)-(1C), JSA Regs 1996 reg 116(1B)-(1C), ESA Regs 2008 reg " - "118(3)-(4); NI: the same regulations of the Northern Ireland rules). " - "In Great Britain that is a care home, an independent hospital, an " - "Abbeyfield Home, or Polish Resettlement Act accommodation where the " - "claimant needs and receives personal care; in Northern Ireland it is a " - "residential care home, a nursing home, an independent hospital or an " - "Abbeyfield Home. That includes the absences each regulation treats as " - "living there permanently. Supply true only after assessing the " - "applicable jurisdiction's rules. Housing Benefit prescribes different " - "circumstances (housing_benefit_residential_capital_exception), and " - "neither input sets the other. Other members who claim in their own " - "right share the benefit unit's tariff income under the existing " - "proxy. No dataset supplies this input. Missing input defaults to " - "false and retains the ordinary threshold; that approximation may " - "understate these benefits for qualifying residents. This does not " - "change eligibility, capital ownership/valuation or the upper capital " - "limit." + "True where the claimant lives permanently in prescribed accommodation " + "for the capital tariff of Income Support, income-based JSA and " + "income-related ESA (GB: IS Regs 1987 reg 53(1B)-(1C), JSA Regs 1996 reg " + "116(1B)-(1C), ESA Regs 2008 reg 118(3)-(4); NI: the same regulations of " + "SR 1987/459, SR 1996/198 and SR 2008/280). True sets the lower " + "threshold to £10,000 instead of £6,000. " + "Assess the claimant's own stay: a partner who lives in a home does not " + "make the claimant qualify. " + "In Great Britain the places are a care home, an independent hospital, " + "an Abbeyfield Home, and Polish Resettlement Act 1947 accommodation " + "where the claimant requires personal care by reason of old age, " + "disablement, past or present dependence on alcohol or drugs, past or " + "present mental disorder or a terminal illness, and receives that care " + "there. In Northern Ireland they are a residential care home, a nursing " + "home, an independent hospital and an Abbeyfield Home. " + "A claimant who is away still counts as living there permanently: away " + "from a home or hospital for up to 13 weeks (52 weeks for a claimant " + "over pensionable age under IS and ESA), or away from Polish " + "Resettlement Act accommodation while the claimant intends, with the " + "manager's agreement, to return in due course, which has no fixed " + "limit. " + "Assess the rules in force for the year and jurisdiction modelled. " + "Housing Benefit prescribes different circumstances " + "(housing_benefit_residential_capital_exception), and neither input sets " + "the other. " + "Other members who claim in their own right use the benefit unit's " + "tariff income, so this input sets their threshold too; the model does " + "not assess their own stay or divide capital between claims. " + "No dataset supplies this input. It defaults to false, which keeps the " + "£6,000 threshold and may understate these benefits for qualifying " + "residents. It does not change eligibility, how capital is valued or " + "the upper capital limit." ) reference = ( "https://www.legislation.gov.uk/uksi/1987/1967/regulation/53", From 583d8d1ab77dd0d29c0425a6c38f69087f676a95 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sun, 1 Nov 2026 06:26:58 -0500 Subject: [PATCH 6/7] Name the actors in the tariff documentation and changelog Rewrites the input's documentation to say how a caller assesses it: the claimant's own stay, the places in each jurisdiction, the 13-week and 52-week absences, the Polish Resettlement Act absence with no fixed limit, and the shared tariff for own-right claims. Puts the programme second in the three parameter descriptions and splits the changelog sentence. Co-Authored-By: Claude Opus 5.5 --- changelog.d/1758.fixed.md | 2 +- .../income/capital/residential_threshold.yaml | 2 +- .../income/capital/residential_threshold.yaml | 2 +- .../capital/residential_threshold.yaml | 2 +- .../esa_income_residential_capital.yaml | 13 +++--- .../income_support_residential_capital.yaml | 11 +++-- .../jsa_income_residential_capital.yaml | 11 +++-- .../test_legacy_capital_tariff_properties.py | 21 +++++---- .../gov/dwp/esa_income_tariff_income.py | 25 ++++++----- .../gov/dwp/income_support_tariff_income.py | 24 +++++----- .../gov/dwp/jsa_income_tariff_income.py | 25 ++++++----- ...scribed_accommodation_capital_exception.py | 45 +++++++++---------- 12 files changed, 91 insertions(+), 92 deletions(-) diff --git a/changelog.d/1758.fixed.md b/changelog.d/1758.fixed.md index 8d99df1463..d0b886a5fc 100644 --- a/changelog.d/1758.fixed.md +++ b/changelog.d/1758.fixed.md @@ -1 +1 @@ -Income Support, income-based JSA and income-related ESA now apply the £10,000 lower capital threshold for tariff income where the claimant lives permanently in prescribed accommodation, which the new legacy_prescribed_accommodation_capital_exception input marks; it defaults to false. +Apply the £10,000 capital tariff threshold to Income Support, income-based JSA and income-related ESA claimants who live permanently in prescribed accommodation. Callers mark them with the new legacy_prescribed_accommodation_capital_exception input, which defaults to false. diff --git a/policyengine_uk/parameters/gov/dwp/ESA/income/capital/residential_threshold.yaml b/policyengine_uk/parameters/gov/dwp/ESA/income/capital/residential_threshold.yaml index c0e6c3b43f..0fa7d05819 100644 --- a/policyengine_uk/parameters/gov/dwp/ESA/income/capital/residential_threshold.yaml +++ b/policyengine_uk/parameters/gov/dwp/ESA/income/capital/residential_threshold.yaml @@ -1,4 +1,4 @@ -description: Income-related Employment and Support Allowance capital tariff threshold where the claimant lives permanently in prescribed accommodation. +description: Capital tariff threshold for income-related Employment and Support Allowance claimants who live permanently in prescribed accommodation. metadata: label: Income-related ESA prescribed accommodation capital tariff threshold unit: currency-GBP diff --git a/policyengine_uk/parameters/gov/dwp/JSA/income/capital/residential_threshold.yaml b/policyengine_uk/parameters/gov/dwp/JSA/income/capital/residential_threshold.yaml index 34fe82b273..8b56ba2b37 100644 --- a/policyengine_uk/parameters/gov/dwp/JSA/income/capital/residential_threshold.yaml +++ b/policyengine_uk/parameters/gov/dwp/JSA/income/capital/residential_threshold.yaml @@ -1,4 +1,4 @@ -description: Income-based Jobseeker's Allowance capital tariff threshold where the claimant lives permanently in prescribed accommodation. +description: Capital tariff threshold for income-based Jobseeker's Allowance claimants who live permanently in prescribed accommodation. metadata: label: Income-based JSA prescribed accommodation capital tariff threshold unit: currency-GBP diff --git a/policyengine_uk/parameters/gov/dwp/income_support/means_test/capital/residential_threshold.yaml b/policyengine_uk/parameters/gov/dwp/income_support/means_test/capital/residential_threshold.yaml index cfe72800bd..4316fb672d 100644 --- a/policyengine_uk/parameters/gov/dwp/income_support/means_test/capital/residential_threshold.yaml +++ b/policyengine_uk/parameters/gov/dwp/income_support/means_test/capital/residential_threshold.yaml @@ -1,4 +1,4 @@ -description: Income Support capital tariff threshold where the claimant lives permanently in prescribed accommodation. +description: Capital tariff threshold for Income Support claimants who live permanently in prescribed accommodation. metadata: label: Income Support prescribed accommodation capital tariff threshold unit: currency-GBP diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/esa_income/esa_income_residential_capital.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/esa_income/esa_income_residential_capital.yaml index a810fb82d4..7d91a2c476 100644 --- a/policyengine_uk/tests/policy/baseline/finance/benefit/family/esa_income/esa_income_residential_capital.yaml +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/esa_income/esa_income_residential_capital.yaml @@ -1,11 +1,10 @@ # Income-related ESA tariff income from capital: ESA Regs 2008 reg 118 (NI: # SR 2008/280 reg 118). Capital above the lower threshold counts as £1 a week # for each complete £250 (reg 118(1)-(2)) and £1 for any part of £250 left -# over (reg 118(5)), over 52 weeks. The lower threshold is £6,000 (reg -# 118(1)), or £10,000 where the claimant lives permanently in prescribed -# accommodation (reg 118(2)-(4)). -# The regulation counts capital only up to £16,000, and esa_income pays -# nothing above the £16,000 capital limit (reg 110). The figures at £16,001 +# over (reg 118(5)), over 52 weeks. The regulation sets the lower threshold +# at £6,000 (reg 118(1)), or £10,000 where the claimant lives permanently in +# prescribed accommodation (reg 118(2)-(4)). It stops counting capital at +# £16,000, and esa_income pays nothing above the £16,000 capital limit (reg 110). The figures at £16,001 # show the model continuing the arithmetic, which the regulation does not # prescribe; the award case at £16,001 shows a nil award at the configured # limit. @@ -51,7 +50,7 @@ housing_benefit_pension_age_regulations_apply: false output: legacy_prescribed_accommodation_capital_exception: false - # HB Regs 2006 reg 52(3): £10,001 is £1 over £10,000, one step. + # HB Regs 2006 reg 52(3): £10,001 exceeds £10,000 by £1, one step. housing_benefit_tariff_income: 52 # ESA Regs 2008 reg 118(1): £4,001 over £6,000, 17 steps. esa_income_tariff_income: 884 @@ -173,7 +172,7 @@ # Reg 118(1): excess over £6,000.02 of 0, 0.01 and 250; 0, 1 and 1 steps. esa_income_tariff_income: [0, 52, 52] -- name: Income-related ESA applies a fractional prescribed accommodation threshold exactly +- name: Income-related ESA applies a fractional prescribed accommodation threshold period: 2025 absolute_error_margin: 0.01 input: diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/income_support/income_support_residential_capital.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/income_support/income_support_residential_capital.yaml index 7f9d4c07a6..6b879b233c 100644 --- a/policyengine_uk/tests/policy/baseline/finance/benefit/family/income_support/income_support_residential_capital.yaml +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/income_support/income_support_residential_capital.yaml @@ -1,11 +1,10 @@ # Income Support tariff income from capital: IS Regs 1987 reg 53 (NI: SR # 1987/459 reg 53). Capital above the lower threshold counts as £1 a week for # each complete £250 (reg 53(1)-(1A)) and £1 for any part of £250 left over -# (reg 53(2)), over 52 weeks. The lower threshold is £6,000 (reg 53(1)), or -# £10,000 where the claimant lives permanently in prescribed accommodation -# (reg 53(1A)-(1C)). -# The regulation counts capital only up to £16,000, and income_support pays -# nothing above the £16,000 capital limit (reg 45). The figures at £16,001 +# (reg 53(2)), over 52 weeks. The regulation sets the lower threshold at +# £6,000 (reg 53(1)), or £10,000 where the claimant lives permanently in +# prescribed accommodation (reg 53(1A)-(1C)). It stops counting capital at +# £16,000, and income_support pays nothing above the £16,000 capital limit (reg 45). The figures at £16,001 # show the model continuing the arithmetic, which the regulation does not # prescribe; the award case at £16,001 shows a nil award at the configured # limit. @@ -165,7 +164,7 @@ # Reg 53(1): excess over £6,000.02 of 0, 0.01 and 250; 0, 1 and 1 steps. income_support_tariff_income: [0, 52, 52] -- name: Income Support applies a fractional prescribed accommodation threshold exactly +- name: Income Support applies a fractional prescribed accommodation threshold period: 2025 absolute_error_margin: 0.01 input: diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/jsa_income/jsa_income_residential_capital.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/jsa_income/jsa_income_residential_capital.yaml index 27926f6ba9..1136509f5b 100644 --- a/policyengine_uk/tests/policy/baseline/finance/benefit/family/jsa_income/jsa_income_residential_capital.yaml +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/jsa_income/jsa_income_residential_capital.yaml @@ -1,11 +1,10 @@ # Income-based JSA tariff income from capital: JSA Regs 1996 reg 116 (NI: SR # 1996/198 reg 116). Capital above the lower threshold counts as £1 a week for # each complete £250 (reg 116(1)-(1A)) and £1 for any part of £250 left over -# (reg 116(2)), over 52 weeks. The lower threshold is £6,000 (reg 116(1)), or -# £10,000 where the claimant lives permanently in prescribed accommodation -# (reg 116(1A)-(1C)). -# The regulation counts capital only up to £16,000, and jsa_income pays -# nothing above the £16,000 capital limit (reg 107). The figures at £16,001 +# (reg 116(2)), over 52 weeks. The regulation sets the lower threshold at +# £6,000 (reg 116(1)), or £10,000 where the claimant lives permanently in +# prescribed accommodation (reg 116(1A)-(1C)). It stops counting capital at +# £16,000, and jsa_income pays nothing above the £16,000 capital limit (reg 107). The figures at £16,001 # show the model continuing the arithmetic, which the regulation does not # prescribe; the award case at £16,001 shows a nil award at the configured # limit. @@ -150,7 +149,7 @@ # Reg 116(1): excess over £6,000.02 of 0, 0.01 and 250; 0, 1 and 1 steps. jsa_income_tariff_income: [0, 52, 52] -- name: Income-based JSA applies a fractional prescribed accommodation threshold exactly +- name: Income-based JSA applies a fractional prescribed accommodation threshold period: 2025 absolute_error_margin: 0.01 input: diff --git a/policyengine_uk/tests/test_legacy_capital_tariff_properties.py b/policyengine_uk/tests/test_legacy_capital_tariff_properties.py index 9f8618df65..8d126ba56b 100644 --- a/policyengine_uk/tests/test_legacy_capital_tariff_properties.py +++ b/policyengine_uk/tests/test_legacy_capital_tariff_properties.py @@ -4,9 +4,10 @@ 116) and income-related ESA (ESA Regs 2008 reg 118), and the same regulations of the Northern Ireland rules, treat capital above a lower threshold as a weekly income of £1 for each complete £250 and £1 for any part of £250 left -over, counting capital up to £16,000. The lower threshold is £6,000, or -£10,000 where the claimant lives permanently in prescribed accommodation (IS -reg 53(1A)-(1C), JSA reg 116(1A)-(1C), ESA reg 118(2)-(4)). Working-age +over, counting capital up to £16,000. The regulations set the lower +threshold at £6,000, or £10,000 where the claimant lives permanently in +prescribed accommodation (IS reg 53(1A)-(1C), JSA reg 116(1A)-(1C), ESA reg +118(2)-(4)). Working-age Housing Benefit (HB Regs 2006 reg 52) uses the same figures, with its own residential circumstances. @@ -82,14 +83,15 @@ } LEGACY = ("esa_income", "jsa_income", "income_support") -# The statutory figures, read from the regulations rather than the model. +# The statutory figures, from the regulations; the reference never reads the +# model's parameters. ORDINARY_THRESHOLD = 6_000 PRESCRIBED_ACCOMMODATION_THRESHOLD = 10_000 UPPER_LIMIT = 16_000 STEP = 250 WEEKLY_AMOUNT = 1 -# Capital in pence, so every value is a sum of money; biased towards the +# Capital in pence, so every value names a sum of money, biased towards the # thresholds and the upper capital limit, where the branches meet. BOUNDARIES = [0, 6_000, 6_250, 10_000, 10_250, 16_000] PENCE = st.one_of( @@ -106,8 +108,9 @@ def tariff_arithmetic(capital, exception): """52 x £1 for each complete or part £250 of capital above the threshold. - Up to UPPER_LIMIT this is the annual tariff income the regulations give. - Above it the regulations give none, and this is the model's continuation. + Up to UPPER_LIMIT it returns the annual tariff income the regulations + give. Above it the regulations give none, and it returns the model's + continuation. """ threshold = PRESCRIBED_ACCOMMODATION_THRESHOLD if exception else ORDINARY_THRESHOLD return WEEKS_IN_YEAR * WEEKLY_AMOUNT * ceil(max(0, capital - threshold) / STEP) @@ -165,7 +168,7 @@ def parameter_key(simulation, name, year): def test_prescribed_accommodation_uses_higher_threshold(simulation): - # £10,001 is £4,001 over £6,000 (17 steps) but £1 over £10,000 (1 step). + # £10,001 exceeds £6,000 by £4,001 (17 steps) and £10,000 by £1 (1 step). result = tariffs(simulation, [10_001, 10_001], [False, True], 2025) for name in LEGACY: assert result[name].tolist() == [884, 52], name @@ -293,7 +296,7 @@ def test_each_programme_reads_its_own_thresholds( for capital, exception, tariff in zip(capitals, exceptions, result[name]): excess = capital - (own_residential if exception else own_ordinary) # Most amounts in pence have no exact float32 value. Where capital - # is a whole number of £250 steps above the threshold, the stored + # lies a whole number of £250 steps above the threshold, the stored # figures can fall either side of the step, so the test skips it. # Anywhere else the excess is at least a penny from a step, far # more than that rounding. diff --git a/policyengine_uk/variables/gov/dwp/esa_income_tariff_income.py b/policyengine_uk/variables/gov/dwp/esa_income_tariff_income.py index b4b503a5c6..5284a56c58 100644 --- a/policyengine_uk/variables/gov/dwp/esa_income_tariff_income.py +++ b/policyengine_uk/variables/gov/dwp/esa_income_tariff_income.py @@ -8,16 +8,17 @@ class esa_income_tariff_income(Variable): label = "Income-related ESA tariff income from capital" documentation = ( "Weekly tariff income from capital under ESA Regs 2008 reg 118 (NI: SR " - "2008/280 reg 118), over 52 weeks: £1 for each complete £250 of " - "capital above the lower threshold and £1 for any part left over. The " - "lower threshold is £6,000, or £10,000 where " - "legacy_prescribed_accommodation_capital_exception is true. The " - "regulation counts capital only up to £16,000. Above that amount this " - "variable continues the same arithmetic, which the regulation does " - "not prescribe. esa_income_eligible and the own-right claims in " - "esa_income pay nothing while capital exceeds the configured capital " - "limit (reg 110, £16,000), so at that limit no award depends on the " - "continuation. A reform that raises the limit lets awards read it." + "2008/280 reg 118), over 52 weeks: £1 for each complete £250 of capital " + "above the lower threshold and £1 for any part left over. The regulation " + "sets the lower threshold at £6,000, or £10,000 where " + "legacy_prescribed_accommodation_capital_exception is true, and stops " + "counting capital at £16,000. Above that amount this variable continues " + "the same arithmetic, though the regulation prescribes none there. " + "esa_income_eligible and the own-right claims in esa_income pay nothing " + "while capital exceeds the configured capital limit (reg 110, £16,000), " + "so at that limit no award depends on the continuation. Under a reform " + "that raises the limit, the model subtracts the continued tariff from " + "awards." ) definition_period = YEAR unit = GBP @@ -30,8 +31,8 @@ def formula(benunit, period, parameters): ESA = parameters(period).gov.dwp.ESA.income capital = benunit("esa_income_assessable_capital", period) mt = ESA.capital - # Reg 118(1)-(4): the lower threshold is £6,000, or £10,000 for a - # claimant who lives permanently in prescribed accommodation. Each + # Reg 118(1)-(4) sets the lower threshold at £6,000, or £10,000 for + # a claimant who lives permanently in prescribed accommodation. Each # branch subtracts its own scalar threshold, so both keep the stored # capital's float32 precision. ordinary_excess = max_(0, capital - mt.tariff_income.threshold) diff --git a/policyengine_uk/variables/gov/dwp/income_support_tariff_income.py b/policyengine_uk/variables/gov/dwp/income_support_tariff_income.py index aba5881cee..6c59e4d455 100644 --- a/policyengine_uk/variables/gov/dwp/income_support_tariff_income.py +++ b/policyengine_uk/variables/gov/dwp/income_support_tariff_income.py @@ -8,17 +8,17 @@ class income_support_tariff_income(Variable): label = "Income Support tariff income from capital" documentation = ( "Weekly tariff income from capital under IS Regs 1987 reg 53 (NI: SR " - "1987/459 reg 53), over 52 weeks: £1 for each complete £250 of " - "capital above the lower threshold and £1 for any part left over. The " - "lower threshold is £6,000, or £10,000 where " - "legacy_prescribed_accommodation_capital_exception is true. The " - "regulation counts capital only up to £16,000. Above that amount this " - "variable continues the same arithmetic, which the regulation does " - "not prescribe. income_support_applicable_income adds it in full, and " + "1987/459 reg 53), over 52 weeks: £1 for each complete £250 of capital " + "above the lower threshold and £1 for any part left over. The regulation " + "sets the lower threshold at £6,000, or £10,000 where " + "legacy_prescribed_accommodation_capital_exception is true, and stops " + "counting capital at £16,000. Above that amount this variable continues " + "the same arithmetic, though the regulation prescribes none there. " + "income_support_applicable_income adds it in full, and " "income_support_entitlement pays nothing while income_support_eligible " - "finds capital above the configured capital limit (reg 45, £16,000), " - "so at that limit no award depends on the continuation. A reform that " - "raises the limit lets awards read it." + "finds capital above the configured capital limit (reg 45, £16,000), so " + "at that limit no award depends on the continuation. Under a reform that " + "raises the limit, the model subtracts the continued tariff from awards." ) definition_period = YEAR unit = GBP @@ -31,8 +31,8 @@ def formula(benunit, period, parameters): IS = parameters(period).gov.dwp.income_support capital = benunit("income_support_assessable_capital", period) mt = IS.means_test.capital - # Reg 53(1)-(1C): the lower threshold is £6,000, or £10,000 for a - # claimant who lives permanently in prescribed accommodation. Each + # Reg 53(1)-(1C) sets the lower threshold at £6,000, or £10,000 for + # a claimant who lives permanently in prescribed accommodation. Each # branch subtracts its own scalar threshold, so both keep the stored # capital's float32 precision. ordinary_excess = max_(0, capital - mt.tariff_income.threshold) diff --git a/policyengine_uk/variables/gov/dwp/jsa_income_tariff_income.py b/policyengine_uk/variables/gov/dwp/jsa_income_tariff_income.py index 943e09e012..7d95d921f8 100644 --- a/policyengine_uk/variables/gov/dwp/jsa_income_tariff_income.py +++ b/policyengine_uk/variables/gov/dwp/jsa_income_tariff_income.py @@ -8,16 +8,17 @@ class jsa_income_tariff_income(Variable): label = "Income-based JSA tariff income from capital" documentation = ( "Weekly tariff income from capital under JSA Regs 1996 reg 116 (NI: SR " - "1996/198 reg 116), over 52 weeks: £1 for each complete £250 of " - "capital above the lower threshold and £1 for any part left over. The " - "lower threshold is £6,000, or £10,000 where " - "legacy_prescribed_accommodation_capital_exception is true. The " - "regulation counts capital only up to £16,000. Above that amount this " - "variable continues the same arithmetic, which the regulation does " - "not prescribe. jsa_income_eligible and the own-right claims in " - "jsa_income pay nothing while capital exceeds the configured capital " - "limit (reg 107, £16,000), so at that limit no award depends on the " - "continuation. A reform that raises the limit lets awards read it." + "1996/198 reg 116), over 52 weeks: £1 for each complete £250 of capital " + "above the lower threshold and £1 for any part left over. The regulation " + "sets the lower threshold at £6,000, or £10,000 where " + "legacy_prescribed_accommodation_capital_exception is true, and stops " + "counting capital at £16,000. Above that amount this variable continues " + "the same arithmetic, though the regulation prescribes none there. " + "jsa_income_eligible and the own-right claims in jsa_income pay nothing " + "while capital exceeds the configured capital limit (reg 107, £16,000), " + "so at that limit no award depends on the continuation. Under a reform " + "that raises the limit, the model subtracts the continued tariff from " + "awards." ) definition_period = YEAR unit = GBP @@ -30,8 +31,8 @@ def formula(benunit, period, parameters): JSA = parameters(period).gov.dwp.JSA.income capital = benunit("jsa_income_assessable_capital", period) mt = JSA.capital - # Reg 116(1)-(1C): the lower threshold is £6,000, or £10,000 for a - # claimant who lives permanently in prescribed accommodation. Each + # Reg 116(1)-(1C) sets the lower threshold at £6,000, or £10,000 for + # a claimant who lives permanently in prescribed accommodation. Each # branch subtracts its own scalar threshold, so both keep the stored # capital's float32 precision. ordinary_excess = max_(0, capital - mt.tariff_income.threshold) diff --git a/policyengine_uk/variables/gov/dwp/legacy_prescribed_accommodation_capital_exception.py b/policyengine_uk/variables/gov/dwp/legacy_prescribed_accommodation_capital_exception.py index bcf3847a73..c725835824 100644 --- a/policyengine_uk/variables/gov/dwp/legacy_prescribed_accommodation_capital_exception.py +++ b/policyengine_uk/variables/gov/dwp/legacy_prescribed_accommodation_capital_exception.py @@ -12,34 +12,31 @@ class legacy_prescribed_accommodation_capital_exception(Variable): "for the capital tariff of Income Support, income-based JSA and " "income-related ESA (GB: IS Regs 1987 reg 53(1B)-(1C), JSA Regs 1996 reg " "116(1B)-(1C), ESA Regs 2008 reg 118(3)-(4); NI: the same regulations of " - "SR 1987/459, SR 1996/198 and SR 2008/280). True sets the lower " - "threshold to £10,000 instead of £6,000. " - "Assess the claimant's own stay: a partner who lives in a home does not " - "make the claimant qualify. " - "In Great Britain the places are a care home, an independent hospital, " - "an Abbeyfield Home, and Polish Resettlement Act 1947 accommodation " - "where the claimant requires personal care by reason of old age, " - "disablement, past or present dependence on alcohol or drugs, past or " - "present mental disorder or a terminal illness, and receives that care " - "there. In Northern Ireland they are a residential care home, a nursing " - "home, an independent hospital and an Abbeyfield Home. " - "A claimant who is away still counts as living there permanently: away " - "from a home or hospital for up to 13 weeks (52 weeks for a claimant " - "over pensionable age under IS and ESA), or away from Polish " - "Resettlement Act accommodation while the claimant intends, with the " - "manager's agreement, to return in due course, which has no fixed " - "limit. " + "SR 1987/459, SR 1996/198 and SR 2008/280). True raises the lower " + "threshold from £6,000 to £10,000. Assess the claimant's own stay: a " + "partner who lives in a home does not make the claimant qualify. In Great " + "Britain the claimant must live permanently in a care home, an " + "independent hospital, an Abbeyfield Home, or Polish Resettlement Act " + "1947 accommodation where they require personal care by reason of old " + "age, disablement, past or present dependence on alcohol or drugs, past " + "or present mental disorder or a terminal illness, and receive that care " + "there. In Northern Ireland the claimant must live permanently in a " + "residential care home, a nursing home, an independent hospital or an " + "Abbeyfield Home. A claimant still counts as living there permanently " + "while away: from a home or hospital for up to 13 weeks (52 weeks for a " + "claimant over pensionable age under IS and ESA), or from Polish " + "Resettlement Act accommodation for as long as they intend, with the " + "manager's agreement, to return in due course, with no fixed limit. " "Assess the rules in force for the year and jurisdiction modelled. " "Housing Benefit prescribes different circumstances " "(housing_benefit_residential_capital_exception), and neither input sets " - "the other. " - "Other members who claim in their own right use the benefit unit's " - "tariff income, so this input sets their threshold too; the model does " - "not assess their own stay or divide capital between claims. " - "No dataset supplies this input. It defaults to false, which keeps the " + "the other. Other members who claim in their own right use the benefit " + "unit's tariff income, so this input sets their threshold too; the model " + "does not assess their own stay or divide capital between claims. No " + "dataset supplies this input. It defaults to false, which keeps the " "£6,000 threshold and may understate these benefits for qualifying " - "residents. It does not change eligibility, how capital is valued or " - "the upper capital limit." + "residents. It does not change eligibility, how the model values capital " + "or the upper capital limit." ) reference = ( "https://www.legislation.gov.uk/uksi/1987/1967/regulation/53", From f2602255e4058bf961af9406b32c2ca5e89d8071 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sun, 1 Nov 2026 07:23:55 -0500 Subject: [PATCH 7/7] Give the tariff documentation its actors DWP and the Department for Communities, the model and the caller now act in the tariff documentation, the formula comments, the YAML headers and the property test docstrings, in place of the regulations and benefits. Co-Authored-By: Claude Opus 5.5 --- .../esa_income_residential_capital.yaml | 20 +++--- .../income_support_residential_capital.yaml | 18 ++--- .../jsa_income_residential_capital.yaml | 18 ++--- .../test_legacy_capital_tariff_properties.py | 69 ++++++++++--------- .../gov/dwp/esa_income_tariff_income.py | 28 ++++---- .../gov/dwp/income_support_tariff_income.py | 29 ++++---- .../gov/dwp/jsa_income_tariff_income.py | 28 ++++---- ...scribed_accommodation_capital_exception.py | 28 ++++---- 8 files changed, 124 insertions(+), 114 deletions(-) diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/esa_income/esa_income_residential_capital.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/esa_income/esa_income_residential_capital.yaml index 7d91a2c476..d74d1c0679 100644 --- a/policyengine_uk/tests/policy/baseline/finance/benefit/family/esa_income/esa_income_residential_capital.yaml +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/esa_income/esa_income_residential_capital.yaml @@ -1,13 +1,13 @@ -# Income-related ESA tariff income from capital: ESA Regs 2008 reg 118 (NI: -# SR 2008/280 reg 118). Capital above the lower threshold counts as £1 a week -# for each complete £250 (reg 118(1)-(2)) and £1 for any part of £250 left -# over (reg 118(5)), over 52 weeks. The regulation sets the lower threshold -# at £6,000 (reg 118(1)), or £10,000 where the claimant lives permanently in -# prescribed accommodation (reg 118(2)-(4)). It stops counting capital at -# £16,000, and esa_income pays nothing above the £16,000 capital limit (reg 110). The figures at £16,001 -# show the model continuing the arithmetic, which the regulation does not -# prescribe; the award case at £16,001 shows a nil award at the configured -# limit. +# Income-related ESA tariff income from capital: ESA Regs 2008 reg 118 (NI: SR +# 2008/280 reg 118). DWP and the Department for Communities count capital +# above the lower threshold as £1 a week for each complete £250 (reg +# 118(1)-(2)) and £1 for any part of £250 left over (reg 118(5)), over 52 +# weeks. They use a lower threshold of £6,000 (reg 118(1)), or £10,000 where +# the claimant lives permanently in prescribed accommodation (reg 118(2)-(4)), +# and stop counting capital at £16,000. The model pays no income-related ESA +# above the £16,000 capital limit (reg 110). At £16,001 the model continues +# the arithmetic, which the regulation does not prescribe, and the award case +# checks for a nil award at the configured limit. - name: Income-related ESA ordinary capital threshold and partial steps period: 2025 diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/income_support/income_support_residential_capital.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/income_support/income_support_residential_capital.yaml index 6b879b233c..501e64df3a 100644 --- a/policyengine_uk/tests/policy/baseline/finance/benefit/family/income_support/income_support_residential_capital.yaml +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/income_support/income_support_residential_capital.yaml @@ -1,13 +1,13 @@ # Income Support tariff income from capital: IS Regs 1987 reg 53 (NI: SR -# 1987/459 reg 53). Capital above the lower threshold counts as £1 a week for -# each complete £250 (reg 53(1)-(1A)) and £1 for any part of £250 left over -# (reg 53(2)), over 52 weeks. The regulation sets the lower threshold at -# £6,000 (reg 53(1)), or £10,000 where the claimant lives permanently in -# prescribed accommodation (reg 53(1A)-(1C)). It stops counting capital at -# £16,000, and income_support pays nothing above the £16,000 capital limit (reg 45). The figures at £16,001 -# show the model continuing the arithmetic, which the regulation does not -# prescribe; the award case at £16,001 shows a nil award at the configured -# limit. +# 1987/459 reg 53). DWP and the Department for Communities count capital above +# the lower threshold as £1 a week for each complete £250 (reg 53(1)-(1A)) and +# £1 for any part of £250 left over (reg 53(2)), over 52 weeks. They use a +# lower threshold of £6,000 (reg 53(1)), or £10,000 where the claimant lives +# permanently in prescribed accommodation (reg 53(1A)-(1C)), and stop counting +# capital at £16,000. The model pays no Income Support above the £16,000 +# capital limit (reg 45). At £16,001 the model continues the arithmetic, which +# the regulation does not prescribe, and the award case checks for a nil award +# at the configured limit. - name: Income Support ordinary capital threshold and partial steps period: 2025 diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/jsa_income/jsa_income_residential_capital.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/jsa_income/jsa_income_residential_capital.yaml index 1136509f5b..6effd37cf8 100644 --- a/policyengine_uk/tests/policy/baseline/finance/benefit/family/jsa_income/jsa_income_residential_capital.yaml +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/jsa_income/jsa_income_residential_capital.yaml @@ -1,13 +1,13 @@ # Income-based JSA tariff income from capital: JSA Regs 1996 reg 116 (NI: SR -# 1996/198 reg 116). Capital above the lower threshold counts as £1 a week for -# each complete £250 (reg 116(1)-(1A)) and £1 for any part of £250 left over -# (reg 116(2)), over 52 weeks. The regulation sets the lower threshold at -# £6,000 (reg 116(1)), or £10,000 where the claimant lives permanently in -# prescribed accommodation (reg 116(1A)-(1C)). It stops counting capital at -# £16,000, and jsa_income pays nothing above the £16,000 capital limit (reg 107). The figures at £16,001 -# show the model continuing the arithmetic, which the regulation does not -# prescribe; the award case at £16,001 shows a nil award at the configured -# limit. +# 1996/198 reg 116). DWP and the Department for Communities count capital +# above the lower threshold as £1 a week for each complete £250 (reg +# 116(1)-(1A)) and £1 for any part of £250 left over (reg 116(2)), over 52 +# weeks. They use a lower threshold of £6,000 (reg 116(1)), or £10,000 where +# the claimant lives permanently in prescribed accommodation (reg +# 116(1A)-(1C)), and stop counting capital at £16,000. The model pays no +# income-based JSA above the £16,000 capital limit (reg 107). At £16,001 the +# model continues the arithmetic, which the regulation does not prescribe, and +# the award case checks for a nil award at the configured limit. - name: Income-based JSA ordinary capital threshold and partial steps period: 2025 diff --git a/policyengine_uk/tests/test_legacy_capital_tariff_properties.py b/policyengine_uk/tests/test_legacy_capital_tariff_properties.py index 8d126ba56b..fc39af1ebc 100644 --- a/policyengine_uk/tests/test_legacy_capital_tariff_properties.py +++ b/policyengine_uk/tests/test_legacy_capital_tariff_properties.py @@ -1,36 +1,37 @@ """Property tests for tariff income from capital in the legacy means tests. -Income Support (IS Regs 1987 reg 53), income-based JSA (JSA Regs 1996 reg -116) and income-related ESA (ESA Regs 2008 reg 118), and the same regulations -of the Northern Ireland rules, treat capital above a lower threshold as a -weekly income of £1 for each complete £250 and £1 for any part of £250 left -over, counting capital up to £16,000. The regulations set the lower -threshold at £6,000, or £10,000 where the claimant lives permanently in -prescribed accommodation (IS reg 53(1A)-(1C), JSA reg 116(1A)-(1C), ESA reg -118(2)-(4)). Working-age -Housing Benefit (HB Regs 2006 reg 52) uses the same figures, with its own -residential circumstances. - -Statutory invariants, for any capital on a penny grid up to £16,000 and -either exception status: +DWP and Northern Ireland's Department for Communities treat capital above a +lower threshold as a weekly income of £1 for each complete £250 and £1 for +any part of £250 left over, counting capital up to £16,000. They apply this +rule in Income Support (IS Regs 1987 reg 53), income-based JSA (JSA Regs +1996 reg 116) and income-related ESA (ESA Regs 2008 reg 118), and under the +same regulations of the Northern Ireland rules. They use a lower threshold of +£6,000, or £10,000 where the claimant lives permanently in prescribed +accommodation (IS reg 53(1A)-(1C), JSA reg 116(1A)-(1C), ESA reg +118(2)-(4)). Working-age Housing Benefit (HB Regs 2006 reg 52) uses the same +figures, with its own residential circumstances. + +These statutory invariants hold for any capital on a penny grid up to +£16,000 and either exception status: 1. Reference: each programme's tariff income is 52 x ceil(max(0, capital - - threshold) / 250) x £1, with the threshold the regulation gives. + threshold) / 250) x £1, with the regulation's threshold. 2. Own parameters: each programme reads its own ordinary and prescribed - accommodation thresholds. With all six set to different amounts in pence, - each programme's tariff income follows its own two. + accommodation thresholds. When the test sets all six thresholds, two per + programme, to different amounts in pence, each programme's tariff income + follows its own two. -Model invariants, for any capital and either exception status: +These model invariants hold for any capital and either exception status: 3. Continuation: above £16,000 the regulations prescribe no tariff income. - The variables continue the same arithmetic there, and the award formulas - pay nothing above the configured £16,000 capital limit. + The variables continue the same arithmetic there, and the model pays no + award above the configured £16,000 capital limit. 4. Monotone: tariff income never falls as capital rises. 5. Bounded: tariff income with the prescribed accommodation exception never exceeds the ordinary tariff income on the same capital. 6. Differential: the four programmes whose tariff parameters match give the - same tariff income at every capital level (Housing Benefit with its own - exception set the same way). + same tariff income at every capital level (the test sets Housing + Benefit's own exception the same way). """ from math import ceil @@ -91,8 +92,9 @@ STEP = 250 WEEKLY_AMOUNT = 1 -# Capital in pence, so every value names a sum of money, biased towards the -# thresholds and the upper capital limit, where the branches meet. +# PENCE draws capital in pence, so every value names a sum of money, and +# biases it towards the thresholds and the upper capital limit, where the +# branches meet. BOUNDARIES = [0, 6_000, 6_250, 10_000, 10_250, 16_000] PENCE = st.one_of( st.integers(0, 4_000_000), @@ -108,9 +110,9 @@ def tariff_arithmetic(capital, exception): """52 x £1 for each complete or part £250 of capital above the threshold. - Up to UPPER_LIMIT it returns the annual tariff income the regulations - give. Above it the regulations give none, and it returns the model's - continuation. + Up to UPPER_LIMIT the function returns the annual tariff income under + the regulations. Above UPPER_LIMIT, where the regulations prescribe no + tariff income, it continues the same arithmetic, as the model does. """ threshold = PRESCRIBED_ACCOMMODATION_THRESHOLD if exception else ORDINARY_THRESHOLD return WEEKS_IN_YEAR * WEEKLY_AMOUNT * ceil(max(0, capital - threshold) / STEP) @@ -141,7 +143,7 @@ def tariffs(simulation, capitals, exceptions, year): for _, capital_variable, exception_variable, _ in PROGRAMMES.values(): simulation.set_input(capital_variable, year, capital) simulation.set_input(exception_variable, year, exception) - # Working-age Housing Benefit rules, no Pension Credit passport. + # Apply working-age Housing Benefit rules, with no Pension Credit passport. for variable in ( "housing_benefit_pension_age_regulations_apply", "in_receipt_of_guarantee_credit", @@ -273,9 +275,10 @@ def set_parameters(simulation, year, values): def test_each_programme_reads_its_own_thresholds( simulation, ordinary, residential, pence, year ): - # 2. Own parameters. Six different thresholds, each with capital a penny - # either side and at equality, so a programme that read another's - # threshold would return a different tariff next to its own. + # 2. Own parameters. Hypothesis draws six different thresholds, and the + # test puts capital at each one and a penny either side, so a programme + # that read another programme's threshold would return a different tariff + # next to its own. assume(len(set(ordinary + residential)) == 6) values = {} for name, own_ordinary, own_residential in zip(LEGACY, ordinary, residential): @@ -297,9 +300,9 @@ def test_each_programme_reads_its_own_thresholds( excess = capital - (own_residential if exception else own_ordinary) # Most amounts in pence have no exact float32 value. Where capital # lies a whole number of £250 steps above the threshold, the stored - # figures can fall either side of the step, so the test skips it. - # Anywhere else the excess is at least a penny from a step, far - # more than that rounding. + # figures can fall either side of the step, so the test skips that + # point. Anywhere else the excess lies at least a penny from a + # step, which exceeds that rounding error. if excess > 0 and excess % (STEP * 100) == 0: continue expected = ( diff --git a/policyengine_uk/variables/gov/dwp/esa_income_tariff_income.py b/policyengine_uk/variables/gov/dwp/esa_income_tariff_income.py index 5284a56c58..d967faffdd 100644 --- a/policyengine_uk/variables/gov/dwp/esa_income_tariff_income.py +++ b/policyengine_uk/variables/gov/dwp/esa_income_tariff_income.py @@ -9,16 +9,17 @@ class esa_income_tariff_income(Variable): documentation = ( "Weekly tariff income from capital under ESA Regs 2008 reg 118 (NI: SR " "2008/280 reg 118), over 52 weeks: £1 for each complete £250 of capital " - "above the lower threshold and £1 for any part left over. The regulation " - "sets the lower threshold at £6,000, or £10,000 where " - "legacy_prescribed_accommodation_capital_exception is true, and stops " + "above the lower threshold and £1 for any part left over. DWP and " + "Northern Ireland's Department for Communities use a lower threshold of " + "£6,000, or £10,000 where " + "legacy_prescribed_accommodation_capital_exception is true, and stop " "counting capital at £16,000. Above that amount this variable continues " - "the same arithmetic, though the regulation prescribes none there. " - "esa_income_eligible and the own-right claims in esa_income pay nothing " - "while capital exceeds the configured capital limit (reg 110, £16,000), " - "so at that limit no award depends on the continuation. Under a reform " - "that raises the limit, the model subtracts the continued tariff from " - "awards." + "the same arithmetic, which the regulation doesn't prescribe. The model " + "pays no ESA through esa_income_eligible or the own-right claims in " + "esa_income while capital exceeds the configured capital limit (reg 110, " + "£16,000), so while the limit stays at £16,000 the arithmetic above it " + "changes no award. Under a reform that raises the limit, the model " + "subtracts the continued tariff from awards." ) definition_period = YEAR unit = GBP @@ -31,10 +32,11 @@ def formula(benunit, period, parameters): ESA = parameters(period).gov.dwp.ESA.income capital = benunit("esa_income_assessable_capital", period) mt = ESA.capital - # Reg 118(1)-(4) sets the lower threshold at £6,000, or £10,000 for - # a claimant who lives permanently in prescribed accommodation. Each - # branch subtracts its own scalar threshold, so both keep the stored - # capital's float32 precision. + # Under reg 118(1)-(4), DWP and the Department for Communities use a + # lower threshold of £6,000, or £10,000 for a claimant who lives + # permanently in prescribed accommodation. Each branch subtracts its + # own scalar threshold, so both keep the stored capital's float32 + # precision. ordinary_excess = max_(0, capital - mt.tariff_income.threshold) residential_excess = max_(0, capital - mt.residential_threshold) excess_capital = where( diff --git a/policyengine_uk/variables/gov/dwp/income_support_tariff_income.py b/policyengine_uk/variables/gov/dwp/income_support_tariff_income.py index 6c59e4d455..398bc0bcba 100644 --- a/policyengine_uk/variables/gov/dwp/income_support_tariff_income.py +++ b/policyengine_uk/variables/gov/dwp/income_support_tariff_income.py @@ -9,16 +9,18 @@ class income_support_tariff_income(Variable): documentation = ( "Weekly tariff income from capital under IS Regs 1987 reg 53 (NI: SR " "1987/459 reg 53), over 52 weeks: £1 for each complete £250 of capital " - "above the lower threshold and £1 for any part left over. The regulation " - "sets the lower threshold at £6,000, or £10,000 where " - "legacy_prescribed_accommodation_capital_exception is true, and stops " + "above the lower threshold and £1 for any part left over. DWP and " + "Northern Ireland's Department for Communities use a lower threshold of " + "£6,000, or £10,000 where " + "legacy_prescribed_accommodation_capital_exception is true, and stop " "counting capital at £16,000. Above that amount this variable continues " - "the same arithmetic, though the regulation prescribes none there. " - "income_support_applicable_income adds it in full, and " - "income_support_entitlement pays nothing while income_support_eligible " - "finds capital above the configured capital limit (reg 45, £16,000), so " - "at that limit no award depends on the continuation. Under a reform that " - "raises the limit, the model subtracts the continued tariff from awards." + "the same arithmetic, which the regulation doesn't prescribe. " + "income_support_applicable_income adds this variable in full, and the " + "model pays no IS through income_support_entitlement while " + "income_support_eligible finds capital above the configured capital limit " + "(reg 45, £16,000), so while the limit stays at £16,000 the arithmetic " + "above it changes no award. Under a reform that raises the limit, the " + "model subtracts the continued tariff from awards." ) definition_period = YEAR unit = GBP @@ -31,10 +33,11 @@ def formula(benunit, period, parameters): IS = parameters(period).gov.dwp.income_support capital = benunit("income_support_assessable_capital", period) mt = IS.means_test.capital - # Reg 53(1)-(1C) sets the lower threshold at £6,000, or £10,000 for - # a claimant who lives permanently in prescribed accommodation. Each - # branch subtracts its own scalar threshold, so both keep the stored - # capital's float32 precision. + # Under reg 53(1)-(1C), DWP and the Department for Communities use a + # lower threshold of £6,000, or £10,000 for a claimant who lives + # permanently in prescribed accommodation. Each branch subtracts its + # own scalar threshold, so both keep the stored capital's float32 + # precision. ordinary_excess = max_(0, capital - mt.tariff_income.threshold) residential_excess = max_(0, capital - mt.residential_threshold) excess_capital = where( diff --git a/policyengine_uk/variables/gov/dwp/jsa_income_tariff_income.py b/policyengine_uk/variables/gov/dwp/jsa_income_tariff_income.py index 7d95d921f8..07f38c69f7 100644 --- a/policyengine_uk/variables/gov/dwp/jsa_income_tariff_income.py +++ b/policyengine_uk/variables/gov/dwp/jsa_income_tariff_income.py @@ -9,16 +9,17 @@ class jsa_income_tariff_income(Variable): documentation = ( "Weekly tariff income from capital under JSA Regs 1996 reg 116 (NI: SR " "1996/198 reg 116), over 52 weeks: £1 for each complete £250 of capital " - "above the lower threshold and £1 for any part left over. The regulation " - "sets the lower threshold at £6,000, or £10,000 where " - "legacy_prescribed_accommodation_capital_exception is true, and stops " + "above the lower threshold and £1 for any part left over. DWP and " + "Northern Ireland's Department for Communities use a lower threshold of " + "£6,000, or £10,000 where " + "legacy_prescribed_accommodation_capital_exception is true, and stop " "counting capital at £16,000. Above that amount this variable continues " - "the same arithmetic, though the regulation prescribes none there. " - "jsa_income_eligible and the own-right claims in jsa_income pay nothing " - "while capital exceeds the configured capital limit (reg 107, £16,000), " - "so at that limit no award depends on the continuation. Under a reform " - "that raises the limit, the model subtracts the continued tariff from " - "awards." + "the same arithmetic, which the regulation doesn't prescribe. The model " + "pays no JSA through jsa_income_eligible or the own-right claims in " + "jsa_income while capital exceeds the configured capital limit (reg 107, " + "£16,000), so while the limit stays at £16,000 the arithmetic above it " + "changes no award. Under a reform that raises the limit, the model " + "subtracts the continued tariff from awards." ) definition_period = YEAR unit = GBP @@ -31,10 +32,11 @@ def formula(benunit, period, parameters): JSA = parameters(period).gov.dwp.JSA.income capital = benunit("jsa_income_assessable_capital", period) mt = JSA.capital - # Reg 116(1)-(1C) sets the lower threshold at £6,000, or £10,000 for - # a claimant who lives permanently in prescribed accommodation. Each - # branch subtracts its own scalar threshold, so both keep the stored - # capital's float32 precision. + # Under reg 116(1)-(1C), DWP and the Department for Communities use a + # lower threshold of £6,000, or £10,000 for a claimant who lives + # permanently in prescribed accommodation. Each branch subtracts its + # own scalar threshold, so both keep the stored capital's float32 + # precision. ordinary_excess = max_(0, capital - mt.tariff_income.threshold) residential_excess = max_(0, capital - mt.residential_threshold) excess_capital = where( diff --git a/policyengine_uk/variables/gov/dwp/legacy_prescribed_accommodation_capital_exception.py b/policyengine_uk/variables/gov/dwp/legacy_prescribed_accommodation_capital_exception.py index c725835824..f102a07748 100644 --- a/policyengine_uk/variables/gov/dwp/legacy_prescribed_accommodation_capital_exception.py +++ b/policyengine_uk/variables/gov/dwp/legacy_prescribed_accommodation_capital_exception.py @@ -12,10 +12,10 @@ class legacy_prescribed_accommodation_capital_exception(Variable): "for the capital tariff of Income Support, income-based JSA and " "income-related ESA (GB: IS Regs 1987 reg 53(1B)-(1C), JSA Regs 1996 reg " "116(1B)-(1C), ESA Regs 2008 reg 118(3)-(4); NI: the same regulations of " - "SR 1987/459, SR 1996/198 and SR 2008/280). True raises the lower " - "threshold from £6,000 to £10,000. Assess the claimant's own stay: a " - "partner who lives in a home does not make the claimant qualify. In Great " - "Britain the claimant must live permanently in a care home, an " + "SR 1987/459, SR 1996/198 and SR 2008/280). Setting it true raises the " + "lower threshold from £6,000 to £10,000. Assess the claimant's own stay: " + "the claimant doesn't qualify through a partner who lives in a home. In " + "Great Britain the claimant must live permanently in a care home, an " "independent hospital, an Abbeyfield Home, or Polish Resettlement Act " "1947 accommodation where they require personal care by reason of old " "age, disablement, past or present dependence on alcohol or drugs, past " @@ -27,16 +27,16 @@ class legacy_prescribed_accommodation_capital_exception(Variable): "claimant over pensionable age under IS and ESA), or from Polish " "Resettlement Act accommodation for as long as they intend, with the " "manager's agreement, to return in due course, with no fixed limit. " - "Assess the rules in force for the year and jurisdiction modelled. " - "Housing Benefit prescribes different circumstances " - "(housing_benefit_residential_capital_exception), and neither input sets " - "the other. Other members who claim in their own right use the benefit " - "unit's tariff income, so this input sets their threshold too; the model " - "does not assess their own stay or divide capital between claims. No " - "dataset supplies this input. It defaults to false, which keeps the " - "£6,000 threshold and may understate these benefits for qualifying " - "residents. It does not change eligibility, how the model values capital " - "or the upper capital limit." + "Assess the input under the rules in force for the year and jurisdiction " + "you model. For Housing Benefit, callers set different circumstances " + "through housing_benefit_residential_capital_exception, and neither input " + "sets the other. The model gives other members who claim in their own " + "right the benefit unit's tariff income, so this input sets their " + "threshold too; the model does not assess their own stay or divide " + "capital between claims. No dataset supplies this input. It defaults to " + "false, which keeps the £6,000 threshold and may understate these " + "benefits for qualifying residents. It does not change eligibility, how " + "the model values capital or the upper capital limit." ) reference = ( "https://www.legislation.gov.uk/uksi/1987/1967/regulation/53",