Skip to content

Itemise corporate_wealth's share-like components for the means tests - #1974

Open
MaxGhenis wants to merge 8 commits into
mainfrom
wealth-share-components
Open

MaxGhenis wants to merge 8 commits into
mainfrom
wealth-share-components

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Oct 1, 2026 •

Copy link
Copy Markdown
Collaborator

What

corporate_wealth bundles assets the means tests value differently:

  • quoted shares are valued less 10% for the expenses of sale (ADM H1665; DMG 29671, 52671, 84763);
  • unit trusts are valued at the bid price with no costs of sale (ADM H1673-H1674; DMG 29680-29681);
  • ISAs are valued at what the holder would get by withdrawing (ADM H1656).

This PR prepares the model for datasets that itemise it, without changing any existing result:

  1. Two household STOCK inputs. directly_held_shares (UK shares, listed or not, plus employee shares and options, held outside ISAs and pooled funds) and unit_and_investment_trusts (the Wealth and Assets Survey asks about both in one question).
  2. Uprating. Both inputs, plus cash_isa and stocks_and_shares_isa from Add cash ISA and stocks and shares ISA holdings variables #1791, join corporate_wealth under per-capita GDP in uprating_indices.yaml, so a dataset's identity corporate_wealth == Σ components survives projection. The growthfactors.md sentence now matches the YAML list exactly.
  3. share_like_capital (formula, STOCK). It is corporate_wealth as supplied, unless the itemised components are positive and add up to more than that by more than 1e-6 of the larger of the two, in which case it is exactly their sum. The tolerance covers float32 rounding: a dataset that builds corporate_wealth as the float64 sum of three components and stores all four rounds each once, and the engine re-adds them in float32, so the two totals differ by at most about 3e-7 of the value. With no corporate_wealth, any positive components count in full, down to a penny.
  4. Capital lists. All six capital sources lists (UC, HB, IS, JSA, ESA, PC) name share_like_capital instead of corporate_wealth.
  5. Inherited fix. The pension-age HB home disregard is Schedule 6 paragraph 26 of SI 2006/214, not paragraph 1.

Impact

None on any dataset; this is tested exactly.

#2131, stacked on #1969, then splits share_like_capital into the part with sale expenses (directly held shares, plus anything not itemised) and the rest. Unit trusts and ISAs therefore escape the 10%, and unsplit inputs keep #1969's treatment exactly.

Invariants (tests)

  • The block's definition (Hypothesis, float32 inputs including negative aggregates): it equals corporate_wealth exactly unless the components exceed it beyond the tolerance, and then equals their sum.

  • Differential against the original lists. On identical inputs, unsplit (negative and fractional aggregates) or split consistently, every programme's assessable capital and UC eligibility equal those under a reform restoring corporate_wealth to the six lists. The check is exact equality, annual and monthly.

  • Boundary cases, specified independently of the formula:

    • Float32 rounding at the limit. Components £6,744.81 + £6,581.24 + £2,673.95 against a supplied £16,000 keep the supplied aggregate and UC eligibility.
    • Negative aggregate. Savings £6,001 with corporate_wealth of −£2 gives main's £5,999.
    • Penny at the limit. A penny of shares with no corporate_wealth, on top of £16,000 of savings, gives the same capital and UC eligibility as the penny entered as corporate_wealth, annually and monthly.
    • Genuine excess. £0.10 of components above the aggregate counts.

    The earlier floored-residual design, and the earlier tolerance, each fail one of these.

  • Uprating. Components share corporate_wealth's entity, quantity type, uprating and YAML index; total_wealth doesn't add them on top of corporate_wealth.

Local runs (head f64da77, main 976ec91 merged)

Run Result
test_capital_share_components.py, test_wealth_components.py, test_stock_capital_periods.py, code_health 2,310 passed
YAML for benefit families, gov/dwp, household/wealth, gov/local_authorities (incl. CTR routes) 1,805 passed
ruff format --check clean
Mutation check: the earlier tolerance (1e-5 relative + £0.01) the new penny-at-the-limit and £0.10-excess tests fail

Reviews

Head Verdict Outcome
8b86a54 APPROVE WITH NITS nits applied
21412b6 REQUEST_CHANGES: float32 reconstruction, negative aggregates, weak differential, HB para 26 all fixed in 7784193
7784193 REQUEST_CHANGES: a component penny lost at a zero aggregate, a tolerance looser than float32 needs, approximate comparisons, overstated descriptions all fixed in f64da77

A round-4 review has been requested at f64da77.

axiom: n/a: model plumbing that changes no rule; the sale-expense rule itself is #1969's (rulespec-uk#395)

🤖 Generated with Claude Code

corporate_wealth bundles three assets the means tests value differently:
quoted shares less 10% for the expenses of sale (ADM H1665), unit trusts at
the manager's withdrawal price with no deduction (H1673-H1674), and stocks
and shares ISAs at their withdrawal value (H1656). Add household inputs for
the two parts not yet modelled, directly_held_shares (UK shares and employee
shares and options) and unit_and_investment_trusts, and uprate them, cash_isa
and stocks_and_shares_isa with per-capita GDP like corporate_wealth, so a
dataset's identity corporate_wealth == sum of components survives
projection. The inputs have no consumers yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@juaristi22

Copy link
Copy Markdown
Collaborator

Reviewed commit 54f4c7da480f79963967dc6689af022e82673636. Needs changes; review coverage is partial. The additive wealth inputs passed their focused tests. The current-main integration now needs a documentation conflict resolved and the #1791 dependency handled before merge.

Confirmed integration conflict. A prospective merge of this exact head onto main 3c48247eb92b221312565bff3a1dbf0b87306e22, attempted on 2 October 2026, stopped with a content conflict in growthfactors.md. The attempt safely aborted without changing the PR. No integration tests ran on that prospective merged tree. GitHub's mergeability against the current add-isa-balance-variables base does not establish mergeability against main.

Preserve the component contract. test_wealth_components.py:26 verifies the share components use the same entity, stock semantics and uprating as corporate_wealth; test_wealth_components.py:42 checks they are not added again to total_wealth. These are useful inputs even before data builders or benefit formulas consume them. Their addition does not establish that every supported dataset has split pensions out of its corporate-wealth bundle, so it does not by itself resolve #1837.

Repair and acceptance steps:

  1. Land the ISA input parent Add cash ISA and stocks and shares ISA holdings variables #1791 first, then retarget this PR to main and rebase on its actual tip. If the parent is consolidated through another agreed route, preserve both ISA definitions and their changelog rather than dropping the dependency silently.
  2. Resolve growthfactors.md by retaining current-main descriptions and this PR's new component entries. Check the final prose against policyengine_uk/data/uprating_indices.yaml and variable metadata; do not select an entire outdated document side.
  3. Keep corporate_wealth as the aggregate holding its share/fund components, with cash_isa following the savings contract. Preserve the no-double-count test and the common uprating index, so a dataset that supplies the component identity retains it when projected.
  4. Run the focused suite on the integrated tree and obtain new-head hosted checks. Check documentation rendering if conflict resolution materially changes the page. Report any data migration separately, with its supported schema and consumers; no population-impact claim is needed for this input-only change.

Suggested checks after resolving the dependency and conflict:

uv run --no-sync pytest policyengine_uk/tests/test_wealth_components.py -q
uv run --no-sync ruff format --check .
uv run --no-sync ruff check .

Validation already completed on this exact PR snapshot: 3 tests passed in test_wealth_components.py, using an asserted snapshot import, core 3.32.9 and the existing environment without installing dependencies. That run preceded the attempted current-main integration. The combined tree, final documentation resolution, hosted CI after retargeting and any population-data consumers remain unverified.

Live posting check (2026-10-02T15:12:45.036767+00:00): same reviewed commit; GitHub reports MERGEABLE. No hosted checks reported. These checks do not replace the remaining validation listed above.

Base automatically changed from add-isa-balance-variables to main October 4, 2026 10:27
MaxGhenis and others added 2 commits October 4, 2026 06:28
# Conflicts:
#	docs/book/assumptions/growthfactors.md
The six capital sources lists (UC, HB, IS, JSA, ESA, PC) name
directly_held_shares, unit_and_investment_trusts and stocks_and_shares_isa
in place of corporate_wealth, plus a new formula variable,
unitemised_corporate_wealth = max(0, corporate_wealth - components), for
whatever a dataset does not itemise. Datasets without the components keep
counting corporate_wealth in full; datasets that build corporate_wealth as
the components' exact sum count the same total, now itemised, so each
component can carry its own valuation rule (the sale-expense deduction stacked
on #1969 exempts unit trusts and ISAs). No dataset's capital changes: a
differential property test checks that every programme's assessable capital
equals the same holdings entered as one corporate_wealth.

Review nits: the growth-factor sentence now matches uprating_indices.yaml
exactly; the variable docs cite the DMG for the legacy benefits, the expert
valuation of unquoted shares and the quoted-share rule for investment trusts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MaxGhenis MaxGhenis changed the title Add share-like wealth component inputs Itemise corporate_wealth's share-like components for the means tests Oct 4, 2026
@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

VERDICT: APPROVE WITH NITS

I couldn't run any commands here: there was no shell, so I didn't run gh pr view, git diff origin/main...HEAD, the merge-commit diff or the uv run pytest … command. I didn't read the two CLAUDE.md files either, because the search for them timed out. The tests have not been run, so I can't say whether they pass. The review below comes from reading the files at HEAD, the ADM H1 PDF and policyengine-uk-data PR #501. Run the pytest command before merging.

What checks out

  • ADM H1665: it falls under "Stocks and shares quoted on the London Stock Exchange" and says to "deduct 10% costs of sale". The docstring's "price less 10% for the expenses of sale" is accurate.

  • ADM H1673–H1674: value is the bid price × units, and "there are no costs of sale" because people withdraw through the manager. The docstring's paraphrase is accurate.

  • WAS mapping in Benefit unit rent fails to calculate from individual rent #501's docs/imputations.md:

    • directly_held_shares = DVFShUKVR8 + DVFESHARESR8 (UK shares plus employee shares and options).
    • unit_and_investment_trusts = DVFCollVR8 ("one survey question").
    • stocks_and_shares_isa = DVIISAVR8.
    • corporate_wealth is "the exact sum of its three components".
    • Overseas shares (DVFShOSVR8) are unmapped.

    This all matches the docstrings.

  • Uprating yaml (policyengine_uk/data/uprating_indices.yaml:92,94,116,118): each of the four variables appears exactly once, under yoy_growth.obr.per_capita.gdp, in alphabetical order. That matches corporate_wealth and savings, and the classes' uprating attribute matches corporate_wealth's. Being listed exactly once matters because apply_single_year_uprating (policyengine_uk/data/economic_assumptions.py:109-116) would apply the growth twice for a variable listed under two indices.

  • Merge commit: the main entries are still there (pension_credit_reported_capital, lifetime_isa_balance, household_lifetime_isa_balance) and there are no conflict markers under policyengine_uk/. That's all I could confirm without git.

  • Would the tests catch wrong uprating? Yes. test_components_match_corporate_wealth fails if a component is:

    • missing from the yaml ([] != [gdp]),
    • filed under a different index,
    • listed twice (a two-element list), or
    • given a class uprating that differs from corporate_wealth's.

    test_cash_isa_is_uprated_with_savings does the same for cash_isa. test_components_are_not_part_of_total_wealth guards against counting the components twice.

  • Naming: nothing collides with directly_held_shares or unit_and_investment_trusts. Putting them in variables/input/ alongside corporate_wealth fits, and the lowercase labels match corporate_wealth.

Findings

  1. docs/book/assumptions/growthfactors.md:63 (nit): the per-capita GDP sentence doesn't match the yaml list exactly. It is missing pension_credit_reported_capital (yaml line 109, from Tighten the Pension Credit reported-capital docs and properties (review of #2018) #2070). This probably came in from main rather than this PR, but this PR rewrites that sentence. Fix: add `pension_credit_reported_capital` between owned_land and pension_income.

  2. policyengine_uk/variables/input/directly_held_shares.py:10 and unit_and_investment_trusts.py:8 (nit): "The Enhanced FRS imputes it" is in the present tense, but uk-data Benefit unit rent fails to calculate from individual rent #501 is still a draft. Fix: say "is to impute (policyengine-uk-data Benefit unit rent fails to calculate from individual rent #501)", or merge Benefit unit rent fails to calculate from individual rent #501 first.

  3. directly_held_shares.py:12-14 (nit): "for the means tests" is too broad. ADM H1 covers Universal Credit only (H1001: "This Chapter gives guidance on capital and its effect on UC"); Pension Credit and legacy benefits are covered by the DMG. Also, the variable includes unlisted shares, which H1679 says need an expert valuation rather than the 10% rule. Fix: "Quoted shares are valued for Universal Credit at their price less 10% for the expenses of sale (ADM H1665); unquoted shares need an expert valuation (ADM H1679)."

  4. directly_held_shares.py:8 (nit): "shares in UK companies, listed or not" isn't supported by Benefit unit rent fails to calculate from individual rent #501's docs, which only say "UK shares". Fix: drop "listed or not", or cite the WAS questionnaire wording.

  5. unit_and_investment_trusts.py:10-13 (nit): the docstring explains only how unit trusts are valued. Investment trusts are listed companies, so they fall under H1665 (less 10%), not H1673–H1674. Also, H1673 refers to the "bid price". Fix: "Unit trusts are valued at the bid price with no deduction for costs of sale (ADM H1673-H1674); investment trusts are quoted shares, valued less 10% (ADM H1665)."

  6. policyengine_uk/variables/input/corporate_wealth.py:6-13 (nit): the new definition (three components, exact sum) describes datasets built with Benefit unit rent fails to calculate from individual rent #501. Datasets published now may also fold overseas shares, gilts, bonds and insurance products into corporate_wealth. Fix: add "Older datasets may also include overseas shares, gilts, bonds and insurance products."

  7. directly_held_shares.py:22 and unit_and_investment_trusts.py:21 (nit): default_value = 0 does nothing, since 0 is already the default for a float. It also differs from corporate_wealth, cash_isa and stocks_and_shares_isa, which leave it out, though some other inputs in this folder (e.g. private_pension_wealth, lifetime_isa_balance) do set it. Fix: remove it, or leave it; either works.

  8. changelog.d/wealth-share-components.added.md:1 (nit): unlike the other fragments (including add-isa-balance-variables.added.md), it doesn't start with "- ". Fix: add the leading dash.

  9. policyengine_uk/variables/household/wealth/{cash_isa,stocks_and_shares_isa}.py (nit, from Add cash ISA and stocks and shares ISA holdings variables #1791): these ISA variables live in household/wealth/, while the new components and corporate_wealth are in input/. Fix: if you want consistency, move them in a later PR; it doesn't need to block this one.

Out of scope, but worth a look: pension_credit_reported_capital uses default_value = -1 to mean "not supplied", and it is in the GDP uprating list. Projecting a dataset that stores that -1 would turn it into roughly -1.03 in later years, unless the consuming formula treats any negative value as unset.

@vahid-ahmadi vahid-ahmadi left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Max. Reviewed at head 21412b6a.

What I checked:

  • Tests. test_capital_share_components.py and test_wealth_components.py: 16 passed. DWP, family and household YAML: 1,040 passed. CI is green and it merges cleanly into current main.
  • The residual. unitemised_corporate_wealth = max(0, cw − Σ components). On an unsplit household the capital totals match main exactly, so "no impact on any dataset" is true for this PR on its own.
  • Uprating. Each component is listed once under per-capita GDP, the same as corporate_wealth, and the growthfactors.md sentence now matches the YAML list.
  • No other readers. Nothing else reads corporate_wealth in a means test: only total_wealth, corporate_sector_wealth and the residual do.

Findings:

  1. Blocking (merge order). This PR must not end up on main alongside #1969 without #2131.

    • #1969's sale_expenses.sources names corporate_wealth, which this PR removes from every capital.sources list, so the 10% on shares silently disappears.
    • On a #1969 + #1974 tree, savings £5,000 + corporate_wealth £12,000 gives £17,000 again for UC, HB and IS, instead of £15,800. Seven of #1969's YAML cases fail.
    • The "Impact: none on any dataset" claim holds only while #1969 is unmerged. Please say so in the body.
    • Safe orders: merge this PR before #1969 and then land #1969 and #2131 back to back; or skip merging it separately and let #2131 (which contains it) carry it after #1969. If you take the second route, close this PR as superseded.
  2. Nit. variables/input/directly_held_shares.py:8-9. "listed or not" includes unquoted shares. They need an expert valuation (ADM H1679-H1681), not the H1665 price formula. The docstring already says so. It's worth a line noting that, under #2131, they still take the 10%, because selling them needs a solicitor or accountant (H1605). That reading is also the one #1965's owner-manager holding relies on.

MaxGhenis and others added 2 commits October 12, 2026 09:59
# Conflicts:
#	docs/book/assumptions/growthfactors.md
#	policyengine_uk/data/uprating_indices.yaml
The independent review of 21412b6 found that reconstructing
corporate_wealth as components plus a floored residual could tip a household
across the capital limit on float32 rounding (components summing to
16,000.0009 against a supplied 16,000), and changed unsplit inputs with a
negative corporate_wealth. The six capital lists now name one formula
variable, share_like_capital: corporate_wealth as supplied, unless the
itemised components add up to more than it beyond a rounding tolerance
(1e-5 relative plus a penny), in which case their sum. Every dataset that
carries only corporate_wealth, or builds it as the components' sum, counts
exactly corporate_wealth; a household given only components has them
counted.

Tests: a differential against the original corporate_wealth lists on
identical inputs (unsplit with negative and fractional aggregates, and
consistent splits), annual and monthly, asserting capital and UC
eligibility equal exactly; the float32 limit case and the negative
aggregate; and the block's definition as a property. Replacing the formula
with the old floored residual fails the float32 test.

Also: the pension-age HB home disregard is Schedule 6 paragraph 26 of SI
2006/214, not paragraph 1 (review finding, predates this PR).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MaxGhenis MaxGhenis added the ci-recheck Re-triggers Fix evidence without changing the PR (UK hub, 2026-10-12) label Oct 12, 2026
MaxGhenis added a commit that referenced this pull request Oct 12, 2026
#1974 replaced the components-plus-residual design with one share_like_capital
block that keeps the supplied corporate_wealth exactly. This follow-up now
splits that block in two:

- share_like_capital_with_sale_expenses: share_like_capital less the itemised
  holdings in gov.dwp.share_like_capital.without_sale_expenses (unit and
  investment trusts, stocks and shares ISAs), floored at nil; all of it when
  nothing is itemised.
- share_like_capital_without_sale_expenses: the rest, exactly nil when nothing
  is itemised.

The six capital lists name both parts and the six sale-expense lists the
first, so datasets that carry only corporate_wealth keep #1969's flat 10%
exactly (differential test, annual and monthly, against the lists before
itemising, including negative aggregates and a value whose 90% is a fraction
of a penny over the limit). The exempt holdings are a parameter list, so the
methodology call on investment trusts and ISAs (d1396) is a one-line change.

Merged the refreshed #1969 (54ed063) and #1974 (7784193). #1969's
property oracle reads the share-like sources from an unitemised household's
corporate_wealth; the split invariants (definition of both parts, the
unitemised differential, split effect = 10% of trusts plus ISAs on UC
capital, components-only valuation) are in test_capital_share_components.
Dropping the nothing-itemised branch from the formula fails the differential.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis and others added 3 commits October 13, 2026 08:25
Round-3 review of 7784193: with no corporate_wealth, a penny of shares fell
inside the absolute tolerance and vanished, so UC eligibility at the £16,000
limit differed from the same penny entered as corporate_wealth; and 1e-5
relative plus a penny (£0.17 at £16,000, about 174 units in the last place)
hid genuine excesses. The tolerance is now 1e-6 of the larger of
corporate_wealth and the components' sum, against a float32 bound of about
3e-7 for a float64 sum of three components rounded and re-added in float32,
and it is relative only, so a household with no corporate_wealth has any
positive components counted in full.

Tests: the component-winning case compares exactly against the engine's
float32 sum; new independently specified cases for a component penny at the
limit (same capital and UC eligibility as the penny entered as
corporate_wealth, annually and monthly) and a genuine £0.10 excess; both fail
under the old tolerance. The six list descriptions and the changelog state
the qualified rule; the module claims are narrowed to UC eligibility and
uprating metadata.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Round-4 review of f64da77 (APPROVE WITH NITS):
- N2: the tests pinned the tolerance from above only. A new case uses
  pence-valued components (£16,015.98 + £286.98 + £81.05) whose float32 sum
  lands one unit in the last place (£0.00195) above £16,384.01: more than 1e-7
  of the value, less than 1e-6. It exposed that comparing
  itemised > corporate_wealth + tolerance in float32 rounds the tolerance up
  to a whole unit; the formula now compares the difference, which float32
  computes exactly for close values, and a 1e-7 tolerance now fails the case.
  Property-test components are drawn as pounds and pence.
- N4: the differential now also runs a projected year (2027, annual and
  monthly), with the original lists restored for 2026-2030.
- N1: the error-bound comment states it assumes float64 dataset tables.
- N5: stocks_and_shares_isa says it is part of corporate_wealth.

N3 (a microcosm export where corporate_wealth falls below the ISA) does not
apply to microcosm main: corporate_wealth there is corporate_wealth_excl_isa
plus the ISA, both drawn non-negative.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

UK hub (2026-10-13 16:16Z): #1965 (UC reg 77) merged as de511929d. The stack order is now #1974 → #1969 → #2131 → the uk-data lock bump. The next step here is to merge main and cap household_uc_pool_company_holdings at share_like_capital, as its owner planned, then a delta review and a hand-off to the hub. Note: main's release tests are currently red for an unrelated reason (test_award_never_rises_with_a_claimants_statutory_pay; a fix is in progress), so a failing Release test 3/4 on this PR is not this PR's fault.

🤖 Generated with Claude Code

@vahid-ahmadi vahid-ahmadi left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs changes before merge. The share_like_capital block is sound, but this head fails a main test in CI, and it needs main's #1965 company-holding cap moved onto the new block.

@MaxGhenis, this is a delta review. My last review was at 21412b6a; this one is at 365f642d. Since then the floored residual has become the share_like_capital block with a 1e-6 relative tolerance, and main has been merged in up to 976ec91fe. Main has since moved on to 714b1438c, which includes #1965.

Status of my earlier findings:

  • F1 (merge order with #1969): still applies. See finding 3.
  • F2 (unquoted shares): addressed. The docstring now sends unquoted shares to an expert valuation (H1679-H1681).

Findings:

  1. must. CI Test / Python 1/4 fails at this head, and this PR causes it. The failing test is tests/test_projection_horizon.py:181, test_unmapped_inputs_preserve_legacy_years_and_grow_after_2030. Main's #2376 asserts that cash_isa and stocks_and_shares_isa are unmapped inputs, which keep their legacy values through 2030. This PR lists both in data/uprating_indices.yaml (lines 96 and 123), so they are no longer in FALLBACK_INDEX_OF. I reproduced it locally on this head and on this head merged into current main. Main alone passes (13 passed).

    • Fix: drop the two names from that set. The per-index test above it then covers them.
    • Say in the body that a dataset storing ISA balances now grows them with per-capita GDP every year, where before it held them flat to 2030. Nothing on main reads either variable in a formula, so no result on main changes. "Impact: none" is still true for outputs, but not for the projected data.
  2. should. household_uc_pool_company_holdings (from #1965, now on main) caps the holdings it takes out of the pool at max_(0, corporate_wealth). That cap is in variables/gov/dwp/universal_credit/household_uc_pool_company_holdings.py:37. Once the capital lists count share_like_capital, a household that enters only components keeps the holding in its capital.

    • Probe on this head merged into main: a single UC claimant who stands as sole owner of a trading company, with a £10,000 holding, £5,000 savings and directly_held_shares of £10,000 (no corporate_wealth). UC capital is £15,000, and the pool holding is 0. The holding is disregarded under reg 77(2), so the capital should be £5,000.
    • The same household with corporate_wealth of £10,000 gives £5,000, as expected.
    • Fix: cap at share_like_capital, as the hub note says is planned. Add this case as a test, so the cap can't drift from the list again.
  3. should (merge order, still live). At these heads, #1969 + #1974 without #2131 drops the 10% on shares, as before.

    • On a #1969 + #1974 tree, £5,000 savings + corporate_wealth £12,000 gives £17,000 for UC and IS, at ages 40 and 70, and for PC at 70. #1969 alone gives £15,800.
    • The reason: #1969's sale_expenses.sources still names corporate_wealth, which this PR takes out of every capital.sources.
    • The hub's order (#1974, then #1969, then #2131) is safe only if the three land back to back, with #2131 retargeted to main and green before the first merge. Landing #2131 alone, after retargeting (it contains both), leaves no gap.
  4. nit. share_like_capital makes components count when corporate_wealth is absent, but total_wealth still adds only corporate_wealth. A household given only components has share-like means-test capital and zero corporate wealth in total_wealth. That's fine for now, since no dataset does this. A line in the share_like_capital docstring would stop someone relying on total_wealth for it.

What I checked:

  • Guidance. ADM H1656, H1665 and H1673-H1674 against the docstrings, from the current ADM H1 PDF. I also confirmed that HB (SPC) Regs 2006 Sch 6 para 26 is the home disregard ("The dwelling occupied as the home"), so the citation fix is right.
  • Block definition. Read share_like_capital.py against the body. The comparison is on the difference, so it doesn't round the aggregate up.
  • Probes. The ones above. I also ran an unsplit corporate_wealth household on this head merged into main, and it matches main exactly.
  • Tests. I ran the capital, share-component, wealth-component, stock-period, projection-horizon, PC reported-capital and savings-credit-only property files, code_health, and #1965's two company-owner files. I also ran the YAML under gov/dwp, finance/benefit, household/wealth and gov/local_authorities.
    • At this head: 1 failed (finding 1) and 2,333 passed; YAML 1,865 passed.
    • Merged into current main: 1 failed (the same) and 2,381 passed; YAML 1,964 passed. Current main 714b1438c on the same set: all 2,363 pass, and YAML 1,964 pass.
  • Lint. ruff check and ruff format --check on the 7 changed Python files: clean.
  • CI at 365f642d. Every check passes except Test / Python 1/4, which fails on finding 1 only (1 failed, 1,417 passed).
  • Overlap with other open PRs. #2382 also edits uprating_indices.yaml and growthfactors.md, but in other sections (the CPI list).
  • Not checked. Any dataset run. The WAS mapping in uk-data #501.

🤖 Generated with Claude Code

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci-recheck Re-triggers Fix evidence without changing the PR (UK hub, 2026-10-12)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants