Repository navigation
Conversation
vahid-ahmadi
left a comment
There was a problem hiding this comment.
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.sourceslists; - 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_wealthof £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 attests/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:
-
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.
-
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.
-
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. -
Nit.
tests/test_capital_share_components.py. You removed the itemising-invariance test on purpose. A one-line comment pointing to the replacement differential intest_capital_valuation_properties.pywould stop someone restoring it.
|
Independent review (GPT-6.1 Sol, UK hub sweep, 2026-10-12): REQUEST_CHANGES at VERDICT: REQUEST_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. GB regulations and programme manuals were reviewed; several NI references and the HMRC percentages remain unverified. Requested |
vahid-ahmadi
left a comment
There was a problem hiding this comment.
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:
-
must (carried in from #1969 and #1974). This head contains both CI failures and the #1965 interaction:
test_projection_horizon.py:181fails, as on #1974. The ISA variables are now mapped inuprating_indices.yaml.test_savings_credit_only_receipt_properties.py:508fails, 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 readscorporate_wealth.
- with
- On this head merged into main, the same 8
uc_company_analogous_business.yamlcases and 3test_uc_company_owner_properties.pyproperties 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_expensesat 90%, since an owner's shares are directly held shares.
Once the fixes land on #1969 and #1974, merge them through and re-run.
-
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. -
should.
parameters/gov/dwp/share_like_capital/without_sale_expenses.yaml:10-12still 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.
-
nit. The six
capital/sources.yamldescriptions each have an unwrapped line of about 175 characters. An example isuniversal_credit/means_test/capital/sources.yaml:8("share_like_capital_with_sale_expensesandshare_like_capital_without_sale_expensessplit ..."). Rewrap to match the rest. -
nit.
share_like_capital_with_sale_expenses.py:3hardcodesCOMPONENTSfor the "is anything itemised" gate, while the exempt list is a parameter. A reform adding a new exempt variable outsideCOMPONENTSwould 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 inwithout_sale_expenses.yamlwould 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.
- unsplit £5,000 +
-
Revert check.
capital_valuation_share_split.yamlrun 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
714b1438con the same set: all 2,363 pass, and YAML 1,964 pass.
-
Lint.
ruff checkandruff format --checkon the 11 changed Python files: clean. -
Repo process. The
.fixedfragment and the Reproducer and axiom lines are present, and the named reproducer case exists. The branch also carries #1974's.addedfragment, 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
Keep Northern Ireland LHA rates off Great Britain freeze and percentile parameters
…p-points Add PIP points-based assessment scaffold (#1069)
Resolve the conflict with #2523 (9387f49), which derives the UC benefit cap earnings threshold from 16 hours at the national living wage in uc_benefit_cap_earnings_exception_threshold. is_uc_benefit_cap_exempt_earnings now reads that variable, keeping this PR's per-claimant earned income comments and references. The PR's own dated parameter gov.dwp.benefit_cap_earnings_exemption is removed: main's variable gives the same 604 to 881 a month for 2020-21 to 2026-27 and also covers earlier years, so keeping both would be two sources for one threshold. The changelog fragment drops the parameter bullet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s-2542 Bus fares: follow published fares by area and year, and price cap changes from a fitted fare table
…-legacy-closure-scope
Uprate additional State Pension and protected payments by September CPI
…rectness Correct participation periods, spillover groups and seeded draws
…unterfactual Fix the inactive Universal Credit health-element counterfactual
…defects Pin benefit cap exemption rules with regression tests and legal references (#1818)
…lity Compute gross council tax from LA Band D amounts and statutory band ratios
Scope legacy-benefit closure to each award's owners
|
UK hub takeover for the Budget fix sprint (Max, 21 Oct). The owner session 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 |
|
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 |
Main moved UC capital to a financial pool (sources) and a property path (property_sources). Keep main's structure with share_like_capital in place of corporate_wealth, and budget reg 77 holdings against share_like_capital, the capital the pool counts (HOLDINGS_SOURCE). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…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 <noreply@anthropic.com>
|
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:
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 Methodology. d1396 is no longer parked as a blocker. Under Max's 2026-10-20 delegation it is a logged session ruling, Follow-ups owed:
🤖 Generated with Claude Code |
|
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):
What the rebase changes on this side, which e78257a already shows on b3f6f60:
The newer #1969 code also has a 🤖 Generated with Claude Code |
Stacked on #1969 (sale-expense deduction, head b3f6f60). It also merges #1974 (
share_like_capital, head 6600477). Once both are on main, this diff is the split below plus its tests.What
#1969 deducts 10% for the expenses of sale from
corporate_wealthas a whole. The guidance treats its parts differently:#1974's
share_like_capitaliscorporate_wealthas supplied, or the itemised components' sum where that is larger. This PR splits it in two:share_like_capital_with_sale_expenses:share_like_capitalless the itemised holdings listed ingov.dwp.share_like_capital.without_sale_expenses(unit and investment trusts, stocks and shares ISAs), floored at nil. When nothing is itemised, it is all ofshare_like_capital.share_like_capital_without_sale_expenses: the rest. It is exactly nil when nothing is itemised.All six capital lists (UC, HB, IS, JSA, ESA, PC) name both parts, and all six sale-expense lists name the first. (On main, UC's lists cover the financial pool; property is valued separately through
property_sources.)Reg 77 holdings. An owner-manager's disregarded company holding (UC reg 77(2)) is shares in a company, which have expenses of sale. So
HOLDINGS_SOURCE, which #1974 defines as the source holdings are budgeted against, becomesshare_like_capital_with_sale_expenses. The holdings budget in the pool and reported-capital variables, and the 90%counted_sharethat #1969 applies when holdings leave the pool, both read it, where #1969 readcorporate_wealth. On unitemised data this is the same value.Methodology (was d1396; now a logged session ruling). The UK hub's review asked whether the trust bucket and stocks and shares ISAs should escape the 10%:
Under Max's 2026-10-20 delegation for single-case readings, the session took the reading this PR encodes: neither the trust bucket nor stocks and shares ISAs take the 10%. The trust bucket takes unit trusts' treatment, and an ISA is valued at its withdrawal value. It is logged in the statute-readings register as
uk:means-test-pooled-funds-and-isas-sale-expenses.The exempt holdings are therefore one parameter list of whole variables. A ruling that moves a whole component, for example applying the 10% to stocks and shares ISAs, or to the whole trust bucket, is a one-line parameter change and needs no code change. A ruling that applies the 10% to only part of a component (say, the investment-trust share of the trust bucket) cannot be expressed in this list; it would need a weighting parameter and a formula change.
Impact
None on any dataset that does not itemise
corporate_wealth, which covers every released uk-data build. The differential test below checks this exactly.On a split dataset (PolicyEngine/policyengine-uk-data#501), WAS round 8 puts directly held shares at 32.9% of the share-like wealth (£217bn of £659bn). The average deduction on
corporate_wealththerefore falls from 10% to about 3.3%. Measured caseload effects will come from #501's release-equivalent builds, which are queued behind the shared heavy-run lock.Invariants (tests)
test_capital_share_components.py, Hypothesis with pounds-and-pence inputs (as a dataset stores them; the engine rounds each to float32), checked against the engine's float32 arithmetic, exactly for 1-3 and within £0.05 for 4:share_like_capitalkeeps the supplied aggregate unless the components exceed it beyond the float32 rounding tolerance.share_like_capitaland the other part is exactly 0. Otherwise the part with sale expenses equalsshare_like_capitalless trusts and ISAs, floored at nil, and the two parts sum toshare_like_capital.Fixed cases:
#1969's property tests keep their own legal asset set, now naming
share_like_capital_with_sale_expenses, and assert each programme's list against it. Their oracle values an unitemised household's share-like capital from itscorporate_wealth.Mutation check. Dropping the nothing-itemised branch from the formula fails the differential.
Round-2 review fixes (head 10c54a5):
ParameterNotFoundError. It now starts on 1996-10-07, with a 2012 regression for IS and JSA.add()returnsNonefor an empty list. The exempt holdings are now summed from a numeric zero, and an empty-list reform is tested.Round-3 review fixes:
share_like_capitalequal to the upratedcorporate_wealthin 2027.@example, as well as against the engine.Local runs (head e78257a):
ruff format --checkgov/dwp,household/wealthandgov/local_authoritiescode_healthThis PR targets #1969's branch, so GitHub runs no CI on it. CI and the Aggregate impact report run once #1969 lands and this PR retargets main.
Reproducer:
policyengine_uk/tests/policy/baseline/gov/dwp/capital_valuation_share_split.yaml::UC split of the same total exempts trusts and ISAs and crosses the limitaxiom: TheAxiomFoundation/rulespec-uk#395 queued (extended with the asset-type distinction: TheAxiomFoundation/rulespec-uk#395 (comment))
🤖 Generated with Claude Code