From a7d4c023dccabc658c8496ee6ef419cc5364611e Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sun, 23 Aug 2026 12:17:11 +0200 Subject: [PATCH 1/2] Add private_pension_wealth as an explicit capital disregard The WAS wealth imputation folds private pension wealth (total private pension wealth less the current-employment defined benefit component) into corporate_wealth, and corporate_wealth is a capital source in every means test, so imputed pension pots fail benefit units against the GBP 16,000 Universal Credit limit: on enhanced_frs_2024_25 at 2025, 46% of UC-reporting benefit units fail the capital test, 96% of them on corporate_wealth alone (policyengine-uk-data#452). The value of a right to receive a pension under an occupational or personal pension scheme is disregarded capital (UC Regs 2013 Sch 10 para 10; HB Regs 2006 Sch 6 paras 31-32 and HB(SPC) Regs 2006 Sch 6 para 24; JSA Regs 1996 Sch 8 paras 28-29; ESA Regs 2008 Sch 9 paras 28-29; IS Regs 1987 Sch 10 paras 23-23A; SPC Regs 2002 Sch V paras 22-23). The data pipelines (microcosm#750; the uk-data mirror is held) will emit the pension component as private_pension_wealth and keep corporate_wealth to the share-like holdings. This makes the disregard explicit on the model side and keeps every other consumer whole: - private_pension_wealth: new Household STOCK input (per-capita-GDP uprating, default 0), documented as disregarded capital and absent from every capital sources list; pension income drawn still counts through private_pension_income. - corporate_wealth: documentation describes the share-like holdings the data carries (no private pensions, no corporate bonds) and names the separately exported stocks_and_shares_isa fold. - total_wealth adds private_pension_wealth, so wealth taxes and wealth deciles keep the pre-split base. - corporate_sector_wealth (corporate_wealth + private_pension_wealth) is the allocation key for shareholding, corporate_land_value and the employer NI capital response, so corporate-tax incidence and corporate land value (the LVT inputs) do not move when the data splits the column. On existing datasets every consumer is bit-identical (verified on enhanced_frs_2024_25 v1.56.16 at 2025 and 2029). - The six capital sources parameters cite the disregard paragraph; uprating_indices.yaml and growthfactors.md register the new stock. - Tests: YAML cases for each means test, the wealth aggregates, and a regression that corporate_land_value and shareholding are unchanged when wealth moves between the two components. Co-Authored-By: Claude Fable 5 --- .../uk-pension-wealth-disregard-452.added.md | 1 + docs/book/assumptions/growthfactors.md | 2 +- policyengine_uk/data/uprating_indices.yaml | 1 + .../gov/dwp/ESA/income/capital/sources.yaml | 7 ++- .../gov/dwp/JSA/income/capital/sources.yaml | 7 ++- .../means_test/capital/sources.yaml | 18 ++++--- .../means_test/capital/sources.yaml | 8 ++- .../income/capital/sources.yaml | 7 +++ .../means_test/capital/sources.yaml | 8 ++- .../family/esa_income/esa_income_capital.yaml | 20 ++++++++ .../housing_benefit_capital.yaml | 22 +++++++++ .../income_support_capital.yaml | 24 +++++++++ .../family/jsa_income/jsa_income_capital.yaml | 20 ++++++++ .../universal_credit/is_UC_eligible.yaml | 19 +++++++ .../pension_credit/pension_credit_income.yaml | 18 +++++++ .../wealth/private_pension_wealth.yaml | 39 +++++++++++++++ .../tests/test_corporate_land_value.py | 49 +++++++++++++++++++ .../employer_ni_response_capital_incidence.py | 3 +- .../household/wealth/corporate_land_value.py | 2 +- .../wealth/corporate_sector_wealth.py | 22 +++++++++ .../household/wealth/shareholding.py | 6 +-- .../household/wealth/total_wealth.py | 1 + .../variables/input/corporate_wealth.py | 10 +++- .../variables/input/private_pension_wealth.py | 33 +++++++++++++ 24 files changed, 330 insertions(+), 17 deletions(-) create mode 100644 changelog.d/uk-pension-wealth-disregard-452.added.md create mode 100644 policyengine_uk/tests/policy/baseline/household/wealth/private_pension_wealth.yaml create mode 100644 policyengine_uk/variables/household/wealth/corporate_sector_wealth.py create mode 100644 policyengine_uk/variables/input/private_pension_wealth.py diff --git a/changelog.d/uk-pension-wealth-disregard-452.added.md b/changelog.d/uk-pension-wealth-disregard-452.added.md new file mode 100644 index 0000000000..cf5a58c116 --- /dev/null +++ b/changelog.d/uk-pension-wealth-disregard-452.added.md @@ -0,0 +1 @@ +Added `private_pension_wealth` (Household, imputed from the Wealth and Assets Survey) as an explicit capital disregard: it is not a capital source in any means test, `corporate_wealth` no longer describes private pensions, `total_wealth` includes it, and a new `corporate_sector_wealth` (corporate plus private pension wealth) is the allocation key for shareholding, corporate land value and the employer NI capital response so those keys do not move when the data splits pension wealth out of corporate wealth (policyengine-uk-data#452). diff --git a/docs/book/assumptions/growthfactors.md b/docs/book/assumptions/growthfactors.md index 746f3a1daa..6a0441da78 100644 --- a/docs/book/assumptions/growthfactors.md +++ b/docs/book/assumptions/growthfactors.md @@ -57,7 +57,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_before_response`, `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_transfer_income`, `property_income`, `savings`, `savings_interest_income`, `sublet_income` +We use per capita GDP for these variables: `capital_gains`, `capital_gains_before_response`, `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` ## Council tax diff --git a/policyengine_uk/data/uprating_indices.yaml b/policyengine_uk/data/uprating_indices.yaml index b8a0ad7b96..b2c39d35cf 100644 --- a/policyengine_uk/data/uprating_indices.yaml +++ b/policyengine_uk/data/uprating_indices.yaml @@ -72,6 +72,7 @@ gov.economic_assumptions.yoy_growth.obr.per_capita.gdp: - other_residential_property_value - owned_land - pension_income +- private_pension_wealth - private_transfer_income - property_income - savings diff --git a/policyengine_uk/parameters/gov/dwp/ESA/income/capital/sources.yaml b/policyengine_uk/parameters/gov/dwp/ESA/income/capital/sources.yaml index 9d682d768e..3eb35b83ef 100644 --- a/policyengine_uk/parameters/gov/dwp/ESA/income/capital/sources.yaml +++ b/policyengine_uk/parameters/gov/dwp/ESA/income/capital/sources.yaml @@ -7,7 +7,10 @@ description: > financial assets, `other_residential_property_value` for residential property other than the home, and `non_residential_property_value` for other land and premises. `main_residence_value` is excluded because Schedule 9 - paragraph 1 disregards premises occupied as the claimant's home. Other + paragraph 1 disregards premises occupied as the claimant's home. + `private_pension_wealth` is excluded because Schedule 9 paragraphs 28 and 29 + disregard the value of the right to receive an occupational or personal + pension and the value of funds held under a personal pension scheme. Other Schedule 9 disregards and the higher residential-care capital disregard are not yet modeled because the current dataset does not isolate those states or asset holdings. Because the dataset only provides household-level capital @@ -24,6 +27,8 @@ metadata: href: https://www.legislation.gov.uk/uksi/2008/794/regulation/111 - title: Employment and Support Allowance Regulations 2008 Schedule 9 para. 1 href: https://www.legislation.gov.uk/uksi/2008/794/schedule/9/paragraph/1 + - title: Employment and Support Allowance Regulations 2008 Schedule 9 paras. 28-29 + href: https://www.legislation.gov.uk/uksi/2008/794/schedule/9/paragraph/28 values: 2008-10-27: - savings diff --git a/policyengine_uk/parameters/gov/dwp/JSA/income/capital/sources.yaml b/policyengine_uk/parameters/gov/dwp/JSA/income/capital/sources.yaml index 715862ca1e..6484484ab8 100644 --- a/policyengine_uk/parameters/gov/dwp/JSA/income/capital/sources.yaml +++ b/policyengine_uk/parameters/gov/dwp/JSA/income/capital/sources.yaml @@ -7,7 +7,10 @@ description: > financial assets, `other_residential_property_value` for residential property other than the home, and `non_residential_property_value` for other land and premises. `main_residence_value` is excluded because Schedule 8 - paragraph 1 disregards premises occupied as the claimant's home. Other + paragraph 1 disregards premises occupied as the claimant's home. + `private_pension_wealth` is excluded because Schedule 8 paragraphs 28 and 29 + disregard the value of the right to receive an occupational or personal + pension and the value of funds held under a personal pension scheme. Other Schedule 8 disregards, the higher residential-care capital disregard, and the separate child-capital disregard are not yet modeled because the current dataset does not isolate those states or asset holdings. Because the dataset @@ -25,6 +28,8 @@ metadata: href: https://www.legislation.gov.uk/uksi/1996/207/regulation/108 - title: Jobseeker's Allowance Regulations 1996 Schedule 8 para. 1 href: https://www.legislation.gov.uk/uksi/1996/207/schedule/8/paragraph/1 + - title: Jobseeker's Allowance Regulations 1996 Schedule 8 paras. 28-29 + href: https://www.legislation.gov.uk/uksi/1996/207/schedule/8/paragraph/28 values: 1996-10-07: - savings diff --git a/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/capital/sources.yaml b/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/capital/sources.yaml index cc94e50fe5..312f781c4e 100644 --- a/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/capital/sources.yaml +++ b/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/capital/sources.yaml @@ -8,12 +8,14 @@ description: > `other_residential_property_value` for residential property other than the home, and `non_residential_property_value` for other land and premises. `main_residence_value` is excluded because Schedule 6 paragraph 1 disregards - premises occupied as the claimant's home. The current dataset definition for - `corporate_wealth` also bundles private pension wealth together with those - countable assets; the model currently includes that mixed bucket rather than - omitting share-like financial wealth entirely, so pension-capital treatment - remains an unresolved data limitation. Other Schedule 6 disregards within - these broad asset classes are not yet split out separately. Because the + premises occupied as the claimant's home. `private_pension_wealth` is + excluded because Schedule 6 paragraphs 31 and 32 disregard the value of the + right to receive an occupational or personal pension and the value of funds + held under a personal pension scheme (paragraph 24 of Schedule 6 to the + pension-age regulations makes the same disregard). Datasets built before + private pension wealth was split out of `corporate_wealth` still carry it + inside that countable bucket. Other Schedule 6 disregards within these broad + asset classes are not yet split out separately. Because the dataset only provides household-level capital stocks, the model allocates them across benunits using an adult-share proxy. metadata: @@ -25,10 +27,14 @@ metadata: href: https://www.legislation.gov.uk/uksi/2006/213/regulation/44 - title: The Housing Benefit Regulations 2006 Schedule 6 para. 1 href: https://www.legislation.gov.uk/uksi/2006/213/schedule/6/paragraph/1 + - title: The Housing Benefit Regulations 2006 Schedule 6 paras. 31-32 + href: https://www.legislation.gov.uk/uksi/2006/213/schedule/6/paragraph/31 - title: The Housing Benefit (Persons who have attained the qualifying age for state pension credit) Regulations 2006 reg. 44(1) href: https://www.legislation.gov.uk/uksi/2006/214/regulation/44 - title: The Housing Benefit (Persons who have attained the qualifying age for state pension credit) Regulations 2006 Schedule 6 para. 1 href: https://www.legislation.gov.uk/uksi/2006/214/schedule/6/paragraph/1 + - title: The Housing Benefit (Persons who have attained the qualifying age for state pension credit) Regulations 2006 Schedule 6 para. 24 + href: https://www.legislation.gov.uk/uksi/2006/214/schedule/6/paragraph/24 values: 2006-04-10: - savings diff --git a/policyengine_uk/parameters/gov/dwp/income_support/means_test/capital/sources.yaml b/policyengine_uk/parameters/gov/dwp/income_support/means_test/capital/sources.yaml index b3055cfc3a..5b28814633 100644 --- a/policyengine_uk/parameters/gov/dwp/income_support/means_test/capital/sources.yaml +++ b/policyengine_uk/parameters/gov/dwp/income_support/means_test/capital/sources.yaml @@ -1,4 +1,8 @@ -description: Capital sources included in the Income Support capital test. +description: > + Capital sources included in the Income Support capital test. + `private_pension_wealth` is excluded because Schedule 10 paragraphs 23 and + 23A disregard the value of the right to receive an occupational or personal + pension and the value of funds held under a personal pension scheme. metadata: label: Income Support capital sources period: year @@ -10,6 +14,8 @@ metadata: href: https://www.legislation.gov.uk/uksi/1987/1967/data.pdf#page=70 - title: Income Support (General) Regulations 1987 Sch. 10 href: https://www.legislation.gov.uk/uksi/1987/1967/data.pdf#page=188 + - title: Income Support (General) Regulations 1987 Sch. 10 paras. 23-23A + href: https://www.legislation.gov.uk/uksi/1987/1967/schedule/10/paragraph/23 unit: list values: diff --git a/policyengine_uk/parameters/gov/dwp/pension_credit/income/capital/sources.yaml b/policyengine_uk/parameters/gov/dwp/pension_credit/income/capital/sources.yaml index cb82eed3c4..0761c1b0f3 100644 --- a/policyengine_uk/parameters/gov/dwp/pension_credit/income/capital/sources.yaml +++ b/policyengine_uk/parameters/gov/dwp/pension_credit/income/capital/sources.yaml @@ -1,6 +1,11 @@ description: > PolicyEngine approximation of Pension Credit capital sources that count toward deemed income from capital, excluding the claimant's main home. + `private_pension_wealth` is excluded because Schedule V paragraphs 22 and 23 + (Part I) disregard the value of the right to receive income from an + occupational or personal pension scheme or under a retirement annuity + contract; the notional income rule for undrawn money purchase pots + (regulation 18) is not modelled. metadata: label: Pension Credit capital sources unit: list @@ -9,6 +14,8 @@ metadata: title: State Pension Credit Regulations 2002 reg. 15 - href: https://www.legislation.gov.uk/uksi/2002/1792/schedule/5 title: State Pension Credit Regulations 2002 Sch. 5 + - href: https://www.legislation.gov.uk/uksi/2002/1792/schedule/V/paragraph/22 + title: State Pension Credit Regulations 2002 Sch. 5 paras. 22-23 - href: https://www.gov.uk/government/publications/pension-credit-technical-guidance/a-detailed-guide-to-pension-credit-for-advisers-and-others title: Pension Credit technical guidance propagate_metadata_to_children: true diff --git a/policyengine_uk/parameters/gov/dwp/universal_credit/means_test/capital/sources.yaml b/policyengine_uk/parameters/gov/dwp/universal_credit/means_test/capital/sources.yaml index c535250583..05d71203b0 100644 --- a/policyengine_uk/parameters/gov/dwp/universal_credit/means_test/capital/sources.yaml +++ b/policyengine_uk/parameters/gov/dwp/universal_credit/means_test/capital/sources.yaml @@ -7,7 +7,11 @@ description: > home, `non_residential_property_value` for other land and premises, and `corporate_wealth` for shares and similar investment assets. `main_residence_value` is excluded because Schedule 10 paragraph 1 disregards premises occupied as - the person's home. Other Schedule 10 disregards within these broad asset + the person's home. `private_pension_wealth` is excluded because Schedule 10 + paragraph 10 disregards the value of any right to receive a pension under an + occupational or personal pension scheme (or any other scheme registered under + section 153 of the Finance Act 2004); pension income actually drawn counts as + unearned income instead. Other Schedule 10 disregards within these broad asset classes are not yet split out separately. Where only household-level capital is available for a multi-benunit household, the model uses a household-wide adult-share proxy for residual capital because the dataset does not identify @@ -31,6 +35,8 @@ metadata: href: https://www.legislation.gov.uk/uksi/2013/376/regulation/48/1 - title: The Universal Credit Regulations 2013 Schedule 10 para. 1 href: https://www.legislation.gov.uk/uksi/2013/376/schedule/10/paragraph/1 + - title: The Universal Credit Regulations 2013 Schedule 10 para. 10 + href: https://www.legislation.gov.uk/uksi/2013/376/schedule/10/paragraph/10 values: 2013-04-29: - savings diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/esa_income/esa_income_capital.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/esa_income/esa_income_capital.yaml index 743653116f..bdbbef68e0 100644 --- a/policyengine_uk/tests/policy/baseline/finance/benefit/family/esa_income/esa_income_capital.yaml +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/esa_income/esa_income_capital.yaml @@ -191,3 +191,23 @@ esa_income: 0 income_support_eligible: true income_support: 5_252.0073 + +- name: Income-related ESA capital test disregards private pension wealth + period: 2026 + absolute_error_margin: 0 + input: + people: + claimant: + age: 40 + esa_income_reported: 1_000 + benunits: + benunit: + members: claimant + households: + household: + members: claimant + savings: 1_000 + corporate_wealth: 2_000 + private_pension_wealth: 150_000 + output: + esa_income_assessable_capital: 3_000 diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/housing_benefit_capital.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/housing_benefit_capital.yaml index 840f3245b9..e9a0cd47c3 100644 --- a/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/housing_benefit_capital.yaml +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/housing_benefit_capital.yaml @@ -247,3 +247,25 @@ housing_benefit_tariff_income: 0 housing_benefit_applicable_income: 0 housing_benefit_eligible: true + +- name: Private pension wealth is disregarded from Housing Benefit capital + period: 2025 + absolute_error_margin: 0 + input: + people: + person: + age: 30 + in_social_housing: true + housing_benefit_reported: 1 + benunits: + benunit: + members: person + would_claim_uc: false + households: + household: + members: person + savings: 5_000 + private_pension_wealth: 150_000 + output: + housing_benefit_assessable_capital: 5_000 + housing_benefit_eligible: true diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/income_support/income_support_capital.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/income_support/income_support_capital.yaml index 9a54ef6617..585884ea9c 100644 --- a/policyengine_uk/tests/policy/baseline/finance/benefit/family/income_support/income_support_capital.yaml +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/income_support/income_support_capital.yaml @@ -210,3 +210,27 @@ savings: 18_000 output: income_support_assessable_capital: [18_000, 18_000] + +- name: Income Support capital test disregards private pension wealth + period: 2021 + absolute_error_margin: 1 + input: + people: + p1: + age: 26 + income_support_reported: true + c1: + age: 4 + is_child: true + benunits: + b1: + members: [p1, c1] + would_claim_IS: true + households: + household: + members: [p1, c1] + savings: 1000 + corporate_wealth: 2000 + private_pension_wealth: 150000 + output: + income_support_assessable_capital: 3000 diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/jsa_income/jsa_income_capital.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/jsa_income/jsa_income_capital.yaml index d727362549..13ecc3d659 100644 --- a/policyengine_uk/tests/policy/baseline/finance/benefit/family/jsa_income/jsa_income_capital.yaml +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/jsa_income/jsa_income_capital.yaml @@ -165,3 +165,23 @@ savings: 18_000 output: jsa_income_assessable_capital: [18_000, 18_000] + +- name: Income-based JSA capital test disregards private pension wealth + period: 2026 + absolute_error_margin: 0 + input: + people: + claimant: + age: 30 + jsa_income_reported: 1_000 + benunits: + benunit: + members: claimant + households: + household: + members: claimant + savings: 1_000 + corporate_wealth: 2_000 + private_pension_wealth: 150_000 + output: + jsa_income_assessable_capital: 3_000 diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/is_UC_eligible.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/is_UC_eligible.yaml index 342cfc52b0..fa145e20c1 100644 --- a/policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/is_UC_eligible.yaml +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/is_UC_eligible.yaml @@ -315,3 +315,22 @@ output: uc_assessable_capital: 0 is_uc_eligible: true + +- name: Private pension wealth is disregarded from Universal Credit capital + period: 2025 + absolute_error_margin: 0 + input: + people: + person: + age: 30 + benunits: + benunit: + members: person + households: + household: + members: person + savings: 10_000 + private_pension_wealth: 150_000 + output: + uc_assessable_capital: 10_000 + is_uc_eligible: true diff --git a/policyengine_uk/tests/policy/baseline/gov/dwp/pension_credit/pension_credit_income.yaml b/policyengine_uk/tests/policy/baseline/gov/dwp/pension_credit/pension_credit_income.yaml index ac6d7c6667..b6cf5c9493 100644 --- a/policyengine_uk/tests/policy/baseline/gov/dwp/pension_credit/pension_credit_income.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/dwp/pension_credit/pension_credit_income.yaml @@ -105,3 +105,21 @@ standard_minimum_guarantee: 12_376 guarantee_credit: 3_016 pension_credit: 3_016 + +- name: Pension Credit capital disregards private pension wealth + period: 2026 + absolute_error_margin: 1 + input: + people: + person1: + age: 81 + benunits: + benunit: + members: [person1] + households: + household: + members: [person1] + savings: 12_000 + private_pension_wealth: 200_000 + output: + pension_credit_assessable_capital: 12_000 diff --git a/policyengine_uk/tests/policy/baseline/household/wealth/private_pension_wealth.yaml b/policyengine_uk/tests/policy/baseline/household/wealth/private_pension_wealth.yaml new file mode 100644 index 0000000000..6f97f961f5 --- /dev/null +++ b/policyengine_uk/tests/policy/baseline/household/wealth/private_pension_wealth.yaml @@ -0,0 +1,39 @@ +- name: Private pension wealth counts in total wealth and corporate sector wealth + period: 2025 + absolute_error_margin: 0 + input: + people: + person: + age: 40 + benunits: + benunit: + members: person + households: + household: + members: person + property_wealth: 200_000 + corporate_wealth: 30_000 + private_pension_wealth: 120_000 + savings: 10_000 + output: + corporate_sector_wealth: 150_000 + total_wealth: 360_000 + +- name: Private pension wealth defaults to zero so corporate sector wealth equals corporate wealth + period: 2025 + absolute_error_margin: 0 + input: + people: + person: + age: 40 + benunits: + benunit: + members: person + households: + household: + members: person + corporate_wealth: 30_000 + output: + private_pension_wealth: 0 + corporate_sector_wealth: 30_000 + total_wealth: 30_000 diff --git a/policyengine_uk/tests/test_corporate_land_value.py b/policyengine_uk/tests/test_corporate_land_value.py index e4124e9596..43986a18e8 100644 --- a/policyengine_uk/tests/test_corporate_land_value.py +++ b/policyengine_uk/tests/test_corporate_land_value.py @@ -63,3 +63,52 @@ def test_corporate_land_value_is_zero_without_corporate_wealth(): ) assert corporate_land_value[0] == 0 + + +def test_corporate_land_value_key_is_unchanged_by_the_pension_split(): + """Moving wealth between corporate_wealth and private_pension_wealth must not + move corporate land value: the allocation key is corporate_sector_wealth.""" + + def land_values(household_1: dict, household_2: dict): + sim = Simulation( + situation={ + "people": { + "person_1": {"age": {2025: 40}}, + "person_2": {"age": {2025: 50}}, + }, + "benunits": { + "benunit_1": {"members": ["person_1"]}, + "benunit_2": {"members": ["person_2"]}, + }, + "households": { + "household_1": { + "members": ["person_1"], + "household_weight": {2025: 2}, + **household_1, + }, + "household_2": { + "members": ["person_2"], + "household_weight": {2025: 1}, + **household_2, + }, + }, + } + ) + return ( + sim.calculate("corporate_land_value", map_to="household", period=2025), + sim.calculate("shareholding", map_to="household", period=2025), + ) + + folded_land, folded_share = land_values( + {"corporate_wealth": {2025: 100_000}}, + {"corporate_wealth": {2025: 300_000}}, + ) + split_land, split_share = land_values( + {"corporate_wealth": {2025: 20_000}, "private_pension_wealth": {2025: 80_000}}, + {"corporate_wealth": {2025: 50_000}, "private_pension_wealth": {2025: 250_000}}, + ) + + assert split_land[0] == pytest.approx(folded_land[0]) + assert split_land[1] == pytest.approx(folded_land[1]) + assert split_share[0] == pytest.approx(folded_share[0]) + assert split_share[1] == pytest.approx(folded_share[1]) diff --git a/policyengine_uk/variables/contrib/policyengine/employer_ni/employer_ni_response_capital_incidence.py b/policyengine_uk/variables/contrib/policyengine/employer_ni/employer_ni_response_capital_incidence.py index cd29a8de2c..d50987b399 100644 --- a/policyengine_uk/variables/contrib/policyengine/employer_ni/employer_ni_response_capital_incidence.py +++ b/policyengine_uk/variables/contrib/policyengine/employer_ni/employer_ni_response_capital_incidence.py @@ -25,7 +25,8 @@ def formula(person, period, parameters): amount_paid_by_employers = (person_weight * change_in_employer_cost).sum() wealth = ( - person.household("corporate_wealth", period) / person.household.nb_persons() + person.household("corporate_sector_wealth", period) + / person.household.nb_persons() ) total_wealth = (wealth * person_weight).sum() share_of_total_wealth = wealth / total_wealth diff --git a/policyengine_uk/variables/household/wealth/corporate_land_value.py b/policyengine_uk/variables/household/wealth/corporate_land_value.py index 29c2b2e6a2..8ada5e20d5 100644 --- a/policyengine_uk/variables/household/wealth/corporate_land_value.py +++ b/policyengine_uk/variables/household/wealth/corporate_land_value.py @@ -11,7 +11,7 @@ class corporate_land_value(Variable): quantity_type = STOCK def formula(household, period, parameters): - corporate_wealth = household("corporate_wealth", period) + corporate_wealth = household("corporate_sector_wealth", period) household_weight = household("household_weight", period) total_weighted_corporate_wealth = (corporate_wealth * household_weight).sum() if total_weighted_corporate_wealth == 0: diff --git a/policyengine_uk/variables/household/wealth/corporate_sector_wealth.py b/policyengine_uk/variables/household/wealth/corporate_sector_wealth.py new file mode 100644 index 0000000000..9ace66bbed --- /dev/null +++ b/policyengine_uk/variables/household/wealth/corporate_sector_wealth.py @@ -0,0 +1,22 @@ +from policyengine_uk.model_api import * + + +class corporate_sector_wealth(Variable): + label = "wealth exposed to the corporate sector" + documentation = ( + "Corporate wealth held directly or through investment funds plus private " + "pension wealth, which pension funds invest in the corporate sector. The " + "allocation key for corporate tax incidence (shareholding), corporate " + "land value and the employer NI capital response. Equals corporate_wealth " + "on datasets built before private pension wealth was split out of it." + ) + entity = Household + definition_period = YEAR + value_type = float + unit = GBP + quantity_type = STOCK + + adds = [ + "corporate_wealth", + "private_pension_wealth", + ] diff --git a/policyengine_uk/variables/household/wealth/shareholding.py b/policyengine_uk/variables/household/wealth/shareholding.py index 68a283eea5..dcf773f8c3 100644 --- a/policyengine_uk/variables/household/wealth/shareholding.py +++ b/policyengine_uk/variables/household/wealth/shareholding.py @@ -10,15 +10,15 @@ class shareholding(Variable): unit = GBP def formula(household, period, parameters): - wealth = household("corporate_wealth", period) + wealth = household("corporate_sector_wealth", period) if ( household.simulation.dataset is not None - and household("corporate_wealth", period).sum() != 0 + and household("corporate_sector_wealth", period).sum() != 0 ): weight = household("household_weight", period) return wealth / (wealth * weight).sum() wealth = parameters(period).household.wealth nbs = wealth.national_balance_sheet - wealth = household("corporate_wealth", period) + wealth = household("corporate_sector_wealth", period) total_wealth = nbs.household.financial_net_worth return wealth / total_wealth diff --git a/policyengine_uk/variables/household/wealth/total_wealth.py b/policyengine_uk/variables/household/wealth/total_wealth.py index 7a1367090c..5334c4f3b2 100644 --- a/policyengine_uk/variables/household/wealth/total_wealth.py +++ b/policyengine_uk/variables/household/wealth/total_wealth.py @@ -12,5 +12,6 @@ class total_wealth(Variable): adds = [ "property_wealth", "corporate_wealth", + "private_pension_wealth", "savings", ] diff --git a/policyengine_uk/variables/input/corporate_wealth.py b/policyengine_uk/variables/input/corporate_wealth.py index 178fae33ed..e1dabe6b62 100644 --- a/policyengine_uk/variables/input/corporate_wealth.py +++ b/policyengine_uk/variables/input/corporate_wealth.py @@ -3,7 +3,15 @@ class corporate_wealth(Variable): label = "corporate wealth" - documentation = "Total owned wealth in corporations, including stocks, mutual funds, private pensions, and corporate bonds" + documentation = ( + "Wealth held in corporations directly or through investment funds: UK " + "shares, employee shares and options, unit and investment trusts, and " + "stocks and shares ISAs, imputed from the Wealth and Assets Survey. The " + "stocks and shares ISA component is also exported on its own as " + "stocks_and_shares_isa, so the two must never be summed. Private pension " + "wealth is carried separately in private_pension_wealth; datasets built " + "before that split folded it into this variable." + ) entity = Household definition_period = YEAR value_type = float diff --git a/policyengine_uk/variables/input/private_pension_wealth.py b/policyengine_uk/variables/input/private_pension_wealth.py new file mode 100644 index 0000000000..10a0e21f83 --- /dev/null +++ b/policyengine_uk/variables/input/private_pension_wealth.py @@ -0,0 +1,33 @@ +from policyengine_uk.model_api import * + + +class private_pension_wealth(Variable): + label = "private pension wealth" + documentation = ( + "Value of the household's private (non-state) pension rights other than " + "current-employment defined benefit entitlements, which the data does " + "not yet carry: defined contribution pots, additional voluntary " + "contributions, current personal pensions, retained rights in earlier " + "occupational and personal schemes (defined benefit and defined " + "contribution, including drawdown funds), pensions in payment valued as " + "the capitalised income stream, and pensions expected under a pension " + "sharing order. Imputed from the Wealth and Assets Survey as total " + "private pension wealth less the current defined benefit component, " + "both at the SCAPE discount rate. Datasets built before this column " + "existed folded it into corporate_wealth. The value of a right to " + "receive a pension under an occupational or personal pension scheme is " + "disregarded as capital in every means test the model runs (see the " + "capital sources parameters of each benefit for the paragraph), so this " + "variable is not a capital source anywhere; pension income actually " + "drawn counts through private_pension_income. Not modelled: the " + "notional income rule for claimants over State Pension Credit age with " + "undrawn money purchase pots, and the capital treatment of withdrawn " + "lump sums." + ) + entity = Household + definition_period = YEAR + value_type = float + unit = GBP + uprating = "gov.economic_assumptions.indices.obr.per_capita.gdp" + quantity_type = STOCK + default_value = 0 From 98b5880b553e6d8065e05c508ab4a035f0c8b756 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Tue, 25 Aug 2026 23:19:46 +0200 Subject: [PATCH 2/2] Address the cross-family (sol) review: NI citations, precise pension wording, gate and incidence tests Documentation and test gaps from the independent sol review, fixed here (part applied by a parallel session on the same verdict, adopted): - Northern Ireland parallels cited where the model assesses NI households: UC Regs (NI) 2016 Sch 10 para 10 and HB Regs (NI) 2006 Sch 7 paras 32-33, in the UC and HB capital sources references and descriptions. - private_pension_wealth documentation: the survey values DB-type components at the SCAPE rate and money-purchase components at reported fund values; "pensions expected from a former spouse or partner"; the unmodelled notional-income rules widened to everything that can apply from SPC qualifying age (mixed-age UC couples, benefits not taken, deferred occupational pension income); withdrawals stated (ad hoc or whole-pot withdrawals become capital, regular withdrawals count as income). - corporate_sector_wealth documented as a compatibility allocation proxy, not a validated incidence model for capitalised pension wealth. - Tests: dividend income stays in UC unearned income when only disregarded pension wealth is held (the tariff-income gate keys on corporate_wealth alone), and the employer-NI capital response is unchanged when wealth moves between the two components. Co-Authored-By: Claude Fable 5 --- .../means_test/capital/sources.yaml | 5 +- .../means_test/capital/sources.yaml | 7 ++- .../income/uc_unearned_income.yaml | 24 ++++++++ .../tests/test_corporate_land_value.py | 58 +++++++++++++++++++ .../wealth/corporate_sector_wealth.py | 8 ++- .../variables/input/private_pension_wealth.py | 31 +++++----- 6 files changed, 115 insertions(+), 18 deletions(-) diff --git a/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/capital/sources.yaml b/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/capital/sources.yaml index 312f781c4e..e313576158 100644 --- a/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/capital/sources.yaml +++ b/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/capital/sources.yaml @@ -12,7 +12,8 @@ description: > excluded because Schedule 6 paragraphs 31 and 32 disregard the value of the right to receive an occupational or personal pension and the value of funds held under a personal pension scheme (paragraph 24 of Schedule 6 to the - pension-age regulations makes the same disregard). Datasets built before + pension-age regulations and paragraphs 32 and 33 of Schedule 7 to the + Northern Ireland regulations make the same disregard). Datasets built before private pension wealth was split out of `corporate_wealth` still carry it inside that countable bucket. Other Schedule 6 disregards within these broad asset classes are not yet split out separately. Because the @@ -29,6 +30,8 @@ metadata: href: https://www.legislation.gov.uk/uksi/2006/213/schedule/6/paragraph/1 - title: The Housing Benefit Regulations 2006 Schedule 6 paras. 31-32 href: https://www.legislation.gov.uk/uksi/2006/213/schedule/6/paragraph/31 + - title: The Housing Benefit Regulations (Northern Ireland) 2006 Schedule 7 paras. 32-33 + href: https://www.legislation.gov.uk/nisr/2006/405/schedule/7/paragraph/32 - title: The Housing Benefit (Persons who have attained the qualifying age for state pension credit) Regulations 2006 reg. 44(1) href: https://www.legislation.gov.uk/uksi/2006/214/regulation/44 - title: The Housing Benefit (Persons who have attained the qualifying age for state pension credit) Regulations 2006 Schedule 6 para. 1 diff --git a/policyengine_uk/parameters/gov/dwp/universal_credit/means_test/capital/sources.yaml b/policyengine_uk/parameters/gov/dwp/universal_credit/means_test/capital/sources.yaml index 05d71203b0..f64474cebb 100644 --- a/policyengine_uk/parameters/gov/dwp/universal_credit/means_test/capital/sources.yaml +++ b/policyengine_uk/parameters/gov/dwp/universal_credit/means_test/capital/sources.yaml @@ -10,8 +10,9 @@ description: > the person's home. `private_pension_wealth` is excluded because Schedule 10 paragraph 10 disregards the value of any right to receive a pension under an occupational or personal pension scheme (or any other scheme registered under - section 153 of the Finance Act 2004); pension income actually drawn counts as - unearned income instead. Other Schedule 10 disregards within these broad asset + section 153 of the Finance Act 2004; paragraph 10 of Schedule 10 to the + Northern Ireland regulations makes the same disregard); pension income + actually drawn counts as unearned income instead. Other Schedule 10 disregards within these broad asset classes are not yet split out separately. Where only household-level capital is available for a multi-benunit household, the model uses a household-wide adult-share proxy for residual capital because the dataset does not identify @@ -37,6 +38,8 @@ metadata: href: https://www.legislation.gov.uk/uksi/2013/376/schedule/10/paragraph/1 - title: The Universal Credit Regulations 2013 Schedule 10 para. 10 href: https://www.legislation.gov.uk/uksi/2013/376/schedule/10/paragraph/10 + - title: The Universal Credit Regulations (Northern Ireland) 2016 Schedule 10 para. 10 + href: https://www.legislation.gov.uk/nisr/2016/216/schedule/10/paragraph/10 values: 2013-04-29: - savings diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/income/uc_unearned_income.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/income/uc_unearned_income.yaml index 8f35bf8e4b..b1db7dcd72 100644 --- a/policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/income/uc_unearned_income.yaml +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/income/uc_unearned_income.yaml @@ -227,3 +227,27 @@ output: uc_tariff_income: 52.2 uc_unearned_income: 52.2 + +- name: Dividend income stays unearned when only disregarded pension wealth is held + period: 2025 + absolute_error_margin: 0.01 + input: + people: + person: + age: 30 + dividend_income: 120 + benunits: + benunit: + members: person + households: + household: + members: person + savings: 6_001 + private_pension_wealth: 150_000 + output: + # Pension wealth is disregarded capital (UC Regs 2013 Sch 10 para 10), so + # the dividend gate keys on corporate_wealth alone: with no share-like + # holdings the dividends are not folded into capital and stay unearned. + uc_assessable_capital: 6_001 + uc_tariff_income: 4.35 * 12 + uc_unearned_income: 4.35 * 12 + 120 diff --git a/policyengine_uk/tests/test_corporate_land_value.py b/policyengine_uk/tests/test_corporate_land_value.py index 43986a18e8..6abb0ad51d 100644 --- a/policyengine_uk/tests/test_corporate_land_value.py +++ b/policyengine_uk/tests/test_corporate_land_value.py @@ -112,3 +112,61 @@ def land_values(household_1: dict, household_2: dict): assert split_land[1] == pytest.approx(folded_land[1]) assert split_share[0] == pytest.approx(folded_share[0]) assert split_share[1] == pytest.approx(folded_share[1]) + + +def test_employer_ni_capital_incidence_is_unchanged_by_the_pension_split(): + """The employer-NI capital response allocates by corporate_sector_wealth, + so moving wealth between corporate_wealth and private_pension_wealth must + not move any household's incidence.""" + reform = { + "gov.contrib.policyengine.employer_ni.employee_incidence": {"2025": 0.5}, + "gov.contrib.policyengine.employer_ni.capital_incidence": {"2025": 0.5}, + } + + def incidence(household_1: dict, household_2: dict): + sim = Simulation( + reform=reform, + situation={ + "people": { + "person_1": { + "age": {2025: 40}, + "employment_income": {2025: 30_000}, + }, + "person_2": { + "age": {2025: 50}, + "employment_income": {2025: 60_000}, + }, + }, + "benunits": { + "benunit_1": {"members": ["person_1"]}, + "benunit_2": {"members": ["person_2"]}, + }, + "households": { + "household_1": { + "members": ["person_1"], + "household_weight": {2025: 2}, + **household_1, + }, + "household_2": { + "members": ["person_2"], + "household_weight": {2025: 1}, + **household_2, + }, + }, + }, + ) + return sim.calculate( + "employer_ni_response_capital_incidence", map_to="household", period=2025 + ) + + folded = incidence( + {"corporate_wealth": {2025: 100_000}}, + {"corporate_wealth": {2025: 300_000}}, + ) + split = incidence( + {"corporate_wealth": {2025: 20_000}, "private_pension_wealth": {2025: 80_000}}, + {"corporate_wealth": {2025: 50_000}, "private_pension_wealth": {2025: 250_000}}, + ) + + assert split[0] == pytest.approx(folded[0]) + assert split[1] == pytest.approx(folded[1]) diff --git a/policyengine_uk/variables/household/wealth/corporate_sector_wealth.py b/policyengine_uk/variables/household/wealth/corporate_sector_wealth.py index 9ace66bbed..a1792de135 100644 --- a/policyengine_uk/variables/household/wealth/corporate_sector_wealth.py +++ b/policyengine_uk/variables/household/wealth/corporate_sector_wealth.py @@ -7,8 +7,12 @@ class corporate_sector_wealth(Variable): "Corporate wealth held directly or through investment funds plus private " "pension wealth, which pension funds invest in the corporate sector. The " "allocation key for corporate tax incidence (shareholding), corporate " - "land value and the employer NI capital response. Equals corporate_wealth " - "on datasets built before private pension wealth was split out of it." + "land value and the employer NI capital response. A compatibility " + "allocation proxy: it preserves the pre-split key (pension funds hold " + "corporate assets) rather than independently validating how much " + "capitalised pension wealth is corporate-sector exposure. Equals " + "corporate_wealth on datasets built before private pension wealth was " + "split out of it." ) entity = Household definition_period = YEAR diff --git a/policyengine_uk/variables/input/private_pension_wealth.py b/policyengine_uk/variables/input/private_pension_wealth.py index 10a0e21f83..566b4d6510 100644 --- a/policyengine_uk/variables/input/private_pension_wealth.py +++ b/policyengine_uk/variables/input/private_pension_wealth.py @@ -10,19 +10,24 @@ class private_pension_wealth(Variable): "contributions, current personal pensions, retained rights in earlier " "occupational and personal schemes (defined benefit and defined " "contribution, including drawdown funds), pensions in payment valued as " - "the capitalised income stream, and pensions expected under a pension " - "sharing order. Imputed from the Wealth and Assets Survey as total " - "private pension wealth less the current defined benefit component, " - "both at the SCAPE discount rate. Datasets built before this column " - "existed folded it into corporate_wealth. The value of a right to " - "receive a pension under an occupational or personal pension scheme is " - "disregarded as capital in every means test the model runs (see the " - "capital sources parameters of each benefit for the paragraph), so this " - "variable is not a capital source anywhere; pension income actually " - "drawn counts through private_pension_income. Not modelled: the " - "notional income rule for claimants over State Pension Credit age with " - "undrawn money purchase pots, and the capital treatment of withdrawn " - "lump sums." + "the capitalised income stream, and pensions expected from a former " + "spouse or partner. Imputed from the Wealth and Assets Survey as total " + "private pension wealth less the current defined benefit component (the " + "survey values defined benefit entitlements and pensions in payment at " + "the SCAPE discount rate; the other components are reported fund " + "values). Datasets built before this column existed folded it into " + "corporate_wealth. The value of a right to receive a pension under an " + "occupational or personal pension scheme is disregarded as capital in " + "every means test the model runs, in Great Britain and Northern Ireland " + "alike (see the capital sources parameters of each benefit for the " + "paragraphs), so this variable is not a capital source anywhere; " + "pension income actually drawn counts through private_pension_income. " + "Not modelled: the notional income rules that can apply from State " + "Pension Credit qualifying age (including the older partner of a " + "mixed-age Universal Credit couple, money purchase benefits not taken, " + "and deferred occupational pension income), and the capital-versus-" + "income treatment of withdrawals (ad hoc or whole-pot withdrawals " + "become capital; regular withdrawals count as income)." ) entity = Household definition_period = YEAR