From c84289040d52bf44fc83460e7cacb1628d2a1b9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mar=C3=ADa=20Juaristi?= <127882282+juaristi22@users.noreply.github.com> Date: Thu, 1 Oct 2026 11:32:13 +0100 Subject: [PATCH 1/3] Count Lifetime ISAs as capital of the holder's benefit unit microcosm datasets now carry three Lifetime ISA cells imputed from the WAS round-8 person file (PolicyEngine/microcosm#1059). Declare them: lifetime_isa_balance (a person input, uprated by per-capita GDP like savings), has_lifetime_isa and household_lifetime_isa_balance. The balance is a component of gross_financial_wealth and stays out of savings, corporate_wealth and total_wealth. Every means test values capital at its current market or surrender value, and DWP guidance counts a Lifetime ISA at 75% of its value under 60 and in full from 60 (ADM H1659; DMG 29665, 52665 and 84751). Add the withdrawal charge (25%, 20% from 6 March 2020 to 5 April 2021) and the charge-free age (60) under gov.hmrc.isa.lifetime, and lifetime_isa_countable_capital at that surrender value. Household stocks reach benunits through per-regime proxies because the data only knows them per household. A Lifetime ISA is one person's, and most holders are young adults living with parents, so the proxies would count an adult child's Lifetime ISA against a parent's JSA claim or a grandchild's against a grandparent's Pension Credit. Each of the six means tests gets a person_sources list beside its household sources, summed over the benunit's own members. Fixes #1981 Co-Authored-By: Claude Opus 5.5 --- changelog.d/1981.added.md | 1 + docs/book/assumptions/growthfactors.md | 2 +- policyengine_uk/data/uprating_indices.yaml | 2 + .../ESA/income/capital/person_sources.yaml | 20 + .../JSA/income/capital/person_sources.yaml | 20 + .../means_test/capital/person_sources.yaml | 23 ++ .../means_test/capital/person_sources.yaml | 20 + .../income/capital/person_sources.yaml | 20 + .../means_test/capital/person_sources.yaml | 20 + .../parameters/gov/hmrc/isa/README.md | 1 + .../gov/hmrc/isa/lifetime/README.md | 1 + .../hmrc/isa/lifetime/charge_free_age.yaml | 11 + .../hmrc/isa/lifetime/withdrawal_charge.yaml | 19 + .../gov/dwp/lifetime_isa_capital.yaml | 356 ++++++++++++++++++ .../tests/test_lifetime_isa_capital.py | 86 +++++ .../gov/dwp/esa_income_assessable_capital.py | 12 +- .../housing_benefit_assessable_capital.py | 15 +- .../dwp/income_support_assessable_capital.py | 13 +- .../gov/dwp/jsa_income_assessable_capital.py | 12 +- .../gov/dwp/lifetime_isa_countable_capital.py | 30 ++ .../pension_credit_assessable_capital.py | 12 +- .../universal_credit/uc_assessable_capital.py | 10 +- .../household/wealth/has_lifetime_isa.py | 12 + .../wealth/household_lifetime_isa_balance.py | 16 + .../variables/input/lifetime_isa_balance.py | 23 ++ 25 files changed, 743 insertions(+), 14 deletions(-) create mode 100644 changelog.d/1981.added.md create mode 100644 policyengine_uk/parameters/gov/dwp/ESA/income/capital/person_sources.yaml create mode 100644 policyengine_uk/parameters/gov/dwp/JSA/income/capital/person_sources.yaml create mode 100644 policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/capital/person_sources.yaml create mode 100644 policyengine_uk/parameters/gov/dwp/income_support/means_test/capital/person_sources.yaml create mode 100644 policyengine_uk/parameters/gov/dwp/pension_credit/income/capital/person_sources.yaml create mode 100644 policyengine_uk/parameters/gov/dwp/universal_credit/means_test/capital/person_sources.yaml create mode 100644 policyengine_uk/parameters/gov/hmrc/isa/README.md create mode 100644 policyengine_uk/parameters/gov/hmrc/isa/lifetime/README.md create mode 100644 policyengine_uk/parameters/gov/hmrc/isa/lifetime/charge_free_age.yaml create mode 100644 policyengine_uk/parameters/gov/hmrc/isa/lifetime/withdrawal_charge.yaml create mode 100644 policyengine_uk/tests/policy/baseline/gov/dwp/lifetime_isa_capital.yaml create mode 100644 policyengine_uk/tests/test_lifetime_isa_capital.py create mode 100644 policyengine_uk/variables/gov/dwp/lifetime_isa_countable_capital.py create mode 100644 policyengine_uk/variables/household/wealth/has_lifetime_isa.py create mode 100644 policyengine_uk/variables/household/wealth/household_lifetime_isa_balance.py create mode 100644 policyengine_uk/variables/input/lifetime_isa_balance.py diff --git a/changelog.d/1981.added.md b/changelog.d/1981.added.md new file mode 100644 index 0000000000..d71b9d5e30 --- /dev/null +++ b/changelog.d/1981.added.md @@ -0,0 +1 @@ +- Added the Lifetime ISA holdings that microcosm datasets carry (`lifetime_isa_balance`, `has_lifetime_isa`, `household_lifetime_isa_balance`) and the Lifetime ISA withdrawal-charge parameters. Universal Credit, Housing Benefit, Income Support, income-based JSA, income-related ESA and Pension Credit now count a Lifetime ISA at its surrender value (75% of the balance under 60, the whole balance from 60) as capital of the holder's own benefit unit. Datasets without the columns are unaffected. diff --git a/docs/book/assumptions/growthfactors.md b/docs/book/assumptions/growthfactors.md index 1b093f7b61..4d88c837b9 100644 --- a/docs/book/assumptions/growthfactors.md +++ b/docs/book/assumptions/growthfactors.md @@ -60,7 +60,7 @@ We derive these rates from OBR GDP growth and ONS population projections. |-------------|------|------|------|------|------|------|------|------| | Growth rate | 9.2% | 5.0% | 3.8% | 2.8% | 2.8% | 3.1% | 3.3% | 3.3% | -We use per capita GDP for these variables: `capital_gains`, `capital_gains_badr`, `capital_gains_before_response`, `capital_gains_carried_interest`, `capital_gains_residential_property`, `corporate_wealth`, `dividend_income`, `gross_financial_wealth`, `lump_sum_income`, `main_residence_value`, `maintenance_income`, `miscellaneous_income`, `mortgage_capital_repayment`, `net_financial_wealth`, `non_residential_property_value`, `other_investment_income`, `other_residential_property_value`, `owned_land`, `pension_income`, `private_pension_wealth`, `private_transfer_income`, `property_income`, `savings`, `savings_interest_income`, `sublet_income` +We use per capita GDP for these variables: `capital_gains`, `capital_gains_badr`, `capital_gains_before_response`, `capital_gains_carried_interest`, `capital_gains_residential_property`, `corporate_wealth`, `dividend_income`, `gross_financial_wealth`, `household_lifetime_isa_balance`, `lifetime_isa_balance`, `lump_sum_income`, `main_residence_value`, `maintenance_income`, `miscellaneous_income`, `mortgage_capital_repayment`, `net_financial_wealth`, `non_residential_property_value`, `other_investment_income`, `other_residential_property_value`, `owned_land`, `pension_income`, `private_pension_wealth`, `private_transfer_income`, `property_income`, `savings`, `savings_interest_income`, `sublet_income` ## Council tax diff --git a/policyengine_uk/data/uprating_indices.yaml b/policyengine_uk/data/uprating_indices.yaml index 4fbc8bc819..c7175e2fcc 100644 --- a/policyengine_uk/data/uprating_indices.yaml +++ b/policyengine_uk/data/uprating_indices.yaml @@ -86,6 +86,8 @@ gov.economic_assumptions.yoy_growth.obr.per_capita.gdp: - corporate_wealth - dividend_income - gross_financial_wealth +- household_lifetime_isa_balance +- lifetime_isa_balance - lump_sum_income - main_residence_value - maintenance_income diff --git a/policyengine_uk/parameters/gov/dwp/ESA/income/capital/person_sources.yaml b/policyengine_uk/parameters/gov/dwp/ESA/income/capital/person_sources.yaml new file mode 100644 index 0000000000..c54fe2cd82 --- /dev/null +++ b/policyengine_uk/parameters/gov/dwp/ESA/income/capital/person_sources.yaml @@ -0,0 +1,20 @@ +description: > + Person-level model variables counted as income-related Employment and + Support Allowance capital of the person's own benefit unit. They are summed + over the benefit unit's members, so they need none of the household proxy + that the household `sources` use. `lifetime_isa_countable_capital` is a + Lifetime ISA at its surrender value: regulation 113 values capital at its + current market or surrender value, and DMG 52665 counts a Lifetime ISA at + 75% of its value under 60 and in full from 60. +metadata: + label: Income-related ESA person-level capital sources + period: year + unit: list + reference: + - title: The Employment and Support Allowance Regulations 2008 reg. 113 + href: https://www.legislation.gov.uk/uksi/2008/794/regulation/113 + - title: Decision Makers' Guide, Chapter 52 (Capital), para. 52665 + href: https://assets.publishing.service.gov.uk/media/69d8f30ad3e08b8871b665ac/dmg-Ch-52.pdf +values: + 2017-04-06: + - lifetime_isa_countable_capital diff --git a/policyengine_uk/parameters/gov/dwp/JSA/income/capital/person_sources.yaml b/policyengine_uk/parameters/gov/dwp/JSA/income/capital/person_sources.yaml new file mode 100644 index 0000000000..75a9698a91 --- /dev/null +++ b/policyengine_uk/parameters/gov/dwp/JSA/income/capital/person_sources.yaml @@ -0,0 +1,20 @@ +description: > + Person-level model variables counted as income-based Jobseeker's Allowance + capital of the person's own benefit unit. They are summed over the benefit + unit's members, so they need none of the household proxy that the household + `sources` use. `lifetime_isa_countable_capital` is a Lifetime ISA at its + surrender value: regulation 111 values capital at its current market or + surrender value, and DMG 29665 counts a Lifetime ISA at 75% of its value + under 60 and in full from 60. +metadata: + label: Income-based JSA person-level capital sources + period: year + unit: list + reference: + - title: The Jobseeker's Allowance Regulations 1996 reg. 111 + href: https://www.legislation.gov.uk/uksi/1996/207/regulation/111 + - title: Decision Makers' Guide, Chapter 29 (Capital), para. 29665 + href: https://assets.publishing.service.gov.uk/media/6894a69d3080e72710b2e237/dmg-ch-29.pdf +values: + 2017-04-06: + - lifetime_isa_countable_capital diff --git a/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/capital/person_sources.yaml b/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/capital/person_sources.yaml new file mode 100644 index 0000000000..e5013ab2dc --- /dev/null +++ b/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/capital/person_sources.yaml @@ -0,0 +1,23 @@ +description: > + Person-level model variables counted as Housing Benefit capital of the + person's own benefit unit. They are summed over the benefit unit's members, + so they need none of the household proxy that the household `sources` use. + `lifetime_isa_countable_capital` is a Lifetime ISA at its surrender value: + regulation 47 (regulation 45 of the pension-age regulations) values capital + at its current market or surrender value, in the words of the DWP benefits' + regulations, for which DWP guidance counts a Lifetime ISA at 75% of its + value under 60 and in full from 60. +metadata: + label: Housing Benefit person-level capital sources + period: year + unit: list + reference: + - title: The Housing Benefit Regulations 2006 reg. 47 + href: https://www.legislation.gov.uk/uksi/2006/213/regulation/47 + - title: The Housing Benefit (Persons who have attained the qualifying age for state pension credit) Regulations 2006 reg. 45 + href: https://www.legislation.gov.uk/uksi/2006/214/regulation/45 + - title: Decision Makers' Guide, Chapter 29 (Capital), para. 29665 (Income Support and JSA, whose valuation rule has the same words) + href: https://assets.publishing.service.gov.uk/media/6894a69d3080e72710b2e237/dmg-ch-29.pdf +values: + 2017-04-06: + - lifetime_isa_countable_capital diff --git a/policyengine_uk/parameters/gov/dwp/income_support/means_test/capital/person_sources.yaml b/policyengine_uk/parameters/gov/dwp/income_support/means_test/capital/person_sources.yaml new file mode 100644 index 0000000000..aeb69780c8 --- /dev/null +++ b/policyengine_uk/parameters/gov/dwp/income_support/means_test/capital/person_sources.yaml @@ -0,0 +1,20 @@ +description: > + Person-level model variables counted as Income Support capital of the + person's own benefit unit. They are summed over the benefit unit's members, + so they need none of the household proxy that the household `sources` use. + `lifetime_isa_countable_capital` is a Lifetime ISA at its surrender value: + regulation 49 values capital at its current market or surrender value, and + DMG 29665 counts a Lifetime ISA at 75% of its value under 60 and in full + from 60. +metadata: + label: Income Support person-level capital sources + period: year + unit: list + reference: + - title: The Income Support (General) Regulations 1987 reg. 49 + href: https://www.legislation.gov.uk/uksi/1987/1967/regulation/49 + - title: Decision Makers' Guide, Chapter 29 (Capital), para. 29665 + href: https://assets.publishing.service.gov.uk/media/6894a69d3080e72710b2e237/dmg-ch-29.pdf +values: + 2017-04-06: + - lifetime_isa_countable_capital diff --git a/policyengine_uk/parameters/gov/dwp/pension_credit/income/capital/person_sources.yaml b/policyengine_uk/parameters/gov/dwp/pension_credit/income/capital/person_sources.yaml new file mode 100644 index 0000000000..498df874f7 --- /dev/null +++ b/policyengine_uk/parameters/gov/dwp/pension_credit/income/capital/person_sources.yaml @@ -0,0 +1,20 @@ +description: > + Person-level model variables counted as Pension Credit capital of the + person's own benefit unit. They are summed over the benefit unit's members, + so they need none of the household proxy that the household `sources` use. + `lifetime_isa_countable_capital` is a Lifetime ISA at its surrender value: + regulation 19 values capital at its current market or surrender value, and + DMG 84751 counts a Lifetime ISA at 75% of its value under 60 and in full + from 60, so a claimant's own Lifetime ISA counts in full. +metadata: + label: Pension Credit person-level capital sources + period: year + unit: list + reference: + - title: The State Pension Credit Regulations 2002 reg. 19 + href: https://www.legislation.gov.uk/uksi/2002/1792/regulation/19 + - title: Decision Makers' Guide, Chapter 84 (Deemed weekly income from capital), para. 84751 + href: https://assets.publishing.service.gov.uk/media/69d8f133daa863aa5db665a5/dmg-Ch-84.pdf +values: + 2017-04-06: + - lifetime_isa_countable_capital diff --git a/policyengine_uk/parameters/gov/dwp/universal_credit/means_test/capital/person_sources.yaml b/policyengine_uk/parameters/gov/dwp/universal_credit/means_test/capital/person_sources.yaml new file mode 100644 index 0000000000..de1cc31205 --- /dev/null +++ b/policyengine_uk/parameters/gov/dwp/universal_credit/means_test/capital/person_sources.yaml @@ -0,0 +1,20 @@ +description: > + Person-level model variables counted as Universal Credit capital of the + person's own benefit unit. They are summed over the benefit unit's members, + so they need none of the household proxy that the household `sources` use. + `lifetime_isa_countable_capital` is a Lifetime ISA at its surrender value: + regulation 49(1) values capital at its current market or surrender value, + and ADM H1659 counts a Lifetime ISA at 75% of its value under 60 and in + full from 60. +metadata: + label: Universal Credit person-level capital sources + period: year + unit: list + reference: + - title: The Universal Credit Regulations 2013 reg. 49(1) + href: https://www.legislation.gov.uk/uksi/2013/376/regulation/49/1 + - title: Advice for Decision Making, Chapter H1 (Capital), para. H1659 + href: https://assets.publishing.service.gov.uk/media/693ace15c72b0f8ccf33d609/adm-ch-H1.pdf +values: + 2017-04-06: + - lifetime_isa_countable_capital diff --git a/policyengine_uk/parameters/gov/hmrc/isa/README.md b/policyengine_uk/parameters/gov/hmrc/isa/README.md new file mode 100644 index 0000000000..84784a6b52 --- /dev/null +++ b/policyengine_uk/parameters/gov/hmrc/isa/README.md @@ -0,0 +1 @@ +# Individual Savings Accounts diff --git a/policyengine_uk/parameters/gov/hmrc/isa/lifetime/README.md b/policyengine_uk/parameters/gov/hmrc/isa/lifetime/README.md new file mode 100644 index 0000000000..f0f1144b7e --- /dev/null +++ b/policyengine_uk/parameters/gov/hmrc/isa/lifetime/README.md @@ -0,0 +1 @@ +# Lifetime ISA diff --git a/policyengine_uk/parameters/gov/hmrc/isa/lifetime/charge_free_age.yaml b/policyengine_uk/parameters/gov/hmrc/isa/lifetime/charge_free_age.yaml new file mode 100644 index 0000000000..1b93c5363a --- /dev/null +++ b/policyengine_uk/parameters/gov/hmrc/isa/lifetime/charge_free_age.yaml @@ -0,0 +1,11 @@ +description: > + Age from which withdrawals from a Lifetime ISA carry no withdrawal charge. +values: + 2017-04-06: 60 +metadata: + label: Lifetime ISA charge-free age + period: year + unit: year + reference: + - title: The Individual Savings Account Regulations 1998, Schedule paragraph 4(1) + href: https://www.legislation.gov.uk/uksi/1998/1870/schedule/paragraph/4 diff --git a/policyengine_uk/parameters/gov/hmrc/isa/lifetime/withdrawal_charge.yaml b/policyengine_uk/parameters/gov/hmrc/isa/lifetime/withdrawal_charge.yaml new file mode 100644 index 0000000000..90c07d0ca5 --- /dev/null +++ b/policyengine_uk/parameters/gov/hmrc/isa/lifetime/withdrawal_charge.yaml @@ -0,0 +1,19 @@ +description: > + Share of the amount withdrawn from a Lifetime ISA that is charged when the + withdrawal is not exempt, that is, when it is made before the charge-free age + and not towards a first home or with a terminal illness. +values: + 2017-04-06: 0.25 + 2020-03-06: 0.2 + 2021-04-06: 0.25 +metadata: + label: Lifetime ISA withdrawal charge + period: year + unit: /1 + reference: + - title: The Individual Savings Account Regulations 1998, Schedule paragraph 5 + href: https://www.legislation.gov.uk/uksi/1998/1870/schedule/paragraph/5 + - title: The Individual Savings Account Regulations 1998, Schedule paragraph 5A (20% from 6 March 2020 to 5 April 2021) + href: https://www.legislation.gov.uk/uksi/1998/1870/schedule/paragraph/5A + - title: The Individual Savings Account (Amendment No. 3) (Coronavirus) Regulations 2020 + href: https://www.legislation.gov.uk/uksi/2020/506/made diff --git a/policyengine_uk/tests/policy/baseline/gov/dwp/lifetime_isa_capital.yaml b/policyengine_uk/tests/policy/baseline/gov/dwp/lifetime_isa_capital.yaml new file mode 100644 index 0000000000..5d1685cc7d --- /dev/null +++ b/policyengine_uk/tests/policy/baseline/gov/dwp/lifetime_isa_capital.yaml @@ -0,0 +1,356 @@ +- name: Holders and the household total come from the person balances + period: 2025 + absolute_error_margin: 0 + input: + people: + person_1: + age: 30 + lifetime_isa_balance: 4_000 + person_2: + age: 28 + lifetime_isa_balance: 6_000 + person_3: + age: 55 + benunits: + benunit_1: + members: [person_1, person_2] + benunit_2: + members: person_3 + households: + household: + members: [person_1, person_2, person_3] + output: + has_lifetime_isa: [true, true, false] + household_lifetime_isa_balance: 10_000 + +- name: Under 60 the Lifetime ISA counts after the 25% withdrawal charge + period: 2025 + absolute_error_margin: 0 + input: + people: + person: + age: 59 + lifetime_isa_balance: 10_000 + benunits: + benunit: + members: person + households: + household: + members: person + output: + lifetime_isa_countable_capital: 7_500 + +- name: From 60 the Lifetime ISA counts in full + period: 2025 + absolute_error_margin: 0 + input: + people: + person: + age: 60 + lifetime_isa_balance: 10_000 + benunits: + benunit: + members: person + households: + household: + members: person + output: + lifetime_isa_countable_capital: 10_000 + +- name: In 2020-21 the withdrawal charge was 20% + period: 2020 + absolute_error_margin: 0 + input: + people: + person: + age: 30 + lifetime_isa_balance: 10_000 + benunits: + benunit: + members: person + households: + household: + members: person + output: + lifetime_isa_countable_capital: 8_000 + +- name: In 2021-22 the withdrawal charge is 25% again + period: 2021 + absolute_error_margin: 0 + input: + people: + person: + age: 30 + lifetime_isa_balance: 10_000 + benunits: + benunit: + members: person + households: + household: + members: person + output: + lifetime_isa_countable_capital: 7_500 + +- name: Universal Credit counts the Lifetime ISA at its surrender value + period: 2025 + absolute_error_margin: 0 + input: + people: + person: + age: 30 + lifetime_isa_balance: 10_000 + benunits: + benunit: + members: person + households: + household: + members: person + output: + uc_assessable_capital: 7_500 + +- name: A Universal Credit claimant aged 60 counts the whole Lifetime ISA + period: 2025 + absolute_error_margin: 0 + input: + people: + person: + age: 60 + lifetime_isa_balance: 10_000 + benunits: + benunit: + members: person + households: + household: + members: person + output: + uc_assessable_capital: 10_000 + +- name: Savings and a Lifetime ISA together reach the Universal Credit capital limit + period: 2025 + absolute_error_margin: 0 + input: + people: + person: + age: 30 + lifetime_isa_balance: 8_000 + benunits: + benunit: + members: person + households: + household: + members: person + savings: 10_000 + output: + uc_assessable_capital: 16_000 + is_uc_eligible: true + +- name: Savings and a Lifetime ISA together exceed the Universal Credit capital limit + period: 2025 + absolute_error_margin: 0 + input: + people: + person: + age: 30 + lifetime_isa_balance: 10_000 + benunits: + benunit: + members: person + households: + household: + members: person + savings: 10_000 + output: + uc_assessable_capital: 17_500 + is_uc_eligible: false + +- name: An adult child's Lifetime ISA counts in the child's Universal Credit benunit only + period: 2025 + absolute_error_margin: 0 + input: + people: + parent_1: + age: 50 + parent_2: + age: 48 + adult_child: + age: 25 + lifetime_isa_balance: 10_000 + benunits: + parents: + members: [parent_1, parent_2] + child: + members: adult_child + households: + household: + members: [parent_1, parent_2, adult_child] + savings: 9_000 + output: + # Household savings are shared by adults (6,000 and 3,000); the + # Lifetime ISA adds 7,500 to the child's benunit alone. + uc_assessable_capital: [6_000, 10_500] + +- name: Reported Universal Credit capital overrides the holder's Lifetime ISA + period: 2025 + absolute_error_margin: 0 + input: + people: + parent_1: + age: 50 + parent_2: + age: 48 + adult_child: + age: 25 + lifetime_isa_balance: 10_000 + benunits: + parents: + members: [parent_1, parent_2] + child: + members: adult_child + uc_reported_capital: 2_000 + households: + household: + members: [parent_1, parent_2, adult_child] + savings: 9_000 + output: + uc_assessable_capital: [7_000, 2_000] + +- name: An adult child's Lifetime ISA counts in the child's Housing Benefit benunit only + period: 2025 + absolute_error_margin: 0 + input: + people: + parent_1: + age: 50 + parent_2: + age: 48 + adult_child: + age: 25 + lifetime_isa_balance: 10_000 + benunits: + parents: + members: [parent_1, parent_2] + child: + members: adult_child + households: + household: + members: [parent_1, parent_2, adult_child] + savings: 9_000 + output: + housing_benefit_assessable_capital: [6_000, 10_500] + +- name: A parent's Income Support claim does not count an adult child's Lifetime ISA + period: 2025 + absolute_error_margin: 0 + input: + people: + parent: + age: 45 + income_support_reported: true + child: + age: 4 + adult_child: + age: 22 + lifetime_isa_balance: 10_000 + benunits: + parent_and_child: + members: [parent, child] + would_claim_IS: true + adult_child: + members: adult_child + households: + household: + members: [parent, child, adult_child] + savings: 2_000 + output: + # The claiming benunit takes the whole household stock (2,000); the + # Lifetime ISA stays with its holder. + income_support_assessable_capital: [2_000, 7_500] + +- name: A parent's income-based JSA claim does not count an adult child's Lifetime ISA + period: 2025 + absolute_error_margin: 0 + input: + people: + parent: + age: 45 + jsa_income_reported: 3_000 + adult_child: + age: 22 + lifetime_isa_balance: 10_000 + benunits: + parent: + members: parent + adult_child: + members: adult_child + households: + household: + members: [parent, adult_child] + savings: 2_000 + output: + jsa_income_assessable_capital: [2_000, 7_500] + +- name: A parent's income-related ESA claim does not count an adult child's Lifetime ISA + period: 2025 + absolute_error_margin: 0 + input: + people: + parent: + age: 45 + esa_income_reported: 3_000 + adult_child: + age: 22 + lifetime_isa_balance: 10_000 + benunits: + parent: + members: parent + adult_child: + members: adult_child + households: + household: + members: [parent, adult_child] + savings: 2_000 + output: + esa_income_assessable_capital: [2_000, 7_500] + +- name: Grandparents' Pension Credit does not count a grandchild's Lifetime ISA + period: 2025 + absolute_error_margin: 0 + input: + people: + grandparent_1: + age: 70 + grandparent_2: + age: 68 + grandchild: + age: 25 + lifetime_isa_balance: 10_000 + benunits: + grandparents: + members: [grandparent_1, grandparent_2] + grandchild: + members: grandchild + households: + household: + members: [grandparent_1, grandparent_2, grandchild] + savings: 4_000 + output: + pension_credit_assessable_capital: [4_000, 0] + +- name: Pension Credit counts a younger partner's Lifetime ISA after the charge + period: 2025 + absolute_error_margin: 0 + input: + people: + claimant: + age: 70 + partner: + age: 45 + lifetime_isa_balance: 10_000 + benunits: + couple: + members: [claimant, partner] + households: + household: + members: [claimant, partner] + output: + pension_credit_assessable_capital: 7_500 diff --git a/policyengine_uk/tests/test_lifetime_isa_capital.py b/policyengine_uk/tests/test_lifetime_isa_capital.py new file mode 100644 index 0000000000..c06f16af6d --- /dev/null +++ b/policyengine_uk/tests/test_lifetime_isa_capital.py @@ -0,0 +1,86 @@ +import pandas as pd +import pytest + +from policyengine_uk import Simulation +from policyengine_uk.data.dataset_schema import UKSingleYearDataset +from policyengine_uk.data.economic_assumptions import extend_single_year_dataset +from policyengine_uk.system import system + +LIFETIME_ISA_HOLDER = { + "people": { + "person": { + "age": {"2025": 30}, + "lifetime_isa_balance": {"2025": 10_000}, + } + }, + "benunits": {"benunit": {"members": ["person"]}}, + "households": {"household": {"members": ["person"], "savings": {"2025": 1_000}}}, +} + + +def test_lifetime_isa_balances_uprate_with_the_other_financial_stocks(): + # Datasets store the person balances and their household sum; both must + # move with savings, or the stored household total drifts from its parts. + dataset = UKSingleYearDataset( + person=pd.DataFrame( + { + "person_id": [1, 2], + "person_benunit_id": [1, 1], + "person_household_id": [1, 1], + "age": [30, 28], + "lifetime_isa_balance": [4_000.0, 6_000.0], + } + ), + benunit=pd.DataFrame({"benunit_id": [1]}), + household=pd.DataFrame( + { + "household_id": [1], + "region": ["LONDON"], + "tenure_type": ["RENT_PRIVATELY"], + "council_tax": [1_500.0], + "rent": [12_000.0], + "household_weight": [1.0], + "savings": [1_000.0], + "household_lifetime_isa_balance": [10_000.0], + } + ), + fiscal_year=2025, + ) + + extended = extend_single_year_dataset( + dataset, + tax_benefit_system_parameters=system.parameters, + end_year=2027, + ) + + for year in (2026, 2027): + growth = extended[year].household["savings"].iloc[0] / 1_000.0 + assert growth != pytest.approx(1.0) + person = extended[year].person["lifetime_isa_balance"] + household = extended[year].household["household_lifetime_isa_balance"] + assert list(person) == pytest.approx([4_000.0 * growth, 6_000.0 * growth]) + assert household.iloc[0] == pytest.approx(person.sum()) + + +def test_emptying_a_person_sources_list_disregards_the_lifetime_isa_there_only(): + # The Treasury Committee's proposal to disregard Lifetime ISAs in Universal + # Credit is a change to one list; the other means tests keep counting them. + reform = { + "gov.dwp.universal_credit.means_test.capital.person_sources": { + "2025-01-01.2100-12-31": [] + } + } + baseline = Simulation(situation=LIFETIME_ISA_HOLDER) + reformed = Simulation(situation=LIFETIME_ISA_HOLDER, reform=reform) + + assert baseline.calculate("uc_assessable_capital", 2025)[0] == 8_500 + assert reformed.calculate("uc_assessable_capital", 2025)[0] == 1_000 + assert reformed.calculate("housing_benefit_assessable_capital", 2025)[0] == 8_500 + + +def test_lifetime_isa_capital_is_not_prorated_monthly(): + simulation = Simulation(situation=LIFETIME_ISA_HOLDER) + + assert simulation.calculate("lifetime_isa_balance", "2025-06")[0] == 10_000 + assert simulation.calculate("lifetime_isa_countable_capital", "2025-06")[0] == 7_500 + assert simulation.calculate("uc_assessable_capital", "2025-06")[0] == 8_500 diff --git a/policyengine_uk/variables/gov/dwp/esa_income_assessable_capital.py b/policyengine_uk/variables/gov/dwp/esa_income_assessable_capital.py index 473bcdfebb..56401fcedc 100644 --- a/policyengine_uk/variables/gov/dwp/esa_income_assessable_capital.py +++ b/policyengine_uk/variables/gov/dwp/esa_income_assessable_capital.py @@ -11,7 +11,9 @@ class esa_income_assessable_capital(Variable): "household level, the model allocates full household capital to any " "benunit with a reported income-related ESA award and only falls back to " "an adult-share proxy when nobody in the household is on that reported " - "claim path." + "claim path. " + "Person-level sources, such as a Lifetime ISA, count only in the " + "holder's own benunit." ) definition_period = YEAR unit = GBP @@ -38,4 +40,10 @@ def formula(benunit, period, parameters): fallback_divisor = max_(1, household_adults) claiming_proxy = where(claiming_esa_income, household_capital, 0) fallback_proxy = household_capital * benunit_adults / fallback_divisor - return where(household_reporting_claimants > 0, claiming_proxy, fallback_proxy) + person_capital = sum( + benunit.sum(person(source, period)) for source in ESA.capital.person_sources + ) + household_share = where( + household_reporting_claimants > 0, claiming_proxy, fallback_proxy + ) + return household_share + person_capital diff --git a/policyengine_uk/variables/gov/dwp/housing_benefit/housing_benefit_assessable_capital.py b/policyengine_uk/variables/gov/dwp/housing_benefit/housing_benefit_assessable_capital.py index c978c5f699..91ff4b64d9 100644 --- a/policyengine_uk/variables/gov/dwp/housing_benefit/housing_benefit_assessable_capital.py +++ b/policyengine_uk/variables/gov/dwp/housing_benefit/housing_benefit_assessable_capital.py @@ -6,8 +6,10 @@ class housing_benefit_assessable_capital(Variable): entity = BenUnit label = "Housing Benefit assessable capital" documentation = ( - "Housing Benefit capital counted from the configured capital sources, " - "allocated across benunits using a household adult-share proxy." + "Housing Benefit capital counted from the configured capital sources. " + "Household sources are allocated across benunits using a household " + "adult-share proxy; person-level sources, such as a Lifetime ISA, " + "count only in the holder's own benunit." ) definition_period = YEAR unit = GBP @@ -19,6 +21,9 @@ def formula(benunit, period, parameters): any_over_SP_age = benunit.any(person("is_SP_age", period)) p = parameters(period).gov.dwp.housing_benefit.means_test.capital household_capital = sum(household(source, period) for source in p.sources) + person_capital = sum( + benunit.sum(person(source, period)) for source in p.person_sources + ) benunit_adults = add(benunit, period, ["is_adult"]) household_adults = benunit.max( person.household.sum(person.household.members("is_adult", period)) @@ -30,4 +35,8 @@ def formula(benunit, period, parameters): 0, ) guarantee_credit = any_over_SP_age & (benunit("guarantee_credit", period) > 0) - return where(guarantee_credit, 0, max_(0, household_capital_proxy)) + return where( + guarantee_credit, + 0, + max_(0, household_capital_proxy + person_capital), + ) diff --git a/policyengine_uk/variables/gov/dwp/income_support_assessable_capital.py b/policyengine_uk/variables/gov/dwp/income_support_assessable_capital.py index e497c670f8..c463b8386e 100644 --- a/policyengine_uk/variables/gov/dwp/income_support_assessable_capital.py +++ b/policyengine_uk/variables/gov/dwp/income_support_assessable_capital.py @@ -10,7 +10,9 @@ class income_support_assessable_capital(Variable): "capital test. Because the dataset only stores these stocks at household " "level, the model allocates full household capital to any benunit on the " "IS claim path and only falls back to an adult-share proxy when nobody in " - "the household is on that path." + "the household is on that path. " + "Person-level sources, such as a Lifetime ISA, count only in the " + "holder's own benunit." ) definition_period = YEAR unit = GBP @@ -42,4 +44,11 @@ def formula(benunit, period, parameters): fallback_divisor = max_(1, household_adults) claiming_proxy = where(would_claim_is, household_capital, 0) fallback_proxy = household_capital * benunit_adults / fallback_divisor - return where(household_claiming_adults > 0, claiming_proxy, fallback_proxy) + person_capital = sum( + benunit.sum(person(source, period)) + for source in IS.means_test.capital.person_sources + ) + household_share = where( + household_claiming_adults > 0, claiming_proxy, fallback_proxy + ) + return household_share + person_capital diff --git a/policyengine_uk/variables/gov/dwp/jsa_income_assessable_capital.py b/policyengine_uk/variables/gov/dwp/jsa_income_assessable_capital.py index 2483cc9945..98cdc322ea 100644 --- a/policyengine_uk/variables/gov/dwp/jsa_income_assessable_capital.py +++ b/policyengine_uk/variables/gov/dwp/jsa_income_assessable_capital.py @@ -11,7 +11,9 @@ class jsa_income_assessable_capital(Variable): "household level, the model allocates full household capital to any " "benunit with a reported income-based JSA award and only falls back to " "an adult-share proxy when nobody in the household is on that reported " - "claim path." + "claim path. " + "Person-level sources, such as a Lifetime ISA, count only in the " + "holder's own benunit." ) definition_period = YEAR unit = GBP @@ -38,4 +40,10 @@ def formula(benunit, period, parameters): fallback_divisor = max_(1, household_adults) claiming_proxy = where(claiming_jsa_income, household_capital, 0) fallback_proxy = household_capital * benunit_adults / fallback_divisor - return where(household_reporting_claimants > 0, claiming_proxy, fallback_proxy) + person_capital = sum( + benunit.sum(person(source, period)) for source in JSA.capital.person_sources + ) + household_share = where( + household_reporting_claimants > 0, claiming_proxy, fallback_proxy + ) + return household_share + person_capital diff --git a/policyengine_uk/variables/gov/dwp/lifetime_isa_countable_capital.py b/policyengine_uk/variables/gov/dwp/lifetime_isa_countable_capital.py new file mode 100644 index 0000000000..6daf2817c3 --- /dev/null +++ b/policyengine_uk/variables/gov/dwp/lifetime_isa_countable_capital.py @@ -0,0 +1,30 @@ +from policyengine_uk.model_api import * + + +class lifetime_isa_countable_capital(Variable): + label = "Lifetime ISA value counted as means-tested capital" + documentation = ( + "Surrender value of the person's Lifetime ISAs: the balance less the " + "withdrawal charge below the charge-free age, and the whole balance " + "from that age. Every means test values capital at its current market " + "or surrender value, and DWP guidance counts a Lifetime ISA at 75% of " + "its value under 60 and in full from 60. Not modelled: the " + "charge-free withdrawals towards a first home or with a terminal " + "illness." + ) + entity = Person + definition_period = YEAR + value_type = float + unit = GBP + quantity_type = STOCK + reference = [ + "https://www.legislation.gov.uk/uksi/2013/376/regulation/49", + "https://assets.publishing.service.gov.uk/media/693ace15c72b0f8ccf33d609/adm-ch-H1.pdf#page=56", + "https://www.legislation.gov.uk/uksi/1998/1870/schedule", + ] + + def formula(person, period, parameters): + p = parameters(period).gov.hmrc.isa.lifetime + balance = person("lifetime_isa_balance", period) + charge_free = person("age", period) >= p.charge_free_age + return where(charge_free, balance, balance * (1 - p.withdrawal_charge)) diff --git a/policyengine_uk/variables/gov/dwp/pension_credit/pension_credit_assessable_capital.py b/policyengine_uk/variables/gov/dwp/pension_credit/pension_credit_assessable_capital.py index 9f4c1fde97..e9261034eb 100644 --- a/policyengine_uk/variables/gov/dwp/pension_credit/pension_credit_assessable_capital.py +++ b/policyengine_uk/variables/gov/dwp/pension_credit/pension_credit_assessable_capital.py @@ -9,7 +9,8 @@ class pension_credit_assessable_capital(Variable): "Pension Credit capital counted from the configured capital sources, " "split only across pension-age adults in the household so pensioner " "couples pool capital together without dilution by unrelated working-" - "age adults." + "age adults. Person-level sources, such as a Lifetime ISA, count " + "only in the holder's own benunit." ) definition_period = YEAR unit = GBP @@ -20,6 +21,9 @@ def formula(benunit, period, parameters): person = benunit.members p = parameters(period).gov.dwp.pension_credit.income.capital household_capital = sum(household(source, period) for source in p.sources) + person_capital = sum( + benunit.sum(person(source, period)) for source in p.person_sources + ) any_pension_age = benunit.any(person("is_SP_age", period)) benunit_pension_age_adults = benunit.sum(person("is_SP_age", period)) household_pension_age_adults = benunit.max( @@ -29,4 +33,8 @@ def formula(benunit, period, parameters): household_capital_proxy = ( household_capital * benunit_pension_age_adults / adult_divisor ) - return where(any_pension_age, max_(0, household_capital_proxy), 0) + return where( + any_pension_age, + max_(0, household_capital_proxy + person_capital), + 0, + ) diff --git a/policyengine_uk/variables/gov/dwp/universal_credit/uc_assessable_capital.py b/policyengine_uk/variables/gov/dwp/universal_credit/uc_assessable_capital.py index eef9c8f729..972b0638e0 100644 --- a/policyengine_uk/variables/gov/dwp/universal_credit/uc_assessable_capital.py +++ b/policyengine_uk/variables/gov/dwp/universal_credit/uc_assessable_capital.py @@ -7,7 +7,9 @@ class uc_assessable_capital(Variable): label = "Universal Credit assessable capital" documentation = ( "Universal Credit capital counted from the configured capital sources, " - "with benunit-reported overrides when available." + "with benunit-reported overrides when available. Household sources are " + "allocated by the household proxy; person-level sources, such as a " + "Lifetime ISA, count only in the holder's own benunit." ) definition_period = YEAR unit = GBP @@ -19,6 +21,10 @@ def formula(benunit, period, parameters): household_capital = sum( household(source, period) for source in p.capital.sources ) + person_capital = sum( + benunit.sum(benunit.members(source, period)) + for source in p.capital.person_sources + ) benunit_adults = add(benunit, period, ["is_adult"]) household_reported_capital = household("household_uc_reported_capital", period) household_unreported_adults = household( @@ -38,6 +44,6 @@ def formula(benunit, period, parameters): assessed_capital = where( use_reported_capital, reported_capital, - household_capital_proxy, + household_capital_proxy + person_capital, ) return max_(0, assessed_capital) diff --git a/policyengine_uk/variables/household/wealth/has_lifetime_isa.py b/policyengine_uk/variables/household/wealth/has_lifetime_isa.py new file mode 100644 index 0000000000..d0373dffec --- /dev/null +++ b/policyengine_uk/variables/household/wealth/has_lifetime_isa.py @@ -0,0 +1,12 @@ +from policyengine_uk.model_api import * + + +class has_lifetime_isa(Variable): + label = "holds a Lifetime ISA" + documentation = "Whether the person holds a Lifetime ISA with a positive balance." + entity = Person + definition_period = YEAR + value_type = bool + + def formula(person, period): + return person("lifetime_isa_balance", period) > 0 diff --git a/policyengine_uk/variables/household/wealth/household_lifetime_isa_balance.py b/policyengine_uk/variables/household/wealth/household_lifetime_isa_balance.py new file mode 100644 index 0000000000..932cf9abdb --- /dev/null +++ b/policyengine_uk/variables/household/wealth/household_lifetime_isa_balance.py @@ -0,0 +1,16 @@ +from policyengine_uk.model_api import * + + +class household_lifetime_isa_balance(Variable): + label = "household Lifetime ISA balance" + documentation = ( + "Lifetime ISA balances summed over the household's members. Like " + "lifetime_isa_balance, it is a component of gross_financial_wealth and " + "must never be added to it." + ) + entity = Household + definition_period = YEAR + value_type = float + unit = GBP + quantity_type = STOCK + adds = ["lifetime_isa_balance"] diff --git a/policyengine_uk/variables/input/lifetime_isa_balance.py b/policyengine_uk/variables/input/lifetime_isa_balance.py new file mode 100644 index 0000000000..c18525a546 --- /dev/null +++ b/policyengine_uk/variables/input/lifetime_isa_balance.py @@ -0,0 +1,23 @@ +from policyengine_uk.model_api import * + + +class lifetime_isa_balance(Variable): + label = "Lifetime ISA balance" + documentation = ( + "Current value of the person's Lifetime ISAs, cash and stocks and " + "shares together. The Wealth and Assets Survey counts every ISA, " + "Lifetime ISAs included, inside household gross financial wealth, so " + "this is already a component of gross_financial_wealth and " + "net_financial_wealth and must never be added to them. It is not part " + "of savings (savings accounts) or corporate_wealth (shares, including " + "stocks and shares ISAs), and total_wealth does not include it. Means " + "tests count it through lifetime_isa_countable_capital, at the value " + "left after the withdrawal charge." + ) + entity = Person + definition_period = YEAR + value_type = float + unit = GBP + uprating = "gov.economic_assumptions.indices.obr.per_capita.gdp" + quantity_type = STOCK + default_value = 0 From 0028ec5be28db67562d9620b09ee6ba2927f18bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mar=C3=ADa=20Juaristi?= <127882282+juaristi22@users.noreply.github.com> Date: Thu, 1 Oct 2026 12:32:40 +0100 Subject: [PATCH 2/3] Count person-level capital for the claimant and partner, from April 2017 Review round 1, items 1 and 2. The six person_sources sums ran over every benefit-unit member, so a dependant aged 18 or 19 in non-advanced education, the ages that can hold a Lifetime ISA, had theirs counted against the parent's claim. Only the claimant's and partner's capital counts (HB reg. 45; IS reg. 47 and JSA reg. 109 as made; ESA reg. 83; WRA 2012 s. 5; SPCA 2002 s. 5), so weight the sums by is_uc_claimant, the claimant-or-partner flag datasets set from recorded relationships. Parameter lookups before the first date return the first value, so the lists also counted the Lifetime ISA in 2015 and 2016. Each list is now empty from its regime's start until 6 April 2017. Co-Authored-By: Claude Opus 5.5 --- changelog.d/1981.added.md | 2 +- .../ESA/income/capital/person_sources.yaml | 16 ++-- .../JSA/income/capital/person_sources.yaml | 17 ++-- .../means_test/capital/person_sources.yaml | 20 +++-- .../means_test/capital/person_sources.yaml | 19 ++-- .../income/capital/person_sources.yaml | 19 ++-- .../means_test/capital/person_sources.yaml | 18 ++-- .../gov/dwp/lifetime_isa_capital.yaml | 90 +++++++++++++++++++ .../gov/dwp/esa_income_assessable_capital.py | 9 +- .../housing_benefit_assessable_capital.py | 8 +- .../dwp/income_support_assessable_capital.py | 8 +- .../gov/dwp/jsa_income_assessable_capital.py | 9 +- .../pension_credit_assessable_capital.py | 8 +- .../universal_credit/uc_assessable_capital.py | 7 +- 14 files changed, 199 insertions(+), 51 deletions(-) diff --git a/changelog.d/1981.added.md b/changelog.d/1981.added.md index d71b9d5e30..7ca46c6819 100644 --- a/changelog.d/1981.added.md +++ b/changelog.d/1981.added.md @@ -1 +1 @@ -- Added the Lifetime ISA holdings that microcosm datasets carry (`lifetime_isa_balance`, `has_lifetime_isa`, `household_lifetime_isa_balance`) and the Lifetime ISA withdrawal-charge parameters. Universal Credit, Housing Benefit, Income Support, income-based JSA, income-related ESA and Pension Credit now count a Lifetime ISA at its surrender value (75% of the balance under 60, the whole balance from 60) as capital of the holder's own benefit unit. Datasets without the columns are unaffected. +- Added the Lifetime ISA holdings that microcosm datasets carry (`lifetime_isa_balance`, `has_lifetime_isa`, `household_lifetime_isa_balance`) and the Lifetime ISA withdrawal-charge parameters. Universal Credit, Housing Benefit, Income Support, income-based JSA, income-related ESA and Pension Credit now count a Lifetime ISA at its surrender value (75% of the balance under 60, the whole balance from 60) as capital of the holder's own benefit unit when the holder is its claimant or partner, and not before the Lifetime ISA existed (6 April 2017). Datasets without the columns are unaffected. diff --git a/policyengine_uk/parameters/gov/dwp/ESA/income/capital/person_sources.yaml b/policyengine_uk/parameters/gov/dwp/ESA/income/capital/person_sources.yaml index c54fe2cd82..ca96d72a24 100644 --- a/policyengine_uk/parameters/gov/dwp/ESA/income/capital/person_sources.yaml +++ b/policyengine_uk/parameters/gov/dwp/ESA/income/capital/person_sources.yaml @@ -1,20 +1,26 @@ description: > Person-level model variables counted as income-related Employment and Support Allowance capital of the person's own benefit unit. They are summed - over the benefit unit's members, so they need none of the household proxy - that the household `sources` use. `lifetime_isa_countable_capital` is a - Lifetime ISA at its surrender value: regulation 113 values capital at its - current market or surrender value, and DMG 52665 counts a Lifetime ISA at - 75% of its value under 60 and in full from 60. + over the benefit unit's claimant and partner (`is_uc_claimant`), so they + need none of the household proxy that the household `sources` use, and a + dependant's holding is left out: regulation 83 adds only the partner's + capital to the claimant's. `lifetime_isa_countable_capital` is a Lifetime + ISA at its surrender value: regulation 113 values capital at its current + market or surrender value, and DMG 52665 counts a Lifetime ISA at 75% of its + value under 60 and in full from 60. Lifetime ISAs exist from 6 April 2017, + and the list is empty before then. metadata: label: Income-related ESA person-level capital sources period: year unit: list reference: + - title: The Employment and Support Allowance Regulations 2008 reg. 83 + href: https://www.legislation.gov.uk/uksi/2008/794/regulation/83 - title: The Employment and Support Allowance Regulations 2008 reg. 113 href: https://www.legislation.gov.uk/uksi/2008/794/regulation/113 - title: Decision Makers' Guide, Chapter 52 (Capital), para. 52665 href: https://assets.publishing.service.gov.uk/media/69d8f30ad3e08b8871b665ac/dmg-Ch-52.pdf values: + 2008-10-27: [] 2017-04-06: - lifetime_isa_countable_capital diff --git a/policyengine_uk/parameters/gov/dwp/JSA/income/capital/person_sources.yaml b/policyengine_uk/parameters/gov/dwp/JSA/income/capital/person_sources.yaml index 75a9698a91..4df9c7ea98 100644 --- a/policyengine_uk/parameters/gov/dwp/JSA/income/capital/person_sources.yaml +++ b/policyengine_uk/parameters/gov/dwp/JSA/income/capital/person_sources.yaml @@ -1,20 +1,27 @@ description: > Person-level model variables counted as income-based Jobseeker's Allowance capital of the person's own benefit unit. They are summed over the benefit - unit's members, so they need none of the household proxy that the household - `sources` use. `lifetime_isa_countable_capital` is a Lifetime ISA at its - surrender value: regulation 111 values capital at its current market or - surrender value, and DMG 29665 counts a Lifetime ISA at 75% of its value - under 60 and in full from 60. + unit's claimant and partner (`is_uc_claimant`), so they need none of the + household proxy that the household `sources` use, and a dependant's holding + is left out: regulation 109 kept a child's or young person's capital out of + the claimant's, and since children moved to child tax credit they are + outside the assessment altogether. `lifetime_isa_countable_capital` is a + Lifetime ISA at its surrender value: regulation 111 values capital at its + current market or surrender value, and DMG 29665 counts a Lifetime ISA at + 75% of its value under 60 and in full from 60. Lifetime ISAs exist from 6 + April 2017, and the list is empty before then. metadata: label: Income-based JSA person-level capital sources period: year unit: list reference: + - title: The Jobseeker's Allowance Regulations 1996 reg. 109 + href: https://www.legislation.gov.uk/uksi/1996/207/regulation/109 - title: The Jobseeker's Allowance Regulations 1996 reg. 111 href: https://www.legislation.gov.uk/uksi/1996/207/regulation/111 - title: Decision Makers' Guide, Chapter 29 (Capital), para. 29665 href: https://assets.publishing.service.gov.uk/media/6894a69d3080e72710b2e237/dmg-ch-29.pdf values: + 1996-10-07: [] 2017-04-06: - lifetime_isa_countable_capital diff --git a/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/capital/person_sources.yaml b/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/capital/person_sources.yaml index e5013ab2dc..b268261563 100644 --- a/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/capital/person_sources.yaml +++ b/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/capital/person_sources.yaml @@ -1,17 +1,22 @@ description: > Person-level model variables counted as Housing Benefit capital of the - person's own benefit unit. They are summed over the benefit unit's members, - so they need none of the household proxy that the household `sources` use. - `lifetime_isa_countable_capital` is a Lifetime ISA at its surrender value: - regulation 47 (regulation 45 of the pension-age regulations) values capital - at its current market or surrender value, in the words of the DWP benefits' - regulations, for which DWP guidance counts a Lifetime ISA at 75% of its - value under 60 and in full from 60. + person's own benefit unit. They are summed over the benefit unit's claimant + and partner (`is_uc_claimant`), so they need none of the household proxy + that the household `sources` use, and a dependant's holding is left out: + regulation 45 says the capital of a child or young person in the claimant's + family is not the claimant's. `lifetime_isa_countable_capital` is a + Lifetime ISA at its surrender value: regulation 47 (regulation 45 of the + pension-age regulations) values capital at its current market or surrender + value, in the words of the DWP benefits' regulations, for which DWP guidance + counts a Lifetime ISA at 75% of its value under 60 and in full from 60. + Lifetime ISAs exist from 6 April 2017, and the list is empty before then. metadata: label: Housing Benefit person-level capital sources period: year unit: list reference: + - title: The Housing Benefit Regulations 2006 reg. 45 + href: https://www.legislation.gov.uk/uksi/2006/213/regulation/45 - title: The Housing Benefit Regulations 2006 reg. 47 href: https://www.legislation.gov.uk/uksi/2006/213/regulation/47 - title: The Housing Benefit (Persons who have attained the qualifying age for state pension credit) Regulations 2006 reg. 45 @@ -19,5 +24,6 @@ metadata: - title: Decision Makers' Guide, Chapter 29 (Capital), para. 29665 (Income Support and JSA, whose valuation rule has the same words) href: https://assets.publishing.service.gov.uk/media/6894a69d3080e72710b2e237/dmg-ch-29.pdf values: + 2006-04-10: [] 2017-04-06: - lifetime_isa_countable_capital diff --git a/policyengine_uk/parameters/gov/dwp/income_support/means_test/capital/person_sources.yaml b/policyengine_uk/parameters/gov/dwp/income_support/means_test/capital/person_sources.yaml index aeb69780c8..d18f864b9b 100644 --- a/policyengine_uk/parameters/gov/dwp/income_support/means_test/capital/person_sources.yaml +++ b/policyengine_uk/parameters/gov/dwp/income_support/means_test/capital/person_sources.yaml @@ -1,20 +1,27 @@ description: > Person-level model variables counted as Income Support capital of the - person's own benefit unit. They are summed over the benefit unit's members, - so they need none of the household proxy that the household `sources` use. - `lifetime_isa_countable_capital` is a Lifetime ISA at its surrender value: - regulation 49 values capital at its current market or surrender value, and - DMG 29665 counts a Lifetime ISA at 75% of its value under 60 and in full - from 60. + person's own benefit unit. They are summed over the benefit unit's claimant + and partner (`is_uc_claimant`), so they need none of the household proxy + that the household `sources` use, and a dependant's holding is left out: + regulation 47 kept a child's or young person's capital out of the + claimant's, and since children moved to child tax credit they are outside + the assessment altogether. `lifetime_isa_countable_capital` is a Lifetime + ISA at its surrender value: regulation 49 values capital at its current + market or surrender value, and DMG 29665 counts a Lifetime ISA at 75% of its + value under 60 and in full from 60. Lifetime ISAs exist from 6 April 2017, + and the list is empty before then. metadata: label: Income Support person-level capital sources period: year unit: list reference: + - title: The Income Support (General) Regulations 1987 reg. 47 + href: https://www.legislation.gov.uk/uksi/1987/1967/regulation/47 - title: The Income Support (General) Regulations 1987 reg. 49 href: https://www.legislation.gov.uk/uksi/1987/1967/regulation/49 - title: Decision Makers' Guide, Chapter 29 (Capital), para. 29665 href: https://assets.publishing.service.gov.uk/media/6894a69d3080e72710b2e237/dmg-ch-29.pdf values: + 2010-01-01: [] 2017-04-06: - lifetime_isa_countable_capital diff --git a/policyengine_uk/parameters/gov/dwp/pension_credit/income/capital/person_sources.yaml b/policyengine_uk/parameters/gov/dwp/pension_credit/income/capital/person_sources.yaml index 498df874f7..56fd084bd9 100644 --- a/policyengine_uk/parameters/gov/dwp/pension_credit/income/capital/person_sources.yaml +++ b/policyengine_uk/parameters/gov/dwp/pension_credit/income/capital/person_sources.yaml @@ -1,20 +1,27 @@ description: > Person-level model variables counted as Pension Credit capital of the - person's own benefit unit. They are summed over the benefit unit's members, - so they need none of the household proxy that the household `sources` use. - `lifetime_isa_countable_capital` is a Lifetime ISA at its surrender value: - regulation 19 values capital at its current market or surrender value, and - DMG 84751 counts a Lifetime ISA at 75% of its value under 60 and in full - from 60, so a claimant's own Lifetime ISA counts in full. + person's own benefit unit. They are summed over the benefit unit's claimant + and partner (`is_uc_claimant`), so they need none of the household proxy + that the household `sources` use, and a dependant's holding is left out: + section 5 of the State Pension Credit Act 2002 adds only the partner's + capital to the claimant's. `lifetime_isa_countable_capital` is a Lifetime + ISA at its surrender value: regulation 19 values capital at its current + market or surrender value, and DMG 84751 counts a Lifetime ISA at 75% of its + value under 60 and in full from 60, so a claimant's own Lifetime ISA counts + in full. Lifetime ISAs exist from 6 April 2017, and the list is empty before + then. metadata: label: Pension Credit person-level capital sources period: year unit: list reference: + - title: State Pension Credit Act 2002 s. 5 + href: https://www.legislation.gov.uk/ukpga/2002/16/section/5 - title: The State Pension Credit Regulations 2002 reg. 19 href: https://www.legislation.gov.uk/uksi/2002/1792/regulation/19 - title: Decision Makers' Guide, Chapter 84 (Deemed weekly income from capital), para. 84751 href: https://assets.publishing.service.gov.uk/media/69d8f133daa863aa5db665a5/dmg-Ch-84.pdf values: + 2003-10-06: [] 2017-04-06: - lifetime_isa_countable_capital diff --git a/policyengine_uk/parameters/gov/dwp/universal_credit/means_test/capital/person_sources.yaml b/policyengine_uk/parameters/gov/dwp/universal_credit/means_test/capital/person_sources.yaml index de1cc31205..36baf598bb 100644 --- a/policyengine_uk/parameters/gov/dwp/universal_credit/means_test/capital/person_sources.yaml +++ b/policyengine_uk/parameters/gov/dwp/universal_credit/means_test/capital/person_sources.yaml @@ -1,20 +1,26 @@ description: > Person-level model variables counted as Universal Credit capital of the - person's own benefit unit. They are summed over the benefit unit's members, - so they need none of the household proxy that the household `sources` use. - `lifetime_isa_countable_capital` is a Lifetime ISA at its surrender value: - regulation 49(1) values capital at its current market or surrender value, - and ADM H1659 counts a Lifetime ISA at 75% of its value under 60 and in - full from 60. + person's own benefit unit. They are summed over the benefit unit's claimant + and partner (`is_uc_claimant`), so they need none of the household proxy + that the household `sources` use, and a dependant's holding is left out: + section 5 of the Welfare Reform Act 2012 tests the claimant's, or the joint + claimants' combined, capital. `lifetime_isa_countable_capital` is a + Lifetime ISA at its surrender value: regulation 49(1) values capital at its + current market or surrender value, and ADM H1659 counts a Lifetime ISA at + 75% of its value under 60 and in full from 60. Lifetime ISAs exist from 6 + April 2017, and the list is empty before then. metadata: label: Universal Credit person-level capital sources period: year unit: list reference: + - title: Welfare Reform Act 2012 s. 5 + href: https://www.legislation.gov.uk/ukpga/2012/5/section/5 - title: The Universal Credit Regulations 2013 reg. 49(1) href: https://www.legislation.gov.uk/uksi/2013/376/regulation/49/1 - title: Advice for Decision Making, Chapter H1 (Capital), para. H1659 href: https://assets.publishing.service.gov.uk/media/693ace15c72b0f8ccf33d609/adm-ch-H1.pdf values: + 2013-04-29: [] 2017-04-06: - lifetime_isa_countable_capital diff --git a/policyengine_uk/tests/policy/baseline/gov/dwp/lifetime_isa_capital.yaml b/policyengine_uk/tests/policy/baseline/gov/dwp/lifetime_isa_capital.yaml index 5d1685cc7d..259462514e 100644 --- a/policyengine_uk/tests/policy/baseline/gov/dwp/lifetime_isa_capital.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/dwp/lifetime_isa_capital.yaml @@ -354,3 +354,93 @@ members: [claimant, partner] output: pension_credit_assessable_capital: 7_500 + +- name: A dependent young person's Lifetime ISA does not count against the parent's claim + period: 2025 + absolute_error_margin: 0 + input: + people: + parent: + age: 45 + is_parent: true + young_person: + # A qualifying young person: 18 and in non-advanced education. + age: 18 + current_education: POST_SECONDARY + lifetime_isa_balance: 10_000 + benunits: + family: + members: [parent, young_person] + households: + household: + members: [parent, young_person] + output: + is_uc_claimant: [true, false] + uc_assessable_capital: 0 + housing_benefit_assessable_capital: 0 + income_support_assessable_capital: 0 + jsa_income_assessable_capital: 0 + esa_income_assessable_capital: 0 + +- name: A holder the dataset records as a dependant does not count + period: 2025 + absolute_error_margin: 0 + input: + people: + parent: + age: 50 + dependant: + age: 19 + is_uc_claimant: false + lifetime_isa_balance: 10_000 + benunits: + family: + members: [parent, dependant] + households: + household: + members: [parent, dependant] + output: + uc_assessable_capital: 0 + housing_benefit_assessable_capital: 0 + +- name: A pensioner's Pension Credit does not count a dependent young person's Lifetime ISA + period: 2025 + absolute_error_margin: 0 + input: + people: + pensioner: + age: 70 + is_parent: true + young_person: + age: 18 + current_education: POST_SECONDARY + lifetime_isa_balance: 10_000 + benunits: + family: + members: [pensioner, young_person] + households: + household: + members: [pensioner, young_person] + output: + pension_credit_assessable_capital: 0 + +- name: Before the Lifetime ISA existed the means tests count none + period: 2016 + absolute_error_margin: 0 + input: + people: + person: + age: 30 + lifetime_isa_balance: 10_000 + benunits: + benunit: + members: person + households: + household: + members: person + output: + uc_assessable_capital: 0 + housing_benefit_assessable_capital: 0 + income_support_assessable_capital: 0 + jsa_income_assessable_capital: 0 + esa_income_assessable_capital: 0 diff --git a/policyengine_uk/variables/gov/dwp/esa_income_assessable_capital.py b/policyengine_uk/variables/gov/dwp/esa_income_assessable_capital.py index 56401fcedc..89cd48f57d 100644 --- a/policyengine_uk/variables/gov/dwp/esa_income_assessable_capital.py +++ b/policyengine_uk/variables/gov/dwp/esa_income_assessable_capital.py @@ -12,8 +12,9 @@ class esa_income_assessable_capital(Variable): "benunit with a reported income-related ESA award and only falls back to " "an adult-share proxy when nobody in the household is on that reported " "claim path. " - "Person-level sources, such as a Lifetime ISA, count only in the " - "holder's own benunit." + "Person-level sources, such as a Lifetime ISA, count only for the " + "holder's own benunit, and only when the holder is its claimant or " + "partner (is_uc_claimant): a dependant's capital is not the claimant's." ) definition_period = YEAR unit = GBP @@ -40,8 +41,10 @@ def formula(benunit, period, parameters): fallback_divisor = max_(1, household_adults) claiming_proxy = where(claiming_esa_income, household_capital, 0) fallback_proxy = household_capital * benunit_adults / fallback_divisor + claimant_or_partner = person("is_uc_claimant", period) person_capital = sum( - benunit.sum(person(source, period)) for source in ESA.capital.person_sources + benunit.sum(person(source, period) * claimant_or_partner) + for source in ESA.capital.person_sources ) household_share = where( household_reporting_claimants > 0, claiming_proxy, fallback_proxy diff --git a/policyengine_uk/variables/gov/dwp/housing_benefit/housing_benefit_assessable_capital.py b/policyengine_uk/variables/gov/dwp/housing_benefit/housing_benefit_assessable_capital.py index 91ff4b64d9..ea49fbf511 100644 --- a/policyengine_uk/variables/gov/dwp/housing_benefit/housing_benefit_assessable_capital.py +++ b/policyengine_uk/variables/gov/dwp/housing_benefit/housing_benefit_assessable_capital.py @@ -9,7 +9,9 @@ class housing_benefit_assessable_capital(Variable): "Housing Benefit capital counted from the configured capital sources. " "Household sources are allocated across benunits using a household " "adult-share proxy; person-level sources, such as a Lifetime ISA, " - "count only in the holder's own benunit." + "count only for the holder's own benunit, and only when the holder is " + "its claimant or partner (is_uc_claimant): a dependant's capital is " + "not the claimant's." ) definition_period = YEAR unit = GBP @@ -21,8 +23,10 @@ def formula(benunit, period, parameters): any_over_SP_age = benunit.any(person("is_SP_age", period)) p = parameters(period).gov.dwp.housing_benefit.means_test.capital household_capital = sum(household(source, period) for source in p.sources) + claimant_or_partner = person("is_uc_claimant", period) person_capital = sum( - benunit.sum(person(source, period)) for source in p.person_sources + benunit.sum(person(source, period) * claimant_or_partner) + for source in p.person_sources ) benunit_adults = add(benunit, period, ["is_adult"]) household_adults = benunit.max( diff --git a/policyengine_uk/variables/gov/dwp/income_support_assessable_capital.py b/policyengine_uk/variables/gov/dwp/income_support_assessable_capital.py index c463b8386e..07905e8c0e 100644 --- a/policyengine_uk/variables/gov/dwp/income_support_assessable_capital.py +++ b/policyengine_uk/variables/gov/dwp/income_support_assessable_capital.py @@ -11,8 +11,9 @@ class income_support_assessable_capital(Variable): "level, the model allocates full household capital to any benunit on the " "IS claim path and only falls back to an adult-share proxy when nobody in " "the household is on that path. " - "Person-level sources, such as a Lifetime ISA, count only in the " - "holder's own benunit." + "Person-level sources, such as a Lifetime ISA, count only for the " + "holder's own benunit, and only when the holder is its claimant or " + "partner (is_uc_claimant): a dependant's capital is not the claimant's." ) definition_period = YEAR unit = GBP @@ -44,8 +45,9 @@ def formula(benunit, period, parameters): fallback_divisor = max_(1, household_adults) claiming_proxy = where(would_claim_is, household_capital, 0) fallback_proxy = household_capital * benunit_adults / fallback_divisor + claimant_or_partner = person("is_uc_claimant", period) person_capital = sum( - benunit.sum(person(source, period)) + benunit.sum(person(source, period) * claimant_or_partner) for source in IS.means_test.capital.person_sources ) household_share = where( diff --git a/policyengine_uk/variables/gov/dwp/jsa_income_assessable_capital.py b/policyengine_uk/variables/gov/dwp/jsa_income_assessable_capital.py index 98cdc322ea..3fa403e268 100644 --- a/policyengine_uk/variables/gov/dwp/jsa_income_assessable_capital.py +++ b/policyengine_uk/variables/gov/dwp/jsa_income_assessable_capital.py @@ -12,8 +12,9 @@ class jsa_income_assessable_capital(Variable): "benunit with a reported income-based JSA award and only falls back to " "an adult-share proxy when nobody in the household is on that reported " "claim path. " - "Person-level sources, such as a Lifetime ISA, count only in the " - "holder's own benunit." + "Person-level sources, such as a Lifetime ISA, count only for the " + "holder's own benunit, and only when the holder is its claimant or " + "partner (is_uc_claimant): a dependant's capital is not the claimant's." ) definition_period = YEAR unit = GBP @@ -40,8 +41,10 @@ def formula(benunit, period, parameters): fallback_divisor = max_(1, household_adults) claiming_proxy = where(claiming_jsa_income, household_capital, 0) fallback_proxy = household_capital * benunit_adults / fallback_divisor + claimant_or_partner = person("is_uc_claimant", period) person_capital = sum( - benunit.sum(person(source, period)) for source in JSA.capital.person_sources + benunit.sum(person(source, period) * claimant_or_partner) + for source in JSA.capital.person_sources ) household_share = where( household_reporting_claimants > 0, claiming_proxy, fallback_proxy diff --git a/policyengine_uk/variables/gov/dwp/pension_credit/pension_credit_assessable_capital.py b/policyengine_uk/variables/gov/dwp/pension_credit/pension_credit_assessable_capital.py index e9261034eb..6b3dfeed1c 100644 --- a/policyengine_uk/variables/gov/dwp/pension_credit/pension_credit_assessable_capital.py +++ b/policyengine_uk/variables/gov/dwp/pension_credit/pension_credit_assessable_capital.py @@ -10,7 +10,9 @@ class pension_credit_assessable_capital(Variable): "split only across pension-age adults in the household so pensioner " "couples pool capital together without dilution by unrelated working-" "age adults. Person-level sources, such as a Lifetime ISA, count " - "only in the holder's own benunit." + "only for the holder's own benunit, and only when the holder is its " + "claimant or partner (is_uc_claimant): a dependant's capital is not " + "the claimant's." ) definition_period = YEAR unit = GBP @@ -21,8 +23,10 @@ def formula(benunit, period, parameters): person = benunit.members p = parameters(period).gov.dwp.pension_credit.income.capital household_capital = sum(household(source, period) for source in p.sources) + claimant_or_partner = person("is_uc_claimant", period) person_capital = sum( - benunit.sum(person(source, period)) for source in p.person_sources + benunit.sum(person(source, period) * claimant_or_partner) + for source in p.person_sources ) any_pension_age = benunit.any(person("is_SP_age", period)) benunit_pension_age_adults = benunit.sum(person("is_SP_age", period)) diff --git a/policyengine_uk/variables/gov/dwp/universal_credit/uc_assessable_capital.py b/policyengine_uk/variables/gov/dwp/universal_credit/uc_assessable_capital.py index 972b0638e0..ebe2537056 100644 --- a/policyengine_uk/variables/gov/dwp/universal_credit/uc_assessable_capital.py +++ b/policyengine_uk/variables/gov/dwp/universal_credit/uc_assessable_capital.py @@ -9,7 +9,9 @@ class uc_assessable_capital(Variable): "Universal Credit capital counted from the configured capital sources, " "with benunit-reported overrides when available. Household sources are " "allocated by the household proxy; person-level sources, such as a " - "Lifetime ISA, count only in the holder's own benunit." + "Lifetime ISA, count only for the holder's own benunit, and only when " + "the holder is its claimant or partner (is_uc_claimant): a dependant's " + "capital is not the claimant's." ) definition_period = YEAR unit = GBP @@ -21,8 +23,9 @@ def formula(benunit, period, parameters): household_capital = sum( household(source, period) for source in p.capital.sources ) + claimant_or_partner = benunit.members("is_uc_claimant", period) person_capital = sum( - benunit.sum(benunit.members(source, period)) + benunit.sum(benunit.members(source, period) * claimant_or_partner) for source in p.capital.person_sources ) benunit_adults = add(benunit, period, ["is_adult"]) From b340cf53dda7e21442be1b48a5496e05599f0a1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mar=C3=ADa=20Juaristi?= <127882282+juaristi22@users.noreply.github.com> Date: Thu, 1 Oct 2026 12:32:40 +0100 Subject: [PATCH 3/3] Say a stored Lifetime ISA column is not recomputed Review round 1, item 3: a dataset that stores has_lifetime_isa or household_lifetime_isa_balance keeps the stored value when a reform or situation edits lifetime_isa_balance. Co-Authored-By: Claude Opus 5.5 --- .../variables/household/wealth/has_lifetime_isa.py | 7 ++++++- .../household/wealth/household_lifetime_isa_balance.py | 5 ++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/policyengine_uk/variables/household/wealth/has_lifetime_isa.py b/policyengine_uk/variables/household/wealth/has_lifetime_isa.py index d0373dffec..858b94f38f 100644 --- a/policyengine_uk/variables/household/wealth/has_lifetime_isa.py +++ b/policyengine_uk/variables/household/wealth/has_lifetime_isa.py @@ -3,7 +3,12 @@ class has_lifetime_isa(Variable): label = "holds a Lifetime ISA" - documentation = "Whether the person holds a Lifetime ISA with a positive balance." + documentation = ( + "Whether the person holds a Lifetime ISA with a positive balance. A " + "dataset that stores this column keeps the stored value: it is " + "recomputed only when not stored, so a reform or situation that edits " + "lifetime_isa_balance leaves it unchanged." + ) entity = Person definition_period = YEAR value_type = bool diff --git a/policyengine_uk/variables/household/wealth/household_lifetime_isa_balance.py b/policyengine_uk/variables/household/wealth/household_lifetime_isa_balance.py index 932cf9abdb..2ec53080cb 100644 --- a/policyengine_uk/variables/household/wealth/household_lifetime_isa_balance.py +++ b/policyengine_uk/variables/household/wealth/household_lifetime_isa_balance.py @@ -6,7 +6,10 @@ class household_lifetime_isa_balance(Variable): documentation = ( "Lifetime ISA balances summed over the household's members. Like " "lifetime_isa_balance, it is a component of gross_financial_wealth and " - "must never be added to it." + "must never be added to it. A dataset that stores this column keeps " + "the stored value: it is recomputed only when not stored, so a reform " + "or situation that edits lifetime_isa_balance leaves it unchanged. No " + "means test reads it." ) entity = Household definition_period = YEAR