diff --git a/changelog.d/uc-capital-corporate-wealth.changed.md b/changelog.d/uc-capital-corporate-wealth.changed.md new file mode 100644 index 0000000000..f01e668054 --- /dev/null +++ b/changelog.d/uc-capital-corporate-wealth.changed.md @@ -0,0 +1 @@ +Universal Credit capital test: excluded `corporate_wealth` from the countable capital sources as an interim approximation pending a dataset-side pension split (policyengine-uk-data#452). The dataset builds that variable as private pension wealth (which Schedule 10 paragraph 10 of the Universal Credit Regulations 2013 disregards) bundled with shares, investment ISAs and collective investments (which the law counts); counting the bundle failed the capital test for claimants whose only large asset is a pension, and excluding it leaves the share-like components uncounted until the dataset separates them. Housing Benefit, JSA, ESA, Income Support and Pension Credit still count the bundle through their own source lists, and the CI regression fixture for the UC taper reform was re-baselined to the new caseload. diff --git a/docs/book/programs/gov/dwp/universal-credit.ipynb b/docs/book/programs/gov/dwp/universal-credit.ipynb index 5581288a93..157fe50bb5 100644 --- a/docs/book/programs/gov/dwp/universal-credit.ipynb +++ b/docs/book/programs/gov/dwp/universal-credit.ipynb @@ -44,7 +44,7 @@ { "cell_type": "markdown", "metadata": {}, - "source": "## How PolicyEngine computes Universal Credit\n\nPolicyEngine computes Universal Credit at the benefit-unit level in three stages:\n\n1. **Maximum entitlement** (`uc_maximum_amount`): sum the elements the benunit qualifies for — `uc_standard_allowance`, `uc_child_element`, `uc_disability_elements`, `uc_carer_element`, `uc_housing_costs_element`, and `uc_childcare_element`.\n2. **Means test**: the maximum entitlement is reduced by the benunit's countable earnings above its work allowance (`uc_work_allowance`) at the published taper rate (the `gov.dwp.universal_credit.means_test.reduction_rate` parameter, currently 55%), plus its unearned income. Asset rules apply on top of this — capital above the lower threshold deems a tariff income, and capital above the upper threshold disqualifies the benunit entirely.\n3. **Benefit cap**: the post-means-test award (`universal_credit_pre_benefit_cap`) is finally reduced by `benefit_cap_reduction` to produce `universal_credit`. The benefit cap only applies to benunits without a benefit-cap exemption (working enough hours, having a qualifying disability benefit, etc.).\n\nThe take-up step is handled by the input variable `would_claim_uc`, which is populated stochastically when the dataset is built so that PolicyEngine's caseload aggregates match published DWP claimant numbers rather than the full eligible population.\n\nParameters live in `policyengine_uk/parameters/gov/dwp/universal_credit/` and the per-element formulas in `policyengine_uk/variables/gov/dwp/universal_credit/`." + "source": "## How PolicyEngine computes Universal Credit\n\nPolicyEngine computes Universal Credit at the benefit-unit level in three stages:\n\n1. **Maximum entitlement** (`uc_maximum_amount`): sum the elements the benunit qualifies for — `uc_standard_allowance`, `uc_child_element`, `uc_disability_elements`, `uc_carer_element`, `uc_housing_costs_element`, and `uc_childcare_element`.\n2. **Means test**: the maximum entitlement is reduced by the benunit's countable earnings above its work allowance (`uc_work_allowance`) at the published taper rate (the `gov.dwp.universal_credit.means_test.reduction_rate` parameter, currently 55%), plus its unearned income. Asset rules apply on top of this — capital above the lower threshold deems a tariff income, and capital above the upper threshold disqualifies the benunit entirely. Countable capital is configured by `gov.dwp.universal_credit.means_test.capital.sources` (savings and property other than the home); `corporate_wealth` is excluded as an interim approximation because the dataset bundles pension wealth, which the regulations disregard, with shares and ISAs, which they count (see the parameter description and policyengine-uk-data#452).\n3. **Benefit cap**: the post-means-test award (`universal_credit_pre_benefit_cap`) is finally reduced by `benefit_cap_reduction` to produce `universal_credit`. The benefit cap only applies to benunits without a benefit-cap exemption (working enough hours, having a qualifying disability benefit, etc.).\n\nThe take-up step is handled by the input variable `would_claim_uc`, which is populated stochastically when the dataset is built so that PolicyEngine's caseload aggregates match published DWP claimant numbers rather than the full eligible population.\n\nParameters live in `policyengine_uk/parameters/gov/dwp/universal_credit/` and the per-element formulas in `policyengine_uk/variables/gov/dwp/universal_credit/`." }, { "cell_type": "code", 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..17c432df7c 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 @@ -4,12 +4,26 @@ description: > treated as income or disregarded under regulation 48 and Schedule 10. The current mappings are `savings` for liquid capital, `other_residential_property_value` for residential property other than the - 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 - 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 + home, and `non_residential_property_value` for other land and premises. + `main_residence_value` is excluded because Schedule 10 paragraph 1 disregards + premises occupied as the person's home. `corporate_wealth` is excluded as an + interim approximation pending a dataset-side pension split + (policyengine-uk-data#452). In law, shares, investment ISAs and collective + investments are countable capital with no general Schedule 10 disregard, but + the dataset builds `corporate_wealth` as total private pension wealth less + current defined benefit pension wealth plus employee shares and options, UK + shares, investment ISAs and collective investments, and Schedule 10 + paragraph 10 disregards the value of rights under an occupational, personal + or other registered pension scheme. Pension wealth is most of that bundle, + so counting it fails the capital test for claimants whose only large asset + is a pension; excluding it leaves the share-like components uncounted until + the dataset separates them from pensions, at which point the share-like + remainder should return to this list. Other disregards within these broad + asset classes (business assets under Schedule 10 paragraphs 7 and 8, life + policies under paragraph 9, owner-managed company holdings under regulation + 77, and personal-injury and compensation sums under regulations 75 and 76) + 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 which adults hold the beneficial interest in each capital asset. The denominator is all unreported adults in the household, including adults in @@ -31,9 +45,10 @@ 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 - other_residential_property_value - non_residential_property_value - - corporate_wealth diff --git a/policyengine_uk/parameters/gov/dwp/universal_credit/means_test/income_definitions/capital_derived.yaml b/policyengine_uk/parameters/gov/dwp/universal_credit/means_test/income_definitions/capital_derived.yaml index 2117240657..bc573c374f 100644 --- a/policyengine_uk/parameters/gov/dwp/universal_credit/means_test/income_definitions/capital_derived.yaml +++ b/policyengine_uk/parameters/gov/dwp/universal_credit/means_test/income_definitions/capital_derived.yaml @@ -4,9 +4,17 @@ description: > from capital, for example rental, interest or dividends. In the current model, those map to `property_income` when countable property capital is present, `savings_interest_income` when savings are present, and - `dividend_income` when corporate wealth is present. Those linked income - streams are removed from `uc_unearned_income` once `uc_tariff_income` - applies to avoid double counting the same capital. The model does not add + `dividend_income` when corporate wealth is present. `corporate_wealth` is + no longer itself a countable capital source (`capital/sources.yaml` + excludes the mixed pension bundle as an interim approximation), but it + still gates the dividend fold because it is the only available signal that + the benunit holds shares, which remain countable capital in law; once + tariff income arises from savings or property, regulation 72(3) treats + dividends from those shares as capital rather than income. Those linked + income streams are removed from `uc_unearned_income` once `uc_tariff_income` + applies, which for savings and property avoids double counting the same + capital and for dividends approximates the legal treatment of income from + shares the dataset cannot separate from pensions. The model does not add annual income flows back into `uc_assessable_capital`, because the dataset does not identify whether those receipts are retained as capital within the assessment year. When benunit-level `uc_reported_capital` is provided, the diff --git a/policyengine_uk/tests/microsimulation/reforms_config.yaml b/policyengine_uk/tests/microsimulation/reforms_config.yaml index 57bcc7d2ef..8668792683 100644 --- a/policyengine_uk/tests/microsimulation/reforms_config.yaml +++ b/policyengine_uk/tests/microsimulation/reforms_config.yaml @@ -19,8 +19,11 @@ reforms: expected_impact: -1.2 parameters: gov.hmrc.child_benefit.amount.additional: 25 +# Re-baselined when corporate_wealth left the UC countable capital sources +# (interim pension-bundle exclusion): the taper reform now operates on the +# larger eligible caseload. - name: Reduce Universal Credit taper rate to 20% - expected_impact: -18.1 + expected_impact: -30.3 tolerance: 3.0 parameters: gov.dwp.universal_credit.means_test.reduction_rate: 0.2 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..1667c0ce7c 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 @@ -280,7 +280,7 @@ uc_assessable_capital: 50_000 is_uc_eligible: false -- name: Countable corporate wealth can push capital over the upper threshold +- name: Corporate wealth is excluded from Universal Credit capital pending a pension split period: 2025 absolute_error_margin: 0 input: @@ -295,8 +295,8 @@ members: person corporate_wealth: 50_000 output: - uc_assessable_capital: 50_000 - is_uc_eligible: false + uc_assessable_capital: 0 + is_uc_eligible: true - name: Main residence value is excluded from Universal Credit capital period: 2025 diff --git a/policyengine_uk/tests/test_stock_capital_periods.py b/policyengine_uk/tests/test_stock_capital_periods.py index d36c3068b2..75e7a95019 100644 --- a/policyengine_uk/tests/test_stock_capital_periods.py +++ b/policyengine_uk/tests/test_stock_capital_periods.py @@ -15,6 +15,7 @@ def test_uc_capital_stocks_are_not_prorated_in_monthly_calculations(): "members": ["person"], "savings": {"2026": 12_000}, "other_residential_property_value": {"2026": 12_000}, + "non_residential_property_value": {"2026": 12_000}, "corporate_wealth": {"2026": 12_000}, } }, @@ -23,6 +24,9 @@ def test_uc_capital_stocks_are_not_prorated_in_monthly_calculations(): assert simulation.calculate("savings", "2026-01")[0] == 12_000 assert simulation.calculate("corporate_wealth", "2026-01")[0] == 12_000 + # corporate_wealth is not a countable UC capital source (interim + # approximation pending the dataset-side pension split), so assessable + # capital is the other three stocks, unprorated. assert simulation.calculate("uc_assessable_capital", "2026-01")[0] == 36_000 assert not simulation.calculate("is_uc_eligible", "2026-01")[0] diff --git a/policyengine_uk/tests/test_uc_capital_sources.py b/policyengine_uk/tests/test_uc_capital_sources.py new file mode 100644 index 0000000000..da8b68b20b --- /dev/null +++ b/policyengine_uk/tests/test_uc_capital_sources.py @@ -0,0 +1,89 @@ +"""Interim Universal Credit capital scope: corporate_wealth is not counted. + +The dataset builds ``corporate_wealth`` as total private pension wealth less +current defined benefit pension wealth plus employee shares and options, UK +shares, investment ISAs and collective investments. The Universal Credit +Regulations 2013 disregard pension rights (Schedule 10 paragraph 10) but count +shares, investment ISAs and collective investments. Until the dataset build +splits pensions from the share-like components (policyengine-uk-data#452), the +UC capital test excludes the whole bundle. These tests pin that scope: the UC +list itself, the UC assessment, and the deliberate decision that nothing +outside the UC chain changes. +""" + +from policyengine_uk import Simulation + +YEAR = 2025 + + +def _single_adult_household(**household_inputs): + return { + "people": {"person": {"age": {YEAR: 30}}}, + "benunits": { + "benunit": { + "members": ["person"], + "would_claim_uc": {YEAR: True}, + } + }, + "households": { + "household": { + "members": ["person"], + **{k: {YEAR: v} for k, v in household_inputs.items()}, + } + }, + } + + +def test_corporate_wealth_is_not_a_uc_capital_source(): + simulation = Simulation(situation=_single_adult_household(savings=1_000)) + parameters = simulation.tax_benefit_system.parameters + for instant in ("2013-04-29", "2020-01-01", f"{YEAR}-01-01", "2030-01-01"): + listed = list( + parameters(instant).gov.dwp.universal_credit.means_test.capital.sources + ) + assert listed == [ + "savings", + "other_residential_property_value", + "non_residential_property_value", + ] + + +def test_uc_assessable_capital_excludes_corporate_wealth(): + simulation = Simulation( + situation=_single_adult_household(savings=5_000, corporate_wealth=250_000) + ) + assert simulation.calculate("uc_assessable_capital", YEAR)[0] == 5_000 + assert simulation.calculate("is_uc_eligible", YEAR)[0] + # Below the 6,000 GBP tariff income threshold, so the pension bundle + # neither disqualifies the claimant nor deems a tariff income. + assert simulation.calculate("uc_tariff_income", YEAR)[0] == 0 + + +def test_uc_still_counts_savings_and_property(): + simulation = Simulation( + situation=_single_adult_household( + savings=7_000, + other_residential_property_value=6_000, + non_residential_property_value=4_000, + corporate_wealth=1_000_000, + ) + ) + assert simulation.calculate("uc_assessable_capital", YEAR)[0] == 17_000 + assert not simulation.calculate("is_uc_eligible", YEAR)[0] + + +def test_corporate_wealth_still_feeds_wealth_and_legacy_capital_tests(): + """Scope guard: only the UC list changed in the interim fix. + + Housing Benefit keeps counting the mixed bundle (its parameter description + documents that as an unresolved data limitation), and total_wealth includes + corporate_wealth regardless of any means test. The follow-up that splits + pension wealth in the dataset build should revisit the legacy lists and + update this test. + """ + simulation = Simulation( + situation=_single_adult_household(savings=1_000, corporate_wealth=50_000) + ) + assert simulation.calculate("uc_assessable_capital", YEAR)[0] == 1_000 + assert simulation.calculate("housing_benefit_assessable_capital", YEAR)[0] == 51_000 + assert simulation.calculate("total_wealth", YEAR)[0] == 51_000