Skip to content

Value share-like capital by asset type for the sale-expense deduction - #2131

Open
MaxGhenis wants to merge 17 commits into
uc-capital-sale-expensesfrom
sale-expense-share-split
Open

MaxGhenis wants to merge 17 commits into
uc-capital-sale-expensesfrom
sale-expense-share-split

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Oct 4, 2026 •

Copy link
Copy Markdown
Collaborator

This PR values share-like capital by asset type in the means tests. It stacks on #1969 (head 3a3241cf6), where PolicyEngine deducts 10% for expenses of sale, and includes main at 2da212abc and #1974. This PR adds the asset split and its tests, updates the CI shard table, and documents secured-debt uprating.

Reproducer: policyengine_uk/tests/test_isa_balances.py::test_person_isa_balances_count_in_full_in_every_means_test

What changes

Under #1969, PolicyEngine takes 10% off all of share_like_capital. DWP tells decision makers to value its parts differently in ADM chapter H1:

Holding Guidance 10%
Quoted shares ADM H1665; DMG 29671, 52671, 84763; HBGM W1.513 Yes
Unit trusts Bid price, with no costs of sale even through a broker: ADM H1673-H1674; DMG 29680-29681, 52680-52681, 84771-84772; HBGM W1.530-W1.531 No
ISAs Withdrawal value, and no ISA paragraph applies the 10%: ADM H1656-H1658; DMG 29662-29664, 52662, 84748-84750 No

This PR splits share_like_capital in two:

  • PolicyEngine calculates share_like_capital_with_sale_expenses by subtracting the holdings in gov.dwp.share_like_capital.without_sale_expenses from share_like_capital, with a floor at nil. The list contains unit and investment trusts and stocks and shares ISAs. For households that itemise nothing, PolicyEngine keeps all share-like capital in this part and retains Value means-tested capital less 10% sale expenses and secured debt (UC reg 49 and legacy equivalents) #1969's flat 10%.
  • PolicyEngine puts the remainder in share_like_capital_without_sale_expenses, or nil when the household itemises no holding.

All six capital lists name both parts beside savings and cash ISAs: Universal Credit, Housing Benefit, Income Support, income-based JSA, income-related ESA and Pension Credit. Their sale-expense lists name the first part and exclude savings and cash ISAs.

Stocks and shares ISAs

On main, PolicyEngine sums cash_isa and stocks_and_shares_isa from each person's balance (#2712), and reads stocks_and_shares_isa as an itemised holding. It puts stocks and shares ISAs in the part without sale expenses, whether a dataset stores household totals or person balances. Each means test counts the ISA at its withdrawal value.

Under #1969, PolicyEngine takes 10% off the ISA, so this PR changes the expectation in test_isa_balances.py. The stored case supplies £1,000 of savings, a £3,000 cash ISA and a £2,000 stocks and shares ISA for 2025. UC capital in 2027 rises from (£1,000 + £3,000 + 90% × £2,000) × 1.07224 = £6,218.99 to (£1,000 + £3,000 + £2,000) × 1.07224 = £6,433.44. Per capita GDP growth of 4.0% in 2026 and 3.1% in 2027 compounds to 1.07224. A new test gives a single adult the same amounts and supplies their own ISA balances. It checks £6,000 of capital in all six means tests.

Regulation 77 company holdings

An owner-manager holds company shares that UC disregards under reg 77(2), and selling shares incurs expenses. This PR moves HOLDINGS_SOURCE from share_like_capital to share_like_capital_with_sale_expenses. PolicyEngine budgets company holdings against that source in the UC pool, so it never deducts them from unit trusts or ISAs. It removes the holding from the pool and any complete override at 90%, matching UC's valuation of shares. On data that don't itemise, PolicyEngine calculates the same value for both sources.

CI shard table

Main's shard_durations.toml names three share-component tests that this PR replaces, and test_ci_test_shards.py fails when a line names a test that doesn't exist. This PR points those lines at the successors and keeps their measured durations.

Growth factors page

This PR adds three secured-debt inputs to the per capita GDP list on growthfactors.md: owned_land_secured_debt, other_residential_property_secured_debt and non_residential_property_secured_debt. PolicyEngine already uprates them by per capita GDP through uprating_indices.yaml.

Methodology

The Wealth and Assets Survey asks about unit and investment trusts in one question. Investment trusts trade as quoted shares; we infer that decision makers apply H1665's 10% deduction to directly held investment trusts. HMRC's ISA statistics, Table 9.6, put investment trusts at 8.2% of pooled funds in stocks and shares ISAs at 5 April 2022 and 6.9% at 5 April 2025. Those figures describe ISA holdings, and we use that composition as a proxy when treating the combined trust bucket outside ISAs as unit trusts. The survey does not separate the two trust types.

We give the whole trust bucket the treatment of unit trusts and count ISAs at their withdrawal value with no deduction. Under H1656, decision makers value an ISA at what its holder would get by withdrawing it; the paragraph does not mention the 10%. PolicyEngine retains the 10% for directly held shares and all of an unitemised corporate_wealth.

A parameter edit can put a whole holding such as stocks and shares ISAs back under the 10%: remove it from gov.dwp.share_like_capital.without_sale_expenses. Applying the 10% to part of a holding, such as the investment trusts in the bucket, would require a weight parameter and a formula change.

Impact

CI compared #1969's head 3a3241cf6 with 2f1051e9f on the certified Enhanced FRS (enhanced_frs_2024_25.h5, policyengine-uk-data 1.58.1). It found no change in any household's net income from fiscal year 2025-26 to 2031-32, or in receipts, the government balance, poverty or inequality.

policyengine-uk-data (main at e1acdf966) imputes one corporate_wealth. It sums WAS non-defined-benefit pension wealth, employee shares and options, UK shares, investment ISAs, and unit and investment trusts. It doesn't impute the three holdings separately, so PolicyEngine retains #1969's flat 10% for every household in that dataset.

Microcosm's UK build writes directly_held_shares, unit_and_investment_trusts and stocks_and_shares_isa so that they sum to corporate_wealth (was_wealth.py at c0513dbae). On data from that build, PolicyEngine excludes trusts and stocks and shares ISAs from the 10% deduction. CI's aggregate report doesn't cover those data.

Invariants

test_capital_share_components.py uses Hypothesis to check these properties on pounds-and-pence inputs that the engine stores as float32:

  1. share_like_capital keeps the supplied corporate_wealth unless the itemised holdings exceed it beyond float32 rounding, and then equals their sum.
  2. With nothing itemised, the part with sale expenses equals share_like_capital and the other part holds nil. Otherwise the first equals share_like_capital less trusts and ISAs, never below nil, and the two parts sum to share_like_capital.
  3. On unitemised inputs, every programme's assessable capital and UC eligibility equal those under the lists before itemising, exactly, for 2026, January 2026, 2027 and January 2027.
  4. For a consistent split, the property checks that UC capital rises by 10% of a working-age adult's trusts and ISAs. It allows the greater of £0.05 and a relative tolerance of 1e-5.
  5. Given only itemised holdings, with the ISAs as person balances, all six means tests count savings, cash ISAs, trusts and stocks and shares ISAs in full. They count directly held shares less 10%. The property checks each total after the programme's rounding rule, to within £0.011.

The capital property suite from #1969 (test_capital_valuation_properties.py) keeps its oracle. The oracle specifies which assets take the 10% from the law, independently of the parameters. It maps the part with sale expenses to the household's corporate_wealth and the part without to nil. It still raises a KeyError for any source it doesn't map.

Fixed cases pin these results:

  • For £399,999.984375 of corporate_wealth beside £200,000 of trusts and a £199,999.984375 ISA, PolicyEngine leaves nil with sale expenses. It rounds both totals to £400,000 in float32 and sums the trusts and ISA before subtracting them;
  • For £16,000 of corporate_wealth beside £8,000.01 of trusts and an £8,000 ISA, PolicyEngine sums the holdings in float32 to £16,000.009765625. It leaves nil with sale expenses and £16,000 without;
  • With an empty exempt list, PolicyEngine deducts 10% from £12,000 of share-like capital whether the household itemises or not. UC counts £11,800 beside £1,000 of savings;
  • Income Support and income-based JSA count £1,000 of savings and £12,000 of corporate_wealth as £11,800 in 2012;
  • UC denies eligibility when a household holds a penny of shares beside £16,000 of savings. PolicyEngine preserves corporate_wealth when float32 rounding pushes the components' sum above £16,000 or £16,384.01;
  • PolicyEngine removes a £30,000 company holding from the UC pool at £27,000;
  • UC values £1,000 of savings, £4,000 of shares, £3,000 of trusts and a £5,000 ISA at £12,600;
  • UC and Pension Credit count itemised holdings in a monthly period without proration (£12,300, in test_stock_capital_periods.py).

Validation

Check Commit Result
CI Python tests, six shards (run 41246639348) 6a0d26d61 11,425 passed, 61 skipped, 2 xfailed
CI YAML tests, same run 6a0d26d61 5,451 passed
CI lint, documentation build, release build and smoke imports on Python 3.11 to 3.14, same run 6a0d26d61 passed
CI Python and YAML tests (run 41225440409) 2f1051e9f 11,424 passed, 61 skipped, 2 xfailed; 5,451 YAML cases passed
Aggregate impact against 3a3241cf6 (run 41225446226) 2f1051e9f no change in any year
Differential against 3a3241cf6 for 400 households that don't itemise: each programme's assessable capital and award, UC eligibility and the UC company-holding variables in 2025, 2026 and 2027, and UC capital, eligibility and award in January 2026 (58 outputs, 27,616 values) 2f1051e9f identical
test_isa_balances.py, test_wealth_components.py, test_isa_accounting.py, test_lifetime_isa_capital.py, test_uc_property_capital_mock.py, test_uc_company_override_properties.py, test_uc_company_owner_axiom_parity.py ce360946a 67 passed
code_health/test_ci_test_shards.py, test_stock_capital_periods.py 6a0d26d61 54 passed
YAML: capital_valuation_share_split.yaml, cash_isa_capital.yaml, isa_balances.yaml, lifetime_isa_capital.yaml 6a0d26d61 44 passed
ruff format --check, ruff check, --select F811,F821, git diff --check 6a0d26d61 clean

ce360946a differs from 2f1051e9f only in shard_durations.toml, and 6a0d26d61 adds the floor case to test_capital_share_components.py. CI at 6a0d26d61 passed every case in these files:

File Passed
test_capital_share_components.py 22
test_capital_valuation_properties.py 18
test_uc_property_capital.py 65
test_uc_company_owner_properties.py 7
test_uc_tax_free_childcare_choice.py 20
test_input_records.py 11
test_fiscal_partition.py 43

CI also passed the 70 cases in capital_valuation.yaml, lifetime_isa_capital.yaml, capital_valuation_share_split.yaml, cash_isa_capital.yaml and isa_balances.yaml.

We ran each mutant with Hypothesis shrinking off:

Mutant Commit Result
Stocks and shares ISAs back under the 10% 2f1051e9f killed: all 7 selected ISA-balance tests and all 3 selected share-component tests fail
HOLDINGS_SOURCE on all of share_like_capital 2f1051e9f killed: the regulation 77 test fails
Income-based JSA's capital list drops the part without sale expenses 2f1051e9f killed: the JSA list test, the valuation-by-type property (£7,600 against £15,600) and the JSA ISA-balance test (£4,000 against £6,000) fail
No floor at nil 6a0d26d61 killed: the floor case and its @example find −£0.009765625 with sale expenses
No branch for households that itemise nothing 6a0d26d61 killed: UC capital of £5,999.00 against £5,999.20 for £6,001 of savings and −£2 of corporate_wealth

axiom: TheAxiomFoundation/rulespec-uk#395 queued

We added the asset-type distinction and hand-computed companion cases to the issue (TheAxiomFoundation/rulespec-uk#395 (comment)).

🤖 Generated with Claude Code

MaxGhenis and others added 2 commits October 4, 2026 06:42
…eduction

corporate_wealth is now itemised in the capital sources lists (#1974), so the
sale-expense lists name directly_held_shares, which DWP values less 10% for
costs of sale (ADM H1665; DMG 29671/52671/84763; HBGM W1.513), and
unitemised_corporate_wealth, which keeps the previous flat treatment on
datasets that do not itemise. Unit trusts have no costs of sale even when sold
through a broker (ADM H1673-H1674; DMG 29680-29681, 52680-52681,
84771-84772; HBGM W1.530-W1.531), and an ISA is valued at what the holder
would get by withdrawing it, with no paragraph applying the 10% (ADM
H1656-H1658; DMG 29662-29664, 52662, 84748-84750). The survey bucket of
unit and investment trusts is treated as having no costs of sale; investment
trusts were 8.2% and 6.9% of the pooled funds in stocks and shares ISAs at
5 April 2022 and 2025 (HMRC ISA Table 9.6).

The property tests keep their own legal asset set and assert each
programme's list against it, and gain the split invariants: the residual,
backward compatibility with every component nil, conservation at a zero rate,
split effect = rate x (trusts + ISA), and no double counting. #1974's
itemising-invariance test no longer holds by design and is replaced by them.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

@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 b1a66004.

The "+10.5k lines" is the main merge carried in through #1974. The real diff over #1969 + #1974 (merged at their heads) is 11 files, +710/−125:

  • the six sale_expenses.sources lists;
  • the new capital_valuation_share_split.yaml;
  • the property tests;
  • test_stock_capital_periods.py;
  • a changelog fragment.

That's what I reviewed.

Guidance:

  • ADM H1674 is explicit: unit trusts have "no costs of sale. This applies even if persons use an agent, such as a stockbroker."
  • H1665 applies the 10% to quoted shares.
  • H1656 values an ISA at "what people would get if they withdrew their investment".

The lists now apply the 10% to directly_held_shares and the unitemised residual only, which is right for shares and unit trusts. SPC Sch V and UC Sch 10 have no disregard that changes this.

Probes:

  • savings £5,000, shares £4,000, trusts £3,000, ISA £5,000 (corporate_wealth £12,000) gives £16,600 for UC, HB and IS. The same holdings with only the components entered also give £16,600.
  • An unsplit corporate_wealth of £12,000 still gives #1969's £15,800.
  • Your monthly case (£12,300) matches.

Tests:

  • At the head: the capital-valuation, share-component, wealth-component, stock-period and PC reported-capital property tests, 26 passed. DWP, family and household YAML, 1,064 passed.
  • Merged into current main (the merge is clean): YAML, 1,389 passed. Properties: 19 passed and 2 failed. The two failures are the same stale applies() oracle as on #1969 (here at tests/test_capital_valuation_properties.py:240), not the new share-split properties. They'll clear when #1969's fix is carried through.
  • There's no CI on this PR, because its base isn't main, so these local runs are the only test evidence so far.

Findings:

  1. Should fix (merge order). Retarget to main as soon as #1969 merges, and get CI green on the retargeted head before merging. Until then, nothing hosted has run on these lists. This PR must land in the same window as #1969 and #1974 (see my notes there): with those two in and this one out, the 10% on shares disappears.

  2. Should fix. parameters/gov/dwp/universal_credit/means_test/capital/sale_expenses/sources.yaml:20-22, and the same sentence in the other five lists. The ISA exemption is weaker than the unit-trust one:

    • H1674 says outright that unit trusts have no costs of sale. For ISAs, the guidance is silent and you're inferring from H1656 ("withdrawal value").
    • A stocks-and-shares ISA holding quoted shares has to sell them through the ISA manager. That is a broker sale under H1605, and reg 49(1)(a) applies wherever "there would be expenses attributable to sale".

    I'd keep ISAs exempt, since most ISA money is in funds, but describe it as a judgement, like the investment-trust one, rather than "no ISA paragraph applies the 10%". Give the directly-held-shares share of stocks-and-shares ISAs from HMRC Table 9.6 alongside it.

  3. Nit. variables/input/directly_held_shares.py:9. Employee share options now take the 10% as "shares". An unexercised option isn't sold through a broker and is often not realisable at all. It's fine as an approximation, but say so in the list descriptions next to the investment-trust caveat.

  4. Nit. tests/test_capital_share_components.py. You removed the itemising-invariance test on purpose. A one-line comment pointing to the replacement differential in test_capital_valuation_properties.py would stop someone restoring it.

@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

Independent review (GPT-6.1 Sol, UK hub sweep, 2026-10-12): REQUEST_CHANGES at b1a660045. The full review is below. Blockers 1 and 2 are methodology calls the ADM leaves open; they are queued for Max as d1396. Findings 3–6 are for whoever picks this up after uk-data#501 lands (the triage deferred this PR until after 28 Oct).

VERDICT: REQUEST_CHANGES
Head reviewed: b1a6600

  1. [blocker] Investment-trust treatment requires Max’s methodology decision. UC sale_expenses/sources.yaml:14 exempts the combined unit-and-investment-trust bucket across all six programmes. ADM H1665/H1674 distinguishes quoted shares, which receive 10%, from unit trusts, which have no sale costs. Fix: obtain Max’s explicit decision; separate investment trusts or implement an approved proxy.

  2. [blocker] The ISA exclusion is an unresolved interpretation. UC sale_expenses/sources.yaml:20 infers exemption from the ISA paragraphs’ silence about 10%. ADM H1656 specifies withdrawal value without expressly overriding the general sale-cost rule. The input describes only “amount held”:7. Fix: have Max settle whether the input represents net withdrawal proceeds and how sale costs apply, then align all six lists and tests.

  3. [should-fix] Reconcile main’s reported-capital changes. Against local origin/main 3f174d0, merge-tree reports conflicts in growth-factor documentation, uprating indices, and uc_assessable_capital.py:10. Main Record benefit-unit capital once; derive UC and Pension Credit reported capital from it #2201 introduced shared reported-capital handling. Fix: update the stack while preserving its reported-capital formulas, documentation, and uprating entries.

  4. [should-fix] Clean merges still change newer regression expectations. pension_credit_assessable_capital.py:33 values unitemised £20,000 corporate wealth at £18,000. Main’s new CTR regression:660 expects £20,000. Fix: recalculate its capital, deemed-income and savings-credit expectations, and test the newer HB/CTR routes after reconciliation.

  5. [should-fix] Impact evidence is missing. The PR’s Impact section defers measurement to the data PR. test_capital_valuation_properties.py:336 establishes compatibility when every component is zero; it does not establish enhanced-FRS impact against main. Fix: provide an actual Microsimulation comparison with dataset/version, simulation year, both SHAs, commands and traceable outputs, including any measured zero result.

  6. [should-fix] The stacked diff includes protected and unrelated changes. Its actual base-to-head diff spans 164 files, including simulation.py:235, tests/conftest.py:19, and unrelated tax, earnings and housing changes inherited from main. Fix: refresh the prerequisites/base so the final review diff isolates the exemption changes.

The stated mechanisms and invariants match the code under the chosen methodology. Property tests cover the load-bearing logic; replacing the old itemising-invariance test is justified. The exemption commit adds no skips or xfails. Ruff formatting and lint passed for its three changed Python files.

Shell testing was attempted below the 30 GB compressor threshold. pytest -n 0 lacked xdist; three property files failed collection because Hypothesis was missing. The serial subset collected six tests but was interrupted before any ran. No independent test passes are claimed.

GB regulations and programme manuals were reviewed; several NI references and the HMRC percentages remain unverified. Requested gh commands failed network access, so web and local Git evidence were used. No files were changed.

@MaxGhenis MaxGhenis added the ci-recheck Re-triggers Fix evidence without changing the PR (UK hub, 2026-10-12) label Oct 12, 2026
MaxGhenis and others added 3 commits October 12, 2026 10:59
…ale-expense-share-split

# Conflicts:
#	docs/book/assumptions/growthfactors.md
#	policyengine_uk/data/uprating_indices.yaml
#	policyengine_uk/tests/test_capital_valuation_properties.py
…le-expense-share-split

# Conflicts:
#	docs/book/assumptions/growthfactors.md
#	policyengine_uk/data/uprating_indices.yaml
#	policyengine_uk/tests/test_capital_share_components.py
#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 <[email protected]>
@MaxGhenis MaxGhenis changed the title Exempt unit trusts and stocks and shares ISAs from the sale-expense deduction Value share-like capital by asset type for the sale-expense deduction Oct 12, 2026
MaxGhenis and others added 5 commits October 13, 2026 08:50
…uping

Review of b7e6bc5:
- gov.dwp.share_like_capital.without_sale_expenses started in 2013, after
  the legacy capital lists (JSA from 1996-10-07), so a 2012 simulation with
  unitemised corporate_wealth raised ParameterNotFoundError; it now starts at
  1996-10-07, and Income Support and JSA capital in 2012 are tested (both
  fail with the 2013 start).
- An empty exempt list made add() return None; the exempt holdings are now
  summed from a numeric zero, and an empty-list reform (every share-like
  holding less 10%) is tested.
- The property oracle now follows the engine's float32 grouping (components
  left to right, exempt holdings summed before subtraction) and compares
  exactly; the reviewer's example (trusts £200,000, ISA £199,999.984375) is
  pinned.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
…le-expense-share-split

# Conflicts:
#	policyengine_uk/tests/test_capital_share_components.py
…le-expense-share-split

# Conflicts:
#	policyengine_uk/tests/test_capital_share_components.py

@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 split itself is right and well tested. But this branch carries both CI failures from #1969 and #1974, it inherits the #1965 holding problem in both forms, and nothing hosted has run on it.

@MaxGhenis, this is a delta review. My last review was at b1a66004; this one is at 6a5a13a4. Since then the split has been rebuilt on #1974's block: share_like_capital_with_sale_expenses and _without_sale_expenses, with an exempt-holdings parameter. I reviewed the diff against #1969 + #1974 merged at their current heads: 21 files, +678/−186.

Status of my earlier findings:

  • F1 (retarget to main, CI): still open. See finding 2.
  • F2 (ISA exemption described as a judgement): partly addressed. The body now has a methodology note, but the parameter text still states it as settled. See finding 3.
  • F3 (employee share options caveat): not taken. See finding 5.
  • F4 (invariance test comment): addressed. The module docstring now points to the differential.

Findings:

  1. must (carried in from #1969 and #1974). This head contains both CI failures and the #1965 interaction:

    • test_projection_horizon.py:181 fails, as on #1974. The ISA variables are now mapped in uprating_indices.yaml.
    • test_savings_credit_only_receipt_properties.py:508 fails, as on #1969. It expects 601.02 where reg 19 gives 621.82.
    • Company holdings, probed on this head merged into current main. Take a single UC claimant who is sole owner of a trading company, with a £10,000 holding and £5,000 savings:
      • with corporate_wealth £10,000, UC capital is £4,000 (expected £5,000);
      • with only directly_held_shares £10,000, UC capital is £14,000 (expected £5,000), because the pool cap still reads corporate_wealth.
    • On this head merged into main, the same 8 uc_company_analogous_business.yaml cases and 3 test_uc_company_owner_properties.py properties fail as on #1969. Five of those YAML cases are the holding problem; three are the plain 90% revaluation and only need new expectations (see my #1969 review).
    • Here the holding should come off share_like_capital_with_sale_expenses at 90%, since an owner's shares are directly held shares.

    Once the fixes land on #1969 and #1974, merge them through and re-run.

  2. must. No CI has run on this PR, because its base is uc-capital-sale-expenses. The body's "Local runs" line is still "pending". Retarget to main and get the full suite green before anything in the stack merges. If this PR lands alone, it carries #1969 and #1974 with no window where the 10% on shares disappears. On a #1969 + #1974 tree without this PR, £5,000 savings + corporate_wealth £12,000 gives £17,000 for UC; this head gives £15,800.

  3. should. parameters/gov/dwp/share_like_capital/without_sale_expenses.yaml:10-12 still says "no ISA paragraph applies the deduction", as if that settled it. The changelog fragment states the ISA exemption flatly too.

    • ADM H1656 and DMG 29662 value an ISA at "what people would get if they withdrew their investment" and say nothing about costs of sale.
    • The body parks this, and the investment-trust question, for a decision (d1396).
    • Until it's decided, the parameter text should call the exemption a judgement, as it already does for investment trusts. I'm not suggesting which way to decide.
  4. nit. The six capital/sources.yaml descriptions each have an unwrapped line of about 175 characters. An example is universal_credit/means_test/capital/sources.yaml:8 ("share_like_capital_with_sale_expenses and share_like_capital_without_sale_expenses split ..."). Rewrap to match the rest.

  5. nit. share_like_capital_with_sale_expenses.py:3 hardcodes COMPONENTS for the "is anything itemised" gate, while the exempt list is a parameter. A reform adding a new exempt variable outside COMPONENTS would be ignored for a household that itemises only that variable. Consider a comment saying the exempt list must be a subset of the components, or a test asserting it. Separately, employee share options still take the 10% as "directly held shares" with no caveat (my F3). One clause in without_sale_expenses.yaml would cover it.

What I checked:

  • Guidance, re-read from the current PDFs:

    • ADM H1656-H1658 (ISA at withdrawal value; nothing on costs of sale);
    • ADM H1673-H1674 and DMG 29680-29681 (unit trusts: "no costs of sale ... even if persons use an agent");
    • DMG 29662 and 29664 (ISA), and DMG 29671 (10% on shares).

    The paragraph numbers in the parameters match. I didn't check the NI or HBGM numbers, or the HMRC Table 9.6 percentages.

  • Probes on this head:

    • unsplit £5,000 + corporate_wealth £12,000 gives £15,800 (UC, IS; PC at 70), as on #1969;
    • split shares £4,000, trusts £3,000, ISA £5,000 gives £16,600;
    • components only with £1,000 savings gives £1,000 + 90% × £4,000 + £3,000 + £5,000 = £12,600, matching the body.
  • Revert check. capital_valuation_share_split.yaml run on #1969 + #1974 without this PR's source changes: 10 failed. At this head: 10 passed.

  • Tests. I ran the same property files and YAML directories as on #1969 and #1974, plus #1965's company-owner tests.

    • At this head: 2 failed (the two carried CI failures) and 2,360 passed; YAML 1,889 passed.
    • Merged into current main (clean): 5 failed (those two plus the three #1965 properties) and 2,405 passed; YAML 8 failed (company cases) and 1,980 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 11 changed Python files: clean.

  • Repo process. The .fixed fragment and the Reproducer and axiom lines are present, and the named reproducer case exists. The branch also carries #1974's .added fragment, which is fine if it lands as one unit.

  • Overlap with my open PRs. #2382 merges cleanly with it.

  • Not checked. Any dataset run, or uk-data #501.

🤖 Generated with Claude Code

@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

UK hub takeover for the Budget fix sprint (Max, 21 Oct). The owner session local_b8fb4700 has been idle since mid-October. The hub woke it on 21 Oct at 23:58Z, and it did not start. To land this before the Budget model locks (the first pe-uk release cut by about 12:00Z Thu 22 Oct), the hub has taken it over as build 20261021-211254-sprint-1974-2131, which merges main, re-tests, pushes as a fast-forward and marks it ready. The hub then reviews it and lands it through the merge queue.

If the owner session wakes, please don't push to this branch; comment here instead, so there's only one writer.

🤖 Generated with Claude Code

@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

UK hub: takeover withdrawn. The owner session has been woken (22 Oct 01:25Z) and is finishing this PR itself, so the hub's build is cancelled. One writer: the owner session.

🤖 Generated with Claude Code

MaxGhenis and others added 2 commits October 21, 2026 23:00
…ale-expense-share-split

# Conflicts:
#	policyengine_uk/parameters/gov/dwp/universal_credit/means_test/capital/sale_expenses/sources.yaml
#	policyengine_uk/parameters/gov/dwp/universal_credit/means_test/capital/sources.yaml
Reg 77 holdings are shares with sale expenses, so HOLDINGS_SOURCE is
share_like_capital_with_sale_expenses: the holdings budget and the 90%
counted share both read it, where #1969 read corporate_wealth. The test file
keeps the round-3 fixes (projected years, the 16,000 overshoot, the
power-of-two precondition, the grouping example, the exempt list's start).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

Hand-off to the UK hub (owner session local_b8fb4700). Head e78257a: #1969's b3f6f60 plus #1974's 6600477, with this PR's split on top.

Depends on:

This PR targets #1969's branch, so GitHub runs no CI on it. Retarget it to main once #1969 lands; CI and the Aggregate impact report then run.

Local results at e78257a:

  • The affected pytest files (share components, capital-valuation properties, the UC company owner, override and parity tests, UC property capital, reported capital, savings-credit-only, Lifetime ISA, stock periods, wealth components): 166 passed.
  • YAML for the benefit families, gov/dwp, household/wealth and gov/local_authorities: 2,315 passed.
  • code_health: 2,554 passed.
  • ruff: clean.

Reviews. Round 3 (6a5a13a) asked for changes. All its findings are fixed here: the projected-year differential, the £16,000 overshoot test and the power-of-two precondition from #1974 are restored; the grouping @example, the exempt-list start invariant and the body wording are added. The rebuild on #1969's new structure also adds the reg 77 HOLDINGS_SOURCE change, explained in the body. The hub's independent review is owed at e78257a.

Methodology. d1396 is no longer parked as a blocker. Under Max's 2026-10-20 delegation it is a logged session ruling, uk:means-test-pooled-funds-and-isas-sale-expenses in ~/reviews/us-hub/source-conflicts/RULINGS.md, and d1396 has a note saying so.

Follow-ups owed:

🤖 Generated with Claude Code

@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

Hand-off addendum. Since the hand-off above, #1969 has moved past b3f6f60 to 9b9ef66 after its Sol v4b and v5 reviews, and is in review v6. So e78257a is not the head to review or land.

Agreed sequence, with #1969's owner (local_c12c197a):

  1. Itemise corporate_wealth's share-like components for the means tests #1974 lands.
  2. Value means-tested capital less 10% sale expenses and secured debt (UC reg 49 and legacy equivalents) #1969 merges main and makes the corporate_wealth → share_like_capital rename itself, from the checklist in Value means-tested capital less 10% sale expenses and secured debt (UC reg 49 and legacy equivalents) #1969 (comment).
  3. Value means-tested capital less 10% sale expenses and secured debt (UC reg 49 and legacy equivalents) #1969 lands.
  4. This PR merges Value means-tested capital less 10% sale expenses and secured debt (UC reg 49 and legacy equivalents) #1969's final head (which by then includes Itemise corporate_wealth's share-like components for the means tests #1974) and retargets main. The independent review runs at that head.

What the rebase changes on this side, which e78257a already shows on b3f6f60:

  • HOLDINGS_SOURCE becomes share_like_capital_with_sale_expenses;
  • the six sale_expenses lists name the part with sale expenses;
  • the property oracle maps that part to corporate_wealth and the other part to nil.

The newer #1969 code also has a whole_penny_situation property that sets corporate_wealth. It is unitemised, so it should need no change.

🤖 Generated with Claude Code

MaxGhenis and others added 5 commits October 31, 2026 12:03
#2131 last merged #1969 at b3f6f60. The merge brings in #1969's float64,
penny-rounded valuation, Lifetime ISA surrender values, cash ISAs in every
capital list, the land secured-debt input and main at 2da212a.

Conflicts and how they were resolved:

- Six sale-expense lists: #1969 renamed corporate_wealth to
  share_like_capital; this branch names share_like_capital_with_sale_expenses.
  Kept this branch's lists (land, both property sources and the part with
  sale expenses) and descriptions, and restored #1969's note that an
  unitemised corporate_wealth still folds in unit trusts, ISAs and, on older
  datasets, pension wealth. Savings and cash ISAs stay out of every list.
- UC capital/sources.yaml: savings, cash_isa (from #1969) and both
  share-like parts.
- household_uc_pool_company_holdings, household_uc_reported_financial_capital
  and uc_assessable_capital: #1969's code (exact_amount, valued_person_capital,
  penny rounding, the recorded-total consumption rule) with this branch's
  wording: a reg. 77 holding sits in, and is budgeted against, the part with
  sale expenses (HOLDINGS_SOURCE).
- test_capital_share_components.py: this branch's version, which already
  carries #1969's 11,800 and 27,000 expectations and replaces each #1974 test
  #1969 adapted with its split equivalent.
- test_capital_valuation_properties.py: #1969's oracle (asset() lookup, cash
  ISAs generated, per-person LISA rounding) with the split mapped into it:
  the part with sale expenses reads corporate_wealth, the part without is nil.
- growthfactors.md: #1969's text and savings-income section, with the
  per-capita GDP row as the union of both sides less other_investment_income
  and savings_interest_income, which main moved to per_capita.savings_income.

Long description lines in five legacy capital lists are wrapped, and Pension
Credit's list description names the two parts.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
#1969's fc066bd made main's test_isa_balances.py expect UC's 10% on a
stored stocks-and-shares ISA total, because the ISA then reached capital
through share_like_capital, which #1969 values less 10%. With the split, an
itemised ISA sits in share_like_capital_without_sale_expenses and counts at
its withdrawal value (ADM H1656), so UC capital is (1,000 + 3,000 + 2,000) x
growth: 6,000 x 1.07224 = 6,433.44 in 2027.

A new parametrised test pins the person-balance route in all six means
tests: a £1,000 saver with a £3,000 cash ISA and a £2,000 stocks-and-shares
ISA has £6,000 of capital, the ISA wholly in the part without sale expenses.

The changelog fragment loses its leading list marker, which towncrier would
render as a double bullet.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
A Hypothesis property gives single adults only itemised holdings, with the
ISAs as their own person-level balances, and checks all six means tests
against the law: savings, cash ISAs, unit trusts and stocks and shares ISAs
in full, directly held shares less 10%, each rounded by the programme's rule,
to within a penny of float32 noise. A pinned example gives £15,600.

The list test also asserts that each capital list names cash_isa and that no
sale-expense list names savings or cash_isa.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
…R renamed

test_ci_test_shards.py fails on any shard_durations.toml entry that names a
missing test. Main's table lists three tests that this PR replaced with its
split versions:

- test_capital_matches_the_original_lists became
  test_unitemised_inputs_match_the_lists_before_itemising, the same
  four-period differential on the unitemised households;
- test_share_like_capital_keeps_the_supplied_aggregate became
  test_share_like_capital_and_its_parts;
- test_float32_rounding_at_the_limit_keeps_eligibility became
  test_float32_rounding_at_the_limit_keeps_the_aggregate.

Each line keeps its measured duration, as the table's header asks of a
renaming pull request, until the table is regenerated. Every one of the 566
entries now names a test that exists.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
When trusts and an ISA exceed corporate_wealth only by float32 rounding,
share_like_capital keeps corporate_wealth and subtracting the exempt
holdings goes below nil. £16,000 of corporate_wealth with £8,000.01 of
trusts and an £8,000 ISA leaves -£0.009765625 without the floor. No fixed
case reached the floor before: the grouping case lands at exactly nil
either way. A fixed test and a matching @example on the property pin it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

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.

2 participants