Repository navigation
Conversation
UC Regs 2013 reg. 49(1) and its legacy equivalents (HB Regs 2006 reg. 47, HB (SPC) Regs 2006 reg. 45, IS Regs 1987 reg. 49, JSA Regs 1996 reg. 111, ESA Regs 2008 reg. 113, SPC Regs 2002 reg. 19) calculate capital at market or surrender value less 10% where a sale would incur expenses and less any encumbrance secured on it. The model summed every capital source at full value. Each programme gets a capital.sale_expenses node (rate 0.1 and the sources whose sale incurs expenses: land, property and corporate_wealth, not cash savings). A shared helper applies the 10% before the encumbrance and never values an encumbered asset below nil. New household inputs carry debt secured on other residential property, non-residential property and land; the Enhanced FRS has none, so they default to zero. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Each programme's sale-expense parameters now cite its GB and NI regulations, the guidance paragraph that orders the 10% before the encumbrance, and per-asset guidance with exact PDF page anchors (DMG ch 52 for ESA, HBGM Annex F for HB savings, H1656/H1665/H1673-H1675 for UC). - The source-list descriptions and changelog say plainly that corporate_wealth also holds unit trusts, ISAs, employee share options and, in datasets built before the pension split, non-DB pension wealth. - The helper and secured-debt docs say debts net within a source category. - The property-test oracle fixes the sale-expense assets and the 10% itself instead of reading the parameters, checks pension-age HB, and runs on larger populations. New hand-computed cases cover HB second homes and premises, per-source flooring between two encumbered sources, pension-age HB, and a monthly secured debt. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
HBGM W1.430 states only the general 10% rule; the rule that real property always has costs of sale comes from R(IS) 21/93, as DMG 29622 applies it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Resolve the capital variables onto main's claimant/partner allocation and person-level sources, valuing household sources through valued_capital. Update the let-property tests #1950 added: £10,000 of property is now £9,000 of capital under reg. 49(1), so 12 tariff steps, not 16. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Two tests main added after this branch's earlier base assumed full-value capital: - The CTR savings-credit-only case holds savings 10,000 and a 7,000 second property: capital is 10,000 + 90% of 7,000 = 16,300 (SPC Regs 2002 reg. 19(a)), still over the 16,000 limit, so CTR stays nil. Deemed income is 13 steps x 52 = 676, Pension Credit income 13,314 + 676 = 13,990, and savings credit 60% of (12,376 - 208.07 x 52) less 40% of (13,990 - 12,376) = 288.22. - The reported-capital property test's proxy oracle now values land, property and shares at 90% and savings at face value. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Keep main's recorded benefit-unit capital (benunit_reported_capital), which replaces the valued household sources as recorded, and register the secured-debt inputs beside main's uprating entries. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
£20,000 of shares is £18,000 of Pension Credit capital (SPC Regs 2002 reg. 19(a)), still over the CTR limit: deemed income 16 x 52 = 832, Pension Credit income 14,146, savings credit 933.816 - 0.4 x 1,770 = 225.82. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Main's Housing Benefit passport (#1927 and the Universal Credit limb) gives a family on a passporting benefit nil HB capital, so the property's independent reg 47 valuation applies only to non-passported families. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review of 54ed063: half the generated units now decline Universal Credit, so the Housing Benefit passport cannot remove every working-age unit from the reg 47 differential, and a passported unit's HB capital is asserted to be nil rather than skipped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Delta review of b541677 (F1): coverage of the HB passport-nil assertion and the working-age reg 47 differential depended on Hypothesis draws. The checks move into check_valuation(), which counts the branches it ran, and a fixed test asserts both run: a UC claimant with £5,000 of savings (passported, nil HB capital) and a non-claimant with a mortgaged second home (valued under reg 47). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Savings 12,000 plus a 5,000 property is 12,000 + 4,500 = 16,500 of Pension Credit capital (SPC Regs 2002 reg. 19(a)), still over the 16,000 CTR limit. Deemed income is 13 a week, so the savings credit is 0.6 x 29.93 - 0.4 x 15 = 11.958 a week (621.82). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Main now values Universal Credit property on its own path (#2439: property_sources, secured debt, a 10% property_sale_cost_rate) and carries regulation 77 company substitution (#1965). Reconcile: - One UC rate: property_sale_cost_rate becomes capital.sale_expenses.rate, used by the property path and the financial pool; UC sale_expenses.sources lists the financial source with costs of sale, corporate_wealth. - The financial pool is valued with valued_capital, so shares count at 90%. - As the second of #1965 and #1969 to land, value reg. 77 holdings where they leave capital: at 90% from the pool (household_uc_pool_company_holdings), in the override floor (household_uc_reported_financial_capital) and from a complete override, which is a countable figure; in full from a recorded financial total, which is used unvalued. A shared counted_share helper. - Keep main's secured-debt inputs, documenting the legacy equivalents, and add owned_land_secured_debt in the same shape. - Tests main added that assumed full-value shares, rewritten from the law: the reg. 77 company cases, the property-capital and mock suites, the company-override conservation identities and the savings-credit-only cases. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… penny From the GPT-6.1 Sol review of a7d8988: - A recorded benunit_reported_capital total holds its owner's reg. 77 holding at full value, while the pool counts that holding at 90%. The report now consumes its known holding part at the counted 90% (household_uc_reported_financial_capital), so it can no longer eat another unit's savings; the owner's own assessment still takes the full holding off the report. The company-override conservation oracle consumes the same way, and a YAML regression pins the reviewer's case: £16,500 of savings and a £10,000 report that is all holding give [0, 16,500]. - valued_capital rounds the 90% value down to the penny in the claimant's favour (ADM H1665), in float64 with a guard against binary noise. A YAML regression puts £1,000 of savings and £16,666.67 of shares at exactly £16,000.00, which passes the limit (WRA 2012 s. 5(1)(a), UC reg. 18(1)). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Inputs are float32, so 90% of £16,001 came to £14,400.8994 and the penny floor gave £14,400.89 (the HB £16,001 case failed). less_sale_expenses takes the value to the penny in float64 before the 10% deduction and rounds down to the penny (ADM H1665). The UC property path now uses it too, so every reg. 49(1)(a) valuation rounds the same way. Adds a Hypothesis property checking 90% of every whole-penny float32 input up to £131,071.99 against integer pence, and a UC YAML case. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sol review v4b found that rounding the input to the penny first could round up (the ADM H1665 example gave 930.94, not 930.93), that nothing was rounded at the last stage, and that UC rounds to the nearest penny. - capital.round_down per programme: false for UC (UC Regs 2013 reg 6(1); NI reg 7(1)) and HB (HB Regs 2006 reg 80; HB (SPC) reg 61(6); NI 78(8), 59(7)); true for IS (reg 27), JSA (reg 92), ESA (reg 3(a)) and Pension Credit (reg 24A), which count a fraction only to the claimant's advantage. - exact_amount removes float32 storage error only where a value is the stored form of a whole penny; other values are kept exactly. - Each valuation, each UC holding and property share, and each programme's assessed capital (the last stage) is rounded by that rule. - A recorded financial total below its owner's holding now consumes the holding at its counted value, as an override does, so no part of the holding is left for another unit. Tests: the H1665 example (930.93 / 930.94), whole-penny properties for both rules, a never-wrong-way bound for any float32 input, UC half-penny, 16,000.00 after a holding, a three-way split, IS shared-capital rounding, and reports below the holding in the reg 77 conservation grid. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sol review v5 at b3f6f60: - exact_amount raised an underflow warning on zero inputs, failing test_warning_filters. Errors are now ignored inside np.errstate, and twice the gap is compared with the spacing. - IS, JSA and ESA read household values through benunit.max, which is float64, so exact_amount used float64 spacing and never removed float32 storage error: 16,000.01 of savings floored to 16,000.00, at the limit. A value equal to its float32 is now judged at float32 spacing. - From 131,072 float32 cannot hold every penny, so no penny is identified and the stored value is kept; rounding down then never goes against the claimant. - The Lifetime ISA's 75% and UC company capital are worked in float64 from the stored amounts; company capital is rounded under reg. 6(1). - test_uc_capital_allocation expected exact conservation; each share is now its own nearest-penny amount (1.00 three ways is 0.33 each). - Docstrings: the guard's intended tolerance; no report leaves a holding in the pool. Tests: a model-level property that every programme counts whole-penny savings, shares and Lifetime ISA balances to the penny; widened-input and above-131,072 helper checks; YAML for 16,000.01 of savings under IS, JSA and ESA and a 1,000.04 Lifetime ISA. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#1974 renamed the share-like capital source from corporate_wealth to share_like_capital. The six sale-expense lists, the three holding valuations (counted_share) and the two holding budgets now name it through HOLDINGS_SOURCE, so shares keep the reg. 49(1)(a) deduction. The sale-list guard now also requires share_like_capital in each list, the differential's original lists rename the sale-expense lists too, and a components-only household counts 1,000 + 90% of 12,000 = 11,800 under UC. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s rule The engine stores lifetime_isa_countable_capital as float32, so 75% of a whole-penny balance that ends in half a penny (75% of 0.26 is 0.195) was read back just below it and rounded the wrong way under UC and HB: 0.26 of LISA and 15,999.81 of savings gave UC capital of 16,000.00, eligible, instead of 16,000.01. valued_person_capital now works a LISA's surrender value out in float64 from the balance and rounds each person's to the penny under the programme's rule, like any other valuation, in all six means tests. The whole-penny property draws any whole-penny balance, with the half-penny cases as examples, and two YAML cases pin the limit case and per-partner rounding. exact_amount's docstring no longer claims that rounding down never goes against the claimant above 131,072: a figure built from a stored value there can be a penny either way. The float32 cast moves inside errstate. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Merge the capital sale-expense prerequisite without rewriting its history, preserve share-component routing, and include worldwide property while counting UK land once. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Keep UK wrapper integration coverage for dataset loading, uprating, monthly stocks, purchases and LVT while avoiding repeated setup for formula-only draws. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Aggregate impact on certified data: passBase Checks: 0 broken by this change, 1 failing on both sides, 0 fixed. Moves flagged for review: 204 (thresholds in Notes
Moves flagged for review
164 more flags are in the report artifact. Receipts by tax head
Spending by benefit and service
Caseloads
Government balance, each component counted once
Model totals
Household income totals
Poverty rates
People in poverty
Inequality
Mean household net income in each side's own deciles
Standard reform costings (change in gov_balance)
Standard reforms: change in each reform's target
Household net income by decileMean change in household net income per household, and as a share of the decile's base income; households stay in the base run's deciles.
No change in any year: population. Checks failing on both base and head (1)
Counted-once checkEach of 92 components was raised by £1 per unit in 2026-27, and every head of its totals was read household by household: its own head must rise by exactly £1, no other tax head may move, no other benefit may rise, and the model's own total must move by what its heads moved. A head that breaks a rule is measured again twice: with every other head held at its baseline value, and with every other head held at its value after the first £1 while the component rises again. It fails where either still shows the response. 0 failed; 0 were skipped.
Provenance
Dataset: Receipts by tax head: head levels
Spending by benefit and service: head levels
Caseloads: head levels
Government balance, each component counted once: head levels
Model totals: head levels
Household income totals: head levels
Population: head levels
Poverty rates: head levels
People in poverty: head levels
Inequality: head levels
Mean household net income in each side's own deciles: head levels
Standard reform costings (change in gov_balance): head levels
Standard reforms: change in each reform's target: head levels
|
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
juaristi22
left a comment
There was a problem hiding this comment.
Program review
Base repository: PolicyEngine/policyengine-uk
PR number: 2688
Reviewed head SHA: 442f033
Merge base SHA: 5b94537
Mode: full
Scope: All77 changed property-accounting/capital-valuation paths and affected six-benefit/land/wealth/override dependencies, including composed1969prerequisite; no separate1969review or privateproducer audit
Source manifest: /private/tmp/policyengine-command-runs/881387a1b745/pr2688-hb1241-review-20261027-review-sources.json
Review status: PARTIAL
Source Documents
- authorized-1.original — cached original.
- authorized-2.original — cached original.
- authorized-3.original — cached original.
- authorized-4.original — cached original.
- authorized-5.original — cached original.
- authorized-6.original — cached original.
- authorized-7.original — cached original.
- authorized-8.original — cached original.
- authorized-9.original — cached original.
- authorized-10.original — cached original.
- authorized-11.original — cached original.
- authorized-12.original — cached original.
- authorized-13.original — cached original.
- authorized-14.original — cached original.
- authorized-15.original — cached original.
- authorized-16.original — cached original.
- authorized-17.original — cached original.
- authorized-18.original — cached original.
- authorized-19.original — cached original.
- authorized-20.original — cached original.
- authorized-21.original — cached original.
- authorized-22.original — cached original.
- authorized-23.original — cached original.
- authorized-24.original — cached original.
- authorized-25.original — cached original.
- authorized-26.original — cached original.
- authorized-27.original — cached original.
- authorized-28.original — cached original.
- authorized-29.original — cached original.
- authorized-30.original — cached original.
- authorized-31.original — cached original.
- authorized-32.original — cached original.
Critical
C1 — Preserve non-financial property when a financial-only total is reported (critical) (OPEN)
Location: policyengine_uk/parameters/gov/dwp/universal_credit/means_test/capital/sources.yaml:59–60, the new placement of overseas/other property in the financial replacement pool. Affected calculation: uc_assessable_capital.py:63–65,77–112.
benunit_reported_capital.py:9–25 explicitly defines a financial observation that excludes property, with zero meaning no financial capital. It also identifies the separate uc_reported_capital input for a complete countable-capital override. The new source-list entries put real property in a pool that is replaced when this financial-only report is nonnegative. uc_assessable_capital.py:99–103 then selects the financial report, losing the new property; lines104–112 add only the separate existing domestic-property valuation.
A consistent direct-input case is a single otherwise eligible claimant who owns an ordinary, freely transferable overseas investment property worth£20,000, has no secured debt or relevant statutory disregard, and reports£0financial capital. Set overseas_property_value=20000, benunit_reported_capital=0, no domestic countable property, no company holdings, no complete UC override (default−1), and zero other financial sources. These inputs are consistent: property ownership does not contradict a zero cash/investment report. The new code source trace gives assessable capital£0; without that financial report the same new property is valued at£18,000. A financial-only report does not extinguish an owned property.
UC regulation49(1),(2) requires valuation of owned overseas assets, with the sale-cost deduction and secured encumbrances taken into account. Official ADM H1601–H1608 explains that non-disregarded capital is included, foreign assets are valued, and real property incurs sale costs. Schedule10 provides actual disregards; the case above deliberately satisfies none. The correct countable amount for the supplied debt-free asset is£18,000. This is material to the implemented benefit: is_UC_eligible.py:31–32,67 compares assessable capital with the captured£16,000 limit, so the source-traced£0 permits the capital condition while£18,000fails it. No executed award or population prevalence is claimed.
The PR body explicitly requests placement in the financial pool. That records design intent but does not reconcile this existing financial-only input contract with the legal treatment of non-financial property. Merely documenting the replacement does not make the consistent case a complete-capital override. Keep these new property values separately counted when a financial-only total is present, or introduce and consistently apply a properly distinct total-capital observation contract; retain the existing complete-override semantics. Add a discriminating property-plus-zero-financial-report final-eligibility regression rather than pinning the loss as an expected accounting identity.
Primary evidence: UC regulation49 (source27; NIequivalent source28), UC Schedule10 (source31), and the official ADM H1 full PDF source32, physical43–45. Source27SHA5391166a8aebcf520fb05d2898ff4632d56ecb572be6da4996f0e59361480d60; its exact full original and derivative are bound in the manifest. The primary/source/code scenario joins are recorded in /private/tmp/policyengine-command-runs/881387a1b745/pr2688-hb1241-review-20261027-policy-evidence.json. This is a confirmed source-derived case, NOT RUN. CODE independently corroborated the input and branch semantics and does not duplicate this finding.
Coordinator assessment: CODE independently confirms the financial-only report and complete-override distinction and the replacement branch. Its complementary £100,000/£90,000 trace is the same defect; the £20,000/£18,000 policy witness directly crosses the £16,000 capital limit. Counted once. All amounts here are source-derived, NOT RUN.
C2 — Current-head required Python and result checks are failing (OPEN)
At captured head 442f0330c8c99ff9965dd76eebfc539dd6f93bc1, the complete 20-row status connection reports 18 SUCCESS and 2 FAILURE. Both failed checks are in CheckSuite 109486082318, whose commit.oid is this exact head:
Test / Python 1/5, check ID121824890358, completed2026-10-26T21:33:19Z: FAILURE.Test / Result, check ID121862213237, completed2026-10-26T21:35:00Z: FAILURE.
The repository's current docs/engineering/skills/testing.md requires the Python shards and Test / Result: the result job rejects incomplete, duplicated or unaccepted test outcomes. A successful YAML job, the other four Python shards or Aggregate impact does not clear these failed required checks. Resolve the failing required checks and establish a passing current-head result before treating this PR as merge ready.
Only check metadata was inspected. No logs, artifacts, rerun or test execution was accessed. The cause is unproved; this finding does not attribute the failure to the new component code or to the composed prerequisite, and does not claim a particular failed test, error, count or official attempt history. The raw capture is the evidence, not the author's focused-pass/mutation report.
Should Address
None.
Suggestions
None.
Evidence Gaps
- POLICY-G1: Full composed six-programme foreign/historical qualification remains incomplete: complete currency/transfer rules, property-specific disregards and historical applicability at all newly parameterised starting dates are not fully traced across all six programmes. UC C1 ordinary sole-owned property case is primary-source supported; this gap does not convert remaining mappings into confirmed defects.
- Role checks incomplete: pr2688-hb1241-review-20261027-review-policy.md; see recovered report.
Notes
- C1 is introduced by the new overseas/other entries in the financial replacement pool, despite disclosed author design intent. Existing financial-only input semantics exclude property, while a distinct complete UC override remains available. Do not replace the legal/value issue with a documentation-only fix.
- Worldwide gross-property identity, component geography, directland count-once algebra, sale-cost/debt ordering and broad rounding rules have supported static results. Unclassified otherproperty geography, within-category debt pooling, missing new debt inputs, private beneficial ownership/disregards and data501/543 release/reconciliation/calibration are disclosed limits, not verified population or adoption certificates.
- The captured API baseRefOid is cc31bd3; fetched public main is bfe4b5b and actual mergebase is 5b94537. These distinct observations remain recorded; drift cause is unproved. POLICY opening text calls the fetched SHA a captured base; this note distinguishes it from API metadata.
- Scope marker92.894844s aftercapture misses60s aim, including dispatch/coordinator overhead; no retiming. Exactfirstdispatch timestamp was not separately sealed, so parallel_review_seconds is omitted rather than manufactured. Actual role/start/finish and canonical whole elapsed remain.
- Generic partial-role aliases added by the assembler refer to the single specific POLICY-G1 check, not another primary evidence gap. Old1969reviewreservation and source/native/private/timing histories remain; no duplicate1969review or takeover.
Validation Summary
Local country/Core imports, collection, model and tests NOT RUN. CODE inventoried77paths, parsed33changedPythonASTs, checked12capital/sale lists and traced finalbenefit consumers. Exacthead20checks=18SUCCESS2FAILURE: Python1/5 and Test/Result; no logs/artifacts/cause/rerun acquired. POLICY acquired32officialpublic originals (30HTML,1WASpublicXLSX,1DWP PDF), no originalreuses; sourcewindow233.686691/300s CLOSED/checker0, relevantPDFtext read/physical45rendered+visuallyinspected, XLSX inertZIP/XML. BothrolesDONE and all ownnativesclosed; exactsnapshotHEAD/tree/clean verified then normallyremoved/no force, hubretained. Author180passes/8mutations/publicbotimpact are not independent execution or population certificates.
Timing
setup seconds: 12.61s; scope seconds: 92.89s; code role seconds: 499.38s; policy role seconds: 455.41s; adjudication seconds: 0.00s; consolidation cleanup seconds: 131.12s; elapsed seconds: 681.06s; source acquisition window seconds: 233.69s
Review Severity
REQUEST_CHANGES. Open findings: 2 critical, 0 should address, 0 suggestions.
Merge main at b9f55f8, including the six Python CI shards. No merge conflicts; preserve the stacked UC sale-expense changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Model stored-input precision, holdings scaling and regulation 6(1) penny rounding at each valuation and benefit-unit allocation step. Pin the CI counterexample and the holdings-budget precision boundary. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ents - changelog.d/2577.fixed.md names the categories that a financial-only Pension Credit report no longer replaces, and says what the household land formula now reads. - The zero-input grid comment credits row 9's Universal Credit change to the corporate_wealth deduction alone. - The pension_credit_reported_capital docstring names the formula that treats a stored financial-only total as a complete override. - The Universal Credit sale-expense rate description lists all four property routes. - The engineering page drops two passives, and the two ISA test comments name the formula that values share_like_capital. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
P3-14 needs no change here. |
|
Ready for hub review Verdict: REQUEST_CHANGES at
One finding needs a change before this is ready:
The P3s are text:
No formula or test expectation needs to change. I ran everything below on detached worktrees, with
P1None. P2P2-1. The
|
| Case | Year | pension_credit_reported_capital |
pension_credit_assessable_capital |
|---|---|---|---|
| Dataset for 2025 that stores 1,000 | 2025 | 1,000.00 | 1,000.00 |
| 2026 | 1,040.00 | 1,040.00 | |
| 2032 | 1,268.69 | 1,268.69 | |
| 2033 | -1.00 | 45,627.87 | |
| Same dataset without the column | 2026 | 37,792.48 | |
| Situation that sets 1,000 for 2026 | 2026 | 1,000.00 | 1,000.00 |
| 2027 | -1.00 | 36,000.00 |
Why this matters:
- The certified file. Vahid's measurement says it stores FRS
TOTCAPB4in this column for 37,916 of 61,214 benefit units, 3,602 of them in households with land or property (comment 6449451323). So for those units the stored total drops the land and property in every year from the data year to 2032, not only the first. A reader of the documentation could take the model's 2026 to 2030 Pension Credit figures as counting that property. - Whose sentence. It is this PR's text in this file (
git blame:6c5cbc83b); Value means-tested capital less 10% sale expenses and secured debt #1969's string atb4e872273has nothing like it. It copiesmain'suc_reported_capital.py:29, which has the same gap, though no dataset sentence follows it there (outside this PR). - The body. Line 28 does state the uprating, but the documentation is what stays with the code.
Replace lines 17-18 with:
"variable does not derive from the financial-only observation. A "
"value that a user sets in a situation applies only in the years the "
"user sets it for. The model uprates a value that a dataset stores in "
"this column into each later year it projects the dataset to, and the "
"formula returns -1 after the last of those years. "P3
P3-1. The #395 note misquotes five of its six regulations, loosens both limbs, stops UC at reg 49(2)(a) and blames the survey
RS395_NOTE.md (comment 6602539758), checked against the law-2688 files and the head.
The quotation. "less, where there would be expenses attributable to sale, 10 per cent and the amount of any incumbrance secured on it" matches IS reg 50 character for character. cd law-2688 && grep -o 'less, where there would be expenses attributable to sale, .*' uksi-*.txt shows how the other five differ:
| Regulation | Where it differs from the quotation |
|---|---|
| JSA reg 112 | "10 per cent. and" |
| ESA reg 114 | "10% and" |
| HB reg 48 | "10 per cent. and the amount of any encumbrances" |
| HB (SPC) reg 46 | "10 per cent. and the amount of any encumbrance" |
| SPC reg 20 | "10 per cent., and the amount of any encumbrance" |
The claim about position holds in all six: each file marks those words "[applies to all limbs above]", after limb (b). So "In each, the words "…"" is right about where the words sit but not about what they say.
The paraphrase. "The decision maker therefore takes the 10% and the encumbrance off both values: the value in that country where that country allows transfer to the United Kingdom, and the willing-buyer price where it prohibits transfer":
- It drops "where there would be expenses attributable to sale", which the quotation one sentence earlier carries. Read alone, the 10% looks unconditional.
- "allows transfer" drops what must be transferable. Limb (a) applies where there is "no prohibition in that country against the transfer to the United Kingdom of an amount equal to its current market or surrender value in that country". A country that lets part of the value out but bars the full amount imposes "such a prohibition", so limb (b) applies.
- "the willing-buyer price" drops "if sold in the United Kingdom".
UC. "For Universal Credit, the decision maker takes it off the value that reg 49(2)(a) sets, under reg 49(1)" is true, but it stops at (a).
- Reg 49(2) sets "the market value of a capital asset possessed by a person in a country outside the United Kingdom" under both (a) and (b).
- Reg 49(1) takes "(a) where there would be expenses attributable to sale, 10%; and (b) the amount of any encumbrances secured on it" off that market value.
- Coming straight after "on a limb (a) value as well", the sentence can be read as confining UC's deductions to the (2)(a) value.
- It makes a regulation the actor ("the value that reg 49(2)(a) sets").
- Reg 49 is the only regulation the note leaves unlinked.
The survey. "The survey records no encumbrance or disregard for either" claims more than the repository shows.
- The repository says only that "No new secured-debt inputs are exported for overseas or other real estate" (
docs/engineering/property-wealth-components.md:172-173). - The same page says WAS's separate
HpropWR8measure "deducts mortgage liabilities" (:30-31). - Both variables give a gross value, and the overseas one includes time-shares (
variables/input/overseas_property_value.py:6-10,other_property_value.py:6-10). The note says neither. - "deducts neither" also overstates. At the head, no disregard is specific to either input, and no formula deducts debt from either (
utils/capital_valuation.py:25-29;uc_overseas_property_capital.py:22-26). But the Housing Benefit passports disregard the whole of a passported claimant's capital, these two inputs included (housing_benefit_assessable_capital.py:96-113).
Actors. The note makes several things act that the writing rules don't allow:
- a pull request ("policyengine-uk#2688 adds");
- the survey;
- the six benefits ("count both");
- a document ("The table above lists");
- a limb ("as limb (a) does").
Replacement for the note below its heading. It keeps every correct fact and the six links:
In policyengine-uk#2688 the model gains two household capital inputs: `overseas_property_value`, the gross value of land and property outside the United Kingdom, including time-shares, and `other_property_value`, the gross value of other real estate with unspecified geography and property type. The Universal Credit, Pension Credit, Housing Benefit, Income Support, income-based JSA and income-related ESA formulas count both at their reported value less 10% for sale expenses. policyengine-uk has no secured-debt input for either category and no disregard specific to either, so the formulas deduct no encumbrance from them.
The legacy and Pension Credit regulations for capital outside the United Kingdom (table above) are [IS reg 50](https://www.legislation.gov.uk/uksi/1987/1967/regulation/50), [JSA reg 112](https://www.legislation.gov.uk/uksi/1996/207/regulation/112), [ESA reg 114](https://www.legislation.gov.uk/uksi/2008/794/regulation/114), [HB reg 48](https://www.legislation.gov.uk/uksi/2006/213/regulation/48), [HB (SPC) reg 46](https://www.legislation.gov.uk/uksi/2006/214/regulation/46) and [SPC reg 20](https://www.legislation.gov.uk/uksi/2002/1792/regulation/20). In each, the closing words follow limb (b) at the level of the whole regulation. In IS reg 50 they read "less, where there would be expenses attributable to sale, 10 per cent and the amount of any incumbrance secured on it"; the other five differ only in "10 per cent." (JSA reg 112), "10%" (ESA reg 114), "10 per cent." and "encumbrance" (HB (SPC) reg 46), "10 per cent.," and "encumbrance" (SPC reg 20), and "10 per cent." and "encumbrances" (HB reg 48). A module for these regulations therefore needs to take 10%, where there would be expenses attributable to sale, and the amount of any encumbrance secured on the capital off the limb (a) value as well as the limb (b) price: the capital's current market or surrender value in that country, where that country does not prohibit the transfer to the United Kingdom of an amount equal to that value, and, where that country prohibits that transfer, the price the capital "would realise if sold in the United Kingdom to a willing buyer". DWP applies both deductions to the value under either limb ([DMG 29614](https://assets.publishing.service.gov.uk/media/69d8f3a496c86b751317022c/dmg-Ch-29.pdf#page=123)). For Universal Credit, a module needs to value a capital asset in a country outside the United Kingdom at the market value in that country, where that country does not prohibit the transfer of an amount equal to the asset's value to the United Kingdom, and, where that country prohibits that transfer, at the amount the asset "would raise if sold in the United Kingdom to a willing buyer" ([UC reg 49(2)](https://www.legislation.gov.uk/uksi/2013/376/regulation/49)). The module then needs to take 10%, where there would be expenses attributable to sale, and the amount of any encumbrances secured on the asset off either value ([UC reg 49(1)](https://www.legislation.gov.uk/uksi/2013/376/regulation/49)).
policyengine-uk has no input for a transfer prohibition, so its formulas value all overseas property at the limb (a) value, or at the reg 49(2)(a) value for Universal Credit.P3-2. The changelog's Pension Credit list can be read to include the main residence
changelog.d/2577.fixed.md:1, second sentence: "The Pension Credit formula now adds owned land and other residential and non-residential property to a financial-only capital report". Review 867's text had a comma list ("owned land, other residential and non-residential property"). Joined with "and … and", the phrase reads like "land and other property", that is, owned land plus all other property, residential or not. That takes in the main residence, which the formula leaves out:
- No Pension Credit list names
main_residence_value(parameters/gov/dwp/pension_credit/income/capital/sources.yaml,property_sources.yaml). - Case: a 70-year-old in London with a £1,000 report, a £300,000 main residence, £40,000 of other residential property, £10,000 overseas and £20,000 other.
claims.pyat the head givespension_credit_assessable_capital64,000.00, which is 1,000 + 0.9 × 70,000. - The next sentence's "residential … real estate" does include the main residence. A London home of £200,000 with a saved
property_wealthof £300,000 giveshousehold_land_value170,000.00, which is 0.85 × 200,000. That makes the wrong reading of the Pension Credit sentence easier.
Replace the second sentence with:
The Pension Credit formula now adds owned land, non-residential property and residential property other than the main residence to a financial-only capital report instead of letting the report replace them.
P3-3. The means-test paragraph turns a requirement into an outcome and doesn't say the certified file stores the total
Body line 28, last three sentences, and line 54.
- Outcome or requirement. "the data release would move such a total" states an outcome and drops where the total goes. The engineering page states a requirement: "The data release must move such a total to
benunit_reported_capitalfor the property to be counted" (docs/engineering/property-wealth-components.md:160-161). The thread says the fix is "either a data-release move tobenunit_reported_capitalor a model contract change" (comment 6577610659) and assigns it to uk-data#552 (comment 6582744096). Nothing says a release has made the move. - The certified file. "If a dataset stores a financial-only survey total in
pension_credit_reported_capital…" describes today's certified file. Vahid's measurement (comment 6449451323) says uk-data stores FRSTOTCAPB4in that column. It also says: "Onenhanced_frs_2024_25.h5(uk-data 1.58.1), 37,916 of 61,214 benefit units carry a non-negative value. 3,602 of them (3.3m weighted) live in a household withowned_land, other residential or non-residential property."- I can't read the file, but the identical Pension Credit lines in this PR's and Value means-tested capital less 10% sale expenses and secured debt #1969's aggregate reports fit that.
- A reader of the changelog's Pension Credit sentence would expect the change to reach those benefit units. It reaches them only once the data producer moves the total, and, because the model uprates the stored total (P2-1), it doesn't reach them in any projected year before that.
- Wording. "The docstring and the engineering page say so" makes two documents act. The string is also the variable's
documentationattribute: the class has no docstring (an AST check givesNone). Line 54's "one docstring" has the same slip.
Replace "The docstring and the engineering page say so; the data release would move such a total." with:
The certified file's producer stores FRS
TOTCAPB4in that column, and 37,916 of the file's 61,214 benefit units carry a nonnegative value (measurement), so for those units the formula counts no land or property, in the data year or any year the model projects it to, until the producer moves the total tobenunit_reported_capital(pension_credit_reported_capitaldocumentation;docs/engineering/property-wealth-components.md).
At line 54, replace "one docstring" with "the pension_credit_reported_capital documentation string".
P3-4. Stored totals: council tax reduction reaches the inputs by a second route
Body line 34, fifth sentence: "…and council tax reduction through Pension Credit's capital on a savings-credit-only award." That is true of council_tax_reduction_assessable_capital, the seventh variable in the file's MEANS_TEST_CAPITAL (council_tax_reduction_assessable_capital.py:44-47). But four council schemes take UC capital instead:
legacy_council_tax_reductiontestsuc_assessable_capitalfor an applicant with a Universal Credit award (council_tax_reduction/_legacy.py:76-81).- The Merton, Kingston upon Thames, Newham and Westminster formulas call it, so both inputs reach those schemes.
- Witness (
ctr_local.py, one adult aged 40 in Newham, council tax £1,800, 2026):- With
overseas_property_value0:uc_assessable_capital0.00 andnewham_council_tax_reduction1,260.00. - With 8,000:
uc_assessable_capital7,200.00, which exceeds Newham's £6,000 limit (parameters/gov/local_authorities/newham/council_tax_reduction/means_test/capital_limit.yaml:3).newham_council_tax_reductionfalls to 0.00, whilecouncil_tax_reduction_assessable_capitalstays at 0.00.
- With
The clause came word for word from review 867's text. Replace the sentence with:
Both new inputs reach Pension Credit and the four legacy programmes through each one's
capital/sources.yaml, Universal Credit through itscapital/property_sources.yaml, andcouncil_tax_reduction_assessable_capitalthrough Pension Credit's capital on a savings-credit-only award. The Merton, Kingston upon Thames, Newham and Westminster council tax reduction formulas also testuc_assessable_capitalfor an applicant with a Universal Credit award, so both inputs reach those schemes as well.
P3-5. Two test comments: the ISA clause names the wrong cause, and the grid comment names corporate_wealth for share_like_capital
(a) test_isa_balances.py:62-65. "…which uc_assessable_capital values less the sale-expense rate until the data separate ISAs from shares." The test's own dataset already stores the ISA apart from any shares, and the formula still takes the rate off it:
share_like_capitaladdsstocks_and_shares_isato its itemised sum (variables/household/wealth/share_like_capital.py:41-55).- UC's
sale_expenses/sources.yaml:41-42listsshare_like_capital. isa_data.pyrebuilds the test's dataset and prints, for 2027:corporate_wealth0.0000;stocks_and_shares_isaandshare_like_capitalboth 2,144.4800;uc_assessable_capital6,218.9902, which is 1,072.24 + 3,216.72 + 0.9 × 2,144.48.
So separating ISAs in the data won't stop the deduction; only a change to what share_like_capital holds or what UC's list names would.
- The clause is Value means-tested capital less 10% sale expenses and secured debt #1969's (
git blame -L 62,65:fc066bd31), and UC'ssale_expenses/sources.yamlsays the same ("until the data separate them"). - This round rewrote line 64, so the comment is now this PR's text.
Replace with:
# Savings and the cash ISA reach the capital pool at their value. The
# stocks-and-shares ISA reaches it through share_like_capital, which
# uc_assessable_capital values less the sale-expense rate whether
# share_like_capital holds shares or, as here, only the ISA.(b) test_property_wealth_components.py:515-518. "…take sale expenses off other residential and non-residential property and off corporate_wealth, and the Universal Credit formula takes them off corporate_wealth." No list names corporate_wealth:
- HB, IS, JSA and ESA's
sale_expenses/sources.yamllistshare_like_capital, and UC's lists onlyshare_like_capital. - In row 9,
share_like_capitalequals the suppliedcorporate_wealth: 10,000.00 at2da212abcand at the head (grid_rows.py). - The body's last invariant (line 50) says "off
corporate_wealth" too.
Its next sentence, "That changes the legacy capital of both rows", lets a process act, though the reply says this comment has "the formulas as actors". "Legacy" also names stored totals elsewhere in this file (test_legacy_stored_total_overrides_formula_until_removed_by_data_release). The comment's other claims hold. Rows 8 and 9 at main 2da212abc and at the head:
| Row | Output | 2da212abc |
Head |
|---|---|---|---|
| 8 | HB, JSA, ESA and IS capital | 230,000 | 207,000 |
| 8 | UC capital | 207,000 | 207,000 |
| 9 | UC capital | 41,500 | 40,500 |
| 9 | HB, JSA, ESA and IS capital | 45,000 | 40,500 |
Wealth and land are the same at both commits, and the outputs are 0 for Pension Credit in both rows.
Replace the comment's lines from "Unlike main" to "no member has reached the qualifying age." with:
# Unlike main at 2da212ab, the Housing Benefit, JSA, ESA and Income Support
# formulas here take sale expenses off other residential and non-residential
# property and off share_like_capital, which holds row 9's corporate_wealth,
# and the Universal Credit formula takes them off share_like_capital. These
# formulas therefore change the Housing Benefit, JSA, ESA and Income Support
# capital of both rows, and row 9's Universal Credit capital, independently
# of the new inputs. Rows 8 and 9 stop before Pension Credit capital, which
# the formula sets to nil while no member has reached the qualifying age.At body line 50, replace "and off corporate_wealth" with "and off share_like_capital, which holds row 9's corporate_wealth".
P3-6. The stacking sentence no longer holds for two of #1969's comments
Body line 7: "Where both branches fixed the same thing, this branch takes #1969's version, so git diff b4e872273..HEAD holds only this PR's own change."
0d760fccdrewrites one line of Value means-tested capital less 10% sale expenses and secured debt #1969's comment intest_isa_balances.py, a test both branches had fixed (review 867's P3-7), and one comment in Value means-tested capital less 10% sale expenses and secured debt #1969'stest_stored_household_totals.py.commofgit diff --name-only b4e872273at1aacd327band at the head lists three files new to the stacked diff: those two andrate.yaml.git diff --stat b4e872273 0d760fccdover the two test files gives 3 insertions and 3 deletions, all in comments.
The conclusion still holds, but the premise no longer does. Replace with:
Where both branches fixed the same thing, this branch takes #1969's version and rewords one comment in each of #1969's
test_isa_balances.pyandtest_stored_household_totals.py, sogit diff b4e872273..HEADholds only this PR's own change.
P3-7. The last paragraph cites an aggregate report that the bot has since rewritten
Body line 153. CI's bot edits one comment per pull request in place. This PR's comment (6425530679) now holds the head's run 41627481670: "Base 1d4d020aa1 against head 83e479b06b", updated 12:37:26Z. It no longer holds run 41604662583, which the paragraph describes.
The figures didn't move: a diff of the report at 1aacd327b, which I saved before the update, against the one posted now changes only four lines:
- line 4 (base and head);
- line 8 (run link);
- the two provenance rows.
So the paragraph stays true, but nothing on the page shows the report it cites. Replace "CI's Aggregate impact job compared 1aacd327b merged into main with main at 52b30809ff on the certified enhanced_frs_2024_25.h5 (policyengine-uk-data 1.58.1)." with:
CI's Aggregate impact job (run 41604662583) compared
1aacd327bmerged intomainwithmainat52b30809ffon the certifiedenhanced_frs_2024_25.h5(policyengine-uk-data 1.58.1), and its run at0d760fccdagainstmainat1d4d020aa(run 41627481670) gives the same figures.
In the same paragraph, replace "#1969's own report on the same base and file shows the same figures, so on that file this PR changes none of them." with:
On the same base and file, the same job gave #1969 the same figures (run 41588793376), so on that file this PR changes none of them.
P3-8. The UC rate description gains a gerund with no actor and keeps reduced passives
policyengine_uk/parameters/gov/dwp/universal_credit/means_test/capital/sale_expenses/rate.yaml:1-8.
- New wording. This round replaced "before any encumbrance secured on the asset is deducted" with "taken from the gross value before deducting any encumbrance secured on the asset". Nobody does the "deducting": the sentence's subject is the rate.
- Reduced passives. It sits beside "deducted from Universal Credit capital", "taken from", "assumed to have costs of sale" and "listed in". The last two are on lines this round changed.
- Facts. They all hold:
- Each of the four
property_sourcesformulas appliesp.sale_expenses.rate:uc_other_residential_property_capital.py:27,uc_non_residential_property_capital.py:27,uc_overseas_property_capital.py:22-26anduc_other_property_capital.py:22-26. - The two that deduct secured debt do so after the rate. In
claims.py, other residential property of 100,000 with 50,000 of debt givesuc_other_residential_property_capital40,000.00, not 45,000.
- Each of the four
- Constraints.
policyengine_uk/tests/test_parameter_descriptions.py:66-76requires a description that ends with a full stop, anddocs/engineering/parameter-descriptions.md:45-48asks a rate's description to start with the quantity. The replacement meets both.
Replace the description with:
description: >
Share of the current market or surrender value that the Universal Credit
formulas deduct from capital where there would be expenses attributable to
sale. The formulas deduct a flat 10% whatever the actual costs, from the
gross value and before any encumbrance secured on the asset. They apply it
to the countable property in property_sources (other residential,
non-residential, overseas and unclassified other property, which they treat
as having costs of sale) and to the financial sources in
sale_expenses.sources.P3-9. Engineering page: the Pension Credit override keeps the dataset as actor, and "weights" means two things
(a) docs/engineering/property-wealth-components.md:157-161 (outside this round's delta). The page says: "A dataset that stores a financial-only survey total in pension_credit_reported_capital, rather than in benunit_reported_capital, turns it into a complete override: the formula then counts none of the household's land or property for that unit. The data release must move such a total to benunit_reported_capital for the property to be counted."
- This is the construction that review 867's P3-9 fixed in the documentation string, and the body leans on this page for it.
- "for the property to be counted" is a passive.
Replace with:
If a dataset stores a financial-only survey total in
pension_credit_reported_capitalrather than inbenunit_reported_capital, the formula treats that total as a complete override and counts none of the household's land or property for that unit. The data producer must move such a total tobenunit_reported_capitalbefore the formula can count the property.
(b) :239-241. "Existing land target definitions and weights stay the same, but calculated land, losses and fitted geographic weights can change." One sentence uses "weights" for two things, with opposite verbs.
- This repository defines no land target, so I can't tell which weights stay the same.
- The sentence predates review 867 (
git show f40cb894); this round changed only "are unchanged" to "stay the same". - If it means the targets' weights, replace with "Existing land target definitions and target weights stay the same, but calculated land, losses and fitted geographic weights can change."
P3-10. The legacy sale-expense lists don't cite the regulations for capital outside the United Kingdom (outside this round's delta)
The note now names six regulations as the ones that value capital outside the United Kingdom. The PR lists overseas_property_value in each legacy and Pension Credit sale_expenses/sources.yaml, but those files cite only the UK valuation rule: HB reg. 47(a) and HB (SPC) reg. 45(a), IS reg. 49(a), JSA reg. 111(a), ESA reg. 113(a) and SPC reg. 19(a).
grep -rnE "2006/213/regulation/48|2006/214/regulation/46|1987/1967/regulation/50|1996/207/regulation/112|2008/794/regulation/114|2002/1792/regulation/20\b" policyengine_uk docsat the head finds nothing.docs/engineering/skills/model-structure.md:28says "Cite durable policy sources inreferencefields when available."- The numbers don't change, since both rules take 10% off.
- The UC formulas cite reg. 49 as a whole (
uc_overseas_property_capital.py:18), which covers reg. 49(2).
Add after the first reference in each file:
# housing_benefit/means_test/capital/sale_expenses/sources.yaml
- title: The Housing Benefit Regulations 2006 reg. 48
href: https://www.legislation.gov.uk/uksi/2006/213/regulation/48
- title: The Housing Benefit (Persons who have attained the qualifying age for state pension credit) Regulations 2006 reg. 46
href: https://www.legislation.gov.uk/uksi/2006/214/regulation/46
# income_support/means_test/capital/sale_expenses/sources.yaml
- title: Income Support (General) Regulations 1987 reg. 50
href: https://www.legislation.gov.uk/uksi/1987/1967/regulation/50
# JSA/income/capital/sale_expenses/sources.yaml
- title: Jobseeker's Allowance Regulations 1996 reg. 112
href: https://www.legislation.gov.uk/uksi/1996/207/regulation/112
# ESA/income/capital/sale_expenses/sources.yaml
- title: Employment and Support Allowance Regulations 2008 reg. 114
href: https://www.legislation.gov.uk/uksi/2008/794/regulation/114
# pension_credit/income/capital/sale_expenses/sources.yaml
- title: State Pension Credit Regulations 2002 reg. 20
href: https://www.legislation.gov.uk/uksi/2002/1792/regulation/20The Northern Ireland equivalents aren't in the law inputs, so I suggest none. The body's Law section (line 40) could name the six too.
Checked and sound
Review 867's findings at this head
| Review 867 finding | At 0d760fccd |
Evidence |
|---|---|---|
| P2-1 changelog fragment | Closed (one new P3: P3-2) | changelog.d/2577.fixed.md:1 now gives:- the Pension Credit categories for the financial-only route; - the land formula's components "instead of property_wealth"; - "direct UK land", "at data-year values" and "whose producer imputed … separately". Review 867's pc_witness.py gives pension_credit_assessable_capital 100,000.00 at the head and 1,000.00 at both #1969's b4e872273 and main 2da212abc, so the Pension Credit change is this PR's. The land witness gives 255,000 at 2da212abc and b4e872273 and 170,000 at the head for the London land-gap household. With only property_wealth of 300,000 it gives 255,000 at both commits and 0 at the head.The certified-file sentence matches comment 6449462413. |
| P2-2 Stored totals | Closed (one new P3: P3-4) | Body line 34 is review 867's text word for word, and line 101 says "as it failed in CI on d0648474a".gh api …/commits/d0648474a: "Merge f40cb89… into 0080274…", with parents 0080274d78 and f40cb894ce. The failure is in job 125542774443 (Python 5/7 of run 41522703728): assert np.float64(5800.0) == 6000.0 ± 0.06.The named test ( :194-238) reads uc_assessable_capital, savings and the ISA totals. MEANS_TEST_CAPITAL (:74-82) is used only in test_stored_totals_and_person_balances_agree_in_every_year. No dataset in the file stores either input. The file gives 42 passed at the head (192 s). |
| P3-1 last paragraph | Closed (P3-7 on the citation) | Test / Result at 1aacd327b (job 125844219921): "11859 tests collected; … (11796 passed, 61 skipped, 2 xfailed) … dataset present". Test / YAML (job 125811527365) gives 5,588 passed, and the Documentation build (job 125811527167) passed.The report gives the body's figures: - UC: +£222.8m in 2025-26, peaking at +£1.53bn in 2029-30. - Pension Credit: +£0.7m to +£1.1m. - Housing Benefit: +£0.3m to +£42.6m. |
| P3-2 Main | Closed | Line 32 names the rewritten comment. git diff b4e872273 0d760fccd -- policyengine_uk/data/uprating_indices.yaml adds the two inputs and rewrites the pension_credit_reported_capital comment (6 insertions, 2 deletions). The 1d4d020aa merge claim holds (see Merges). |
| P3-3 household land | Closed | Line 17 cites 798491cb7. These commands both print nothing:- git diff --stat 798491cb7 0d760fccd -- household_land_value.py residential_property_value.py main_residence_value.py other_residential_property_value.py non_residential_property_value.py owned_land.py other_property_value.py region.py parameters/household/wealth/land tests/certified_dataset.yaml;- git log --oneline 798491cb7..0d760fccd over the same paths.household_land_value.py:19-69 reads exactly those inputs, and the pin is still enhanced_frs_2024_25.h5 at 1.58.1 (certified_dataset.yaml:8-14). |
| P3-4 source-list sentence | Closed | Line 30 is review 867's text, and the code matches it: uc_property_capital.py:31-34, pension_credit_assessable_capital.py:64-68 and capital_valuation.py:47-50. |
P3-5 the d0648474a line |
Closed | Line 151. The shard logs of run 41522703728 give 1,645, 1,665, 1,687, 1,643, 1,681 (plus 1 failed), 1,640 and 1,681 passed, which is 11,642. YAML gives 5,526 passed. |
| P3-6 allocation test path | Closed | Lines 62 and 118 give the full path, and the file passes at the head (1 passed). |
| P3-7 ISA comments | Closed in this PR (P3-5(a) on the clause it kept) | test_isa_balances.py:64 and test_stored_household_totals.py:221-222 name uc_assessable_capital. Its formula values UC's capital.sources through valued_capital, which takes the rate off share_like_capital, the only name in UC's sale_expenses.sources (uc_assessable_capital.py:63-65; capital_valuation.py:52-55).Both comments match the assertions next to them. git diff --stat b4e872273 0d760fccd over the two files gives 3 insertions and 3 deletions, all in comments. The reply's reason holds: main's last first-parent merges (#2776, #2765, #2756, #2781, #2780, #2772, #2771) are merge commits, though the repository also allows squash and rebase merges. |
| P3-8 grid comment | Closed (P3-5(b) on share_like_capital) |
The comment credits row 9's UC change to the UC formula's deduction alone and drops "so neither pins". Rows 8 and 9 behave as it says (table in P3-5).CAPITAL runs UC, HB, PC, JSA, ESA, IS (:523-530), so the 4- and 3-value rows stop before Pension Credit. pension_credit_assessable_capital.py:87 sets it to nil without a member over the qualifying age. A run of all nine rows on main 2da212abc reproduces every pinned value. |
| P3-9 documentation string | Closed | pension_credit_reported_capital.py:19-23 is review 867's text word for word. With an override of 5,000 set for 2026, pension_credit_assessable_capital is 5,000.00 in 2026 and 45,126.31 in 2027, when the override reads -1, as the string says. |
| P3-10 UC rate description | Closed (P3-8 on wording) | The "It applies to …" sentence is review 867's text. All four property formulas apply the rate (P3-8). |
| P3-11 engineering page | Closed (P3-9(b) on a sentence it touched) | :238-239 is review 867's text word for word. |
| P3-12 writing nits | Closed | Lines 15, 50, 54, 56 and 113 carry review 867's replacements. |
| P3-13 axiom line | Closed | Line 5 is a standard option (see "The Axiom line and the #395 note"). |
| P3-14 hand-offs | No PR change | The reply says so. |
Departures from review 867's text
| Text | Departure | Result |
|---|---|---|
| Changelog fragment | Four sentences instead of two; "instead of" for "rather than"; "household land … rises" for "that raises household land"; "owned land and other residential and non-residential property" for a comma list | Keeps every fact. The last change admits a main-residence reading (P3-2). |
| Grid comment | Recast with the formulas as actors | Keeps every fact. It names corporate_wealth where the lists say share_like_capital, and "That changes" lets a process act (P3-5(b)). |
| UC rate description | Also changes "before any encumbrance … is deducted" to "before deducting any encumbrance" | True, but nobody does the deducting (P3-8). |
| Engineering page | Also changes "are unchanged" to "stay the same" | No fact changes. That sentence's "weights" was already ambiguous (P3-9(b)). |
| Two ISA comments | uc_assessable_capital as actor, in place of "the formula … for Universal Credit"; the change is made on this PR, not #1969 |
Accurate (uc_assessable_capital.py:63-65). The clause both kept is wrong (P3-5(a)). |
| Means-test paragraph | "If a dataset stores …, the … formula treats" in place of the dataset-as-actor sentence; "the data release would move such a total" in place of "moving such a total belongs to the data release" | The first is right. The second turns a requirement into an outcome (P3-3). |
| Tests opening | A new first sentence on what 0d760fccd changes |
Right, apart from "one docstring" (P3-3). |
| Last paragraph | "We didn't run … locally" for "Not run locally"; "at 1aacd327b" for "at this head"; adds "the documentation build passed"; "stays unmeasured until that release exists" for "awaits that release" |
Keeps every fact. CI's merge 1429650d43 has 1aacd327b's own tree (87a1bdfe), and job 125811527167 passed. Only the citation needs work (P3-7). |
| Household land | git diff 798491cb7 0d760fccd in place of … 1aacd327b |
Holds: the diff over the land paths is empty. |
Text only
git diff --stat 1aacd327b 0d760fccd: 7 files, 27 insertions, 25 deletions. A script compared the files at both commits (exec_check.py, in the details at the end):
- Python.
ast.dumpis identical for the three test files, so only comments changed.pension_credit_reported_capital.pydiffers only in one string constant, itsdocumentationattribute: with string constants masked, the trees are identical. - YAML.
rate.yamlloads to the same data oncedescriptionis masked. - Tests. No test expectation changed.
At the head:
- Ruff.
uvx ruff@0.17.2 format --check .prints "2184 files already formatted".uvx ruff@0.17.2 check .anduvx ruff@0.17.2 check --select F811,F821 policyengine_ukprint "All checks passed!".git diff --check 1aacd327b 0d760fccdis clean. - The changed parameter.
CountryTaxBenefitSystem().parameters.gov.dwp.universal_credit.means_test.capital.sale_expenses.rategives 0.1 on 2026-01-01, with the new description. - Code health.
uv run --frozen pytest policyengine_uk/tests/code_health -n 4gives 3,115 passed (288 s). No code-health test constrains a parameter description's wording;test_parameter_descriptions.pyrequires only a closing full stop. - The three touched test files, each with
-n 4:test_property_wealth_components.py: 61 passedtest_isa_balances.py: 2 passedtest_stored_household_totals.py: 42 passed
The description
-
Live body. The live title and body equal the hub's
PR_TITLE.txtandPR_BODY.md(diffis empty). -
Backticked names. The body has 108 distinct backticked tokens, and every one exists at the head or is a command (
backticks.py, in the details):- Files, test node ids, variables, parameter paths,
valued_capitalandless_sale_expenses. capital.sources, which is code atcapital_valuation.py:47-49.household/wealthandgov/dwp, which are YAML test directories underpolicyengine_uk/tests/policy/baseline/.enhanced_frs_2024_25.h5, which iscertified_dataset.yaml:10.- Every SHA.
d0648474ais GitHub's merge commit, which the REST API resolves.
- Files, test node ids, variables, parameter paths,
-
Fix evidence.
.github/check_fix_pr.py, run the waypr_fix_evidence.yamlruns it, prints "Fix evidence: PASS … Changed executable tests: 31". I ran it frommain's checkout at1d4d020aa, whose checker equals52b30809f's, with:- the live title and body;
- empty labels;
git diff --name-status --no-renames -zfrom the merge base52b30809f;--head-sha 0d760fccd….
CI's own Fix evidence job at the head (job 125888578481) gives the same, on a log that echoes the new body.
-
New and changed sentences.
- Tests opening. The list of what
0d760fccdchanges is complete (apart from P3-3's "docstring"). - Land figure (P3-3 above).
- Means-test paragraph. The override behaviour holds: a stored 5,000 gives 5,000.00, and
benunit_reported_capitalof 5,000 gives 32,000.00 for a couple aged 80 and 78 with £1,000 of savings and the two inputs. The engineering page does say so (:157-161). - Main (see Merges).
- Stored totals. Witness: for a working-age couple with £1,000 of savings plus the two inputs, UC, HB, IS, JSA and ESA capital are each 28,000.00 and council tax reduction capital is 1,000.00. Pension Credit gives 28,000.00 for the couple aged 80 and 78, and council tax reduction gives 28,000.00 once a savings-credit-only award is set.
- Invariants, last bullet (P3-5(b) aside).
- Last paragraph, the figures (P3-7 aside).
Each holds against the head, CI at
1aacd327band review 867's evidence. - Tests opening. The list of what
-
Unchanged numbers I reran at the head. The London land-gap household gives 170,000.00. The negative-input case gives 19,999.650391. An overseas-only household has 0.00 of land.
git diff --stat b4e872273 1aacd327bgives 45 files, 2,631 insertions and 85 deletions.
The Axiom line and the #395 note
- The template.
.github/PULL_REQUEST_TEMPLATE.mdatmain1d4d020aa, identical at the head, offers "axiom: encoded-correct | encoded | queued | n/a: ". It keepsaxiom: neededfor external contributors.axiom: TheAxiomFoundation/rulespec-uk#395 queuedis the<rulespec-uk issue> queuedoption, the same line Value means-tested capital less 10% sale expenses and secured debt #1969 uses. - What a queued issue needs.
docs/engineering/skills/github-prs.md:153-158asks that a queued issue be "dispatch-ready and labelledpe-parity", with "the module path and corpus citation, the verbatim law, the required outputs, and companion tests", and that the blocker be recorded. The note itself supplies none of these for the two categories. I can't read Add parameter for marriage allowance-eligible tax bands #395 (see below). - The law (P3-1 aside).
- Links. All six links match their files' Source lines, and each file is its SI's "Calculation of capital outside the United Kingdom".
- Position. In all six, the deduction words follow limb (b) at the level of the whole regulation, so both deductions reach the value under either limb.
- DMG 29614. It reads "less 10% of the market or surrender value or price if there are costs of sale and the amount of any incumbrance secured on the capital", which covers both numbered items, so "DWP applies the deduction to both limbs" holds.
- The module sentence. "A module … needs the deduction on a limb (a) value as well" follows from the texts.
- UC. Reg 49(1) does reach the reg 49(2)(a) value.
- The model. How the head treats
overseas_property_valueandother_property_value, witnessed for one adult in London in 2026. £10,000 overseas gives 9,000.00 in each capital variable that assesses the household, and £20,000 of other property gives 18,000.00 (Pension Credit at age 70).
| Programme | Capital sources | Sale expenses | Encumbrance | Disregards and gates | Household value to benefit unit |
|---|---|---|---|---|---|
| Universal Credit | uc_overseas_property_capital and uc_other_property_capital in universal_credit/means_test/capital/property_sources.yaml:29-30; neither input is in capital/sources.yaml |
Each formula: less_sale_expenses(max_(0, input), rate, round_down) (uc_overseas_property_capital.py:22-26, uc_other_property_capital.py:22-26); rate 0.1, nearest penny |
None: no SECURED_DEBT key (capital_valuation.py:25-29), no debt input |
No disregard specific to either. A nonnegative uc_reported_capital replaces both; a benunit_reported_capital keeps both (uc_assessable_capital.py:96-108) |
Shared by is_uc_claimant counts (uc_property_capital.py:35-43): two single units get £13,500 each of £27,000 |
| Pension Credit | pension_credit/income/capital/sources.yaml:41-42 and property_sources.yaml:18-19 |
sale_expenses/sources.yaml:47-48 through valued_capital; rounded down |
None | Nil with nobody over the qualifying age (pension_credit_assessable_capital.py:47-49, :87). A nonnegative pension_credit_reported_capital replaces both; a benunit_reported_capital keeps both (:76-84) |
Shared by pension-age adults (:50-63, :69) |
| Housing Benefit | housing_benefit/means_test/capital/sources.yaml:60-61, for both sets of regulations |
sale_expenses/sources.yaml:60-61; nearest penny |
None | Nil on UC, IS, income-based JSA or income-related ESA, or on guarantee credit at pension age; Pension Credit's capital on a savings-credit-only award (housing_benefit_assessable_capital.py:82-113) |
Shared by claimants and partners (:61-74) |
| Income Support | income_support/means_test/capital/sources.yaml:41-42 |
sale_expenses/sources.yaml:45-46; rounded down |
None | None | Full value to each unit on the claim path, otherwise shared (income_support_assessable_capital.py:34-69) |
| Income-based JSA, income-related ESA | JSA/income/capital/sources.yaml:52-53, ESA/income/capital/sources.yaml:52-53 |
sale_expenses/sources.yaml:45-46 and :47-48; rounded down |
None | None | Full value to a unit with a reported award, otherwise shared (jsa_income_assessable_capital.py:35-62; ESA alike) |
| Council tax reduction | Pension Credit's capital on a savings-credit-only award (council_tax_reduction_assessable_capital.py:44-47); UC's capital in the four legacy_council_tax_reduction schemes (_legacy.py:76-81); otherwise household savings only |
Inherited | None | Nil on guarantee credit | Inherited |
- How far the note describes this. It describes the valuation correctly: 10% off both inputs in all six DWP means tests, no encumbrance, and no transfer-prohibition input. Its gaps are those in P3-1. It doesn't describe the household allocation, the gates or council tax reduction. Those are model conventions, not valuation rules, so it doesn't need to.
- Signs. UC floors each input at nil. The legacy formulas don't, so a negative input lowers their capital. For example, −£10,000 beside £5,000 of savings gives −4,000 for IS, JSA and ESA. A dataset would have to store a negative value for that to matter.
CI at the head
gh pr checks 2688 fails here ("HTTP 403: GitHub GraphQL is not available"). The REST check-runs list for 0d760fccd shows all 21 checks completed and passed:
- Build and lint: Changes, Lint, Changelog fragments, Test documentation builds, Documentation build, Release build records the commit.
- Smoke imports: the four Install + smoke-import jobs, py3.11 to py3.14.
- Fix evidence.
- Tests: the seven Python shards, Test / Result and Test / YAML.
- Aggregate impact.
CI tested 83e479b, "Merge 0d760fc… into 1d4d020…", whose tree is 7eee8483. That is the tree git merge-tree --write-tree 0d760fccd origin/main gives here.
- Test / Result (job 125920346482): "11963 tests collected; 7 shards ran 1681/1701/1745/1692/1722/1686/1736 = 11963 (11900 passed, 61 skipped, 2 xfailed); 0 modules skipped or failed at collection; mode source, dataset present". The shard summaries add up to that: 1,716, 1,694, 1,673, 1,686, 1,678, 1,728 and 1,725 passed.
- Test / YAML: 5,601 passed.
- Why the counts rose. Python is 104 above and YAML 13 above
1aacd327b's counts. That comes frommain's Disregard WTC before the Housing Benefit childcare deduction #2776, which adds three Python test files andhousing_benefit_wtc_disregard.yaml(13 tests). The body's counts are1aacd327b's and stay right as such. test_capital_share_components.py. The shard logs list all 14 of its tests as PASSED and none failed.- Aggregate impact (run 41627481670): the report compares
main1d4d020aa1with83e479b06bon the same file and sha256. Adiffagainst the1aacd327breport changes only the base and head SHAs, the run link and the two provenance rows. So every figure the body quotes still holds against the newmain.
Merges
- With
main.git merge-tree --write-tree --name-only 0d760fccd origin/main, withorigin/mainat1d4d020aa, exits 0 with tree7eee8483and no conflict. The only file both sides changed since52b30809fisutils/supplied_inputs.py:mainaddshousing_benefit_wtc_disregardtoSUPPLIED_INPUT_VARIABLESand Value means-tested capital less 10% sale expenses and secured debt #1969 adds a Lifetime ISA entry. Between52b30809fand1d4d020aa,maintouched no capital, wealth, land or uprating-index file. - With Value means-tested capital less 10% sale expenses and secured debt #1969.
git merge-tree --write-tree --name-only 0d760fccd b4e872273exits 0 with tree668f9f39, the head's own tree, sinceb4e872273is an ancestor of the head.
What I couldn't check
-
rulespec-uk#395 itself. It is outside this session's repository scope, so I couldn't check:
- whether the posted comment 6602539758 equals
RS395_NOTE.md; - what "The table above" lists;
- whether the issue meets
github-prs.md:153-158(labelledpe-parity, the blocker recorded, a module path, the verbatim law, required outputs and companion tests).
- whether the posted comment 6602539758 equals
-
DMG 29614's scope and page. The law file holds no text for the paragraph's footnote, so I can't say which benefits DMG 29614 covers. The PR's own references cite DMG chapter 29 for IS and JSA (and for Housing Benefit, by analogy), chapter 52 for ESA and chapter 84 for Pension Credit. So a reader shouldn't take 29614 to cover all six regulations. I also couldn't check that
#page=123is the PDF's page index, since the PDF doesn't resolve here. -
The law beyond the inputs. legislation.gov.uk doesn't resolve here, so I relied on the
law-2688files and the repository. They don't say how the regulations treat time-shares, who decides Housing Benefit claims, or how the council tax reduction schemes value capital outside the United Kingdom. -
The certified file. I have no access to it. So I couldn't rerun:
- the land figure (£5,113bn to £5,521bn);
- the count of benefit units with a stored
pension_credit_reported_capital(P2-1 and P3-3 rely on Vahid's measurement).
-
uk-data Add high-income households #543 and Production emissions by industry #552. Both are outside this session's scope, so I can't confirm what a data release will store or move.
-
The hub's local runs at
1aacd327b. I didn't repeat them. I reran at the head:- the four touched-file runs (code health and the three touched test files);
- single nodes of
test_uc_capital_allocation.py,test_isa_balances.pyandtest_stored_household_totals.py.
I left
test_capital_share_components.pyto CI, which passed all 14 of its tests at this head.
Witness scripts
Each script was run as <checkout>/.venv/bin/python -I <script>, with HUGGING_FACE_TOKEN and POLICYENGINE_UK_DEFAULT_DATASET unset, for 2026 unless noted.
| Script | What it does |
|---|---|
exec_check.py |
For each file in git diff --name-only 1aacd327b 0d760fccd, compares ast.dump at both commits, both as is and with every string constant masked. For YAML, compares yaml.safe_load with description masked. |
backticks.py |
Extracts every backticked token from the live body and classifies it as a SHA, path, test node, variable, parameter path or command. Checks each against git ls-tree and the tax-benefit system at the head. |
pc_witness.py |
Review 867's script: a 70-year-old in London with a £1,000 benunit_reported_capital, £8,000 of savings, £40,000 of other residential property, £50,000 of non-residential property and £20,000 of owned_land. |
land_witness.py |
London: main_residence_value 200,000 with a saved property_wealth of 300,000, property_wealth alone, other_property_value −1 with owned_land 20,000.50, and overseas only. |
witness.py |
One adult aged 40 and one aged 70, with £10,000 overseas, £20,000 other property, or neither. Prints the six DWP capital variables. |
claims.py |
The Pension Credit main-residence cases, the UC rate cases (with secured debt) and the override-year case. |
grid_rows.py, grid_all.py |
Rebuild ZERO_INPUT_GRID from the head's test file with ast, and print every output for each row. |
isa_data.py |
Rebuilds the dataset of test_stored_household_totals_without_person_balances_project_with_savings and prints share_like_capital and uc_assessable_capital for 2027. |
routes_witness.py |
The Stored totals routes: a working-age couple and a couple aged 80 and 78, with £1,000 of savings plus both inputs. Also covers a savings-credit-only award and the two reported-capital cases. |
pc_years.py, pc_override_years.py |
One person aged 75 in London with £40,000 of other residential property. A fiscal-year-2025 dataset stores pension_credit_reported_capital 1,000, alongside the same dataset without that column and a situation that sets 1,000 for 2026. Prints the column and Pension Credit capital for 2025 to 2035. |
ctr_local.py |
One adult aged 40 in Newham or Westminster, with council tax of £1,800 and £0 or £8,000 overseas, in 2026 and 2027. |
Generated by Claude Code
|
Picking this up for the UK hub. Reply here if you're mid-change and I'll stop. Generated by Claude Code |
…tations The pension_credit_reported_capital documentation now says that the model uprates a stored value into each projected year. The changelog, two test comments, the UC sale-expense rate description and the engineering page take the review's wording, and each legacy and Pension Credit sale-expense list cites the regulation for capital outside the United Kingdom. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LwQq6JUKtCvbBcX1Pd9TLH
|
Generated by Claude Code |
|
Generated by Claude Code |
|
Generated by Claude Code |
|
Ready for hub review Head: Findings
Text only
Tests at
|
|
Ready for hub review Verdict: APPROVE WITH NITS at
The findings below need no change to this commit's code:
I ran everything below on a detached worktree of the head, after P1None. P2P2-1. Once the branch merges with
|
| Finding | At 80e0d344f |
Evidence |
|---|---|---|
P2-1 pension_credit_reported_capital documentation |
Closed | :17-21 is review 995's text word for word. It matches uprating_indices.yaml:143-147, PROJECTION_END_YEAR = 2032 (economic_assumptions.py:172, whose comment says the formula serves the column after that year) and formula returning -1 (:37-38). Witness below. |
| P3-1 #395 note | Closed, with P3-1 above | Quotations, links and actors as above. The limb (a) paraphrase follows the regulation's "no prohibition in that country against the transfer to the United Kingdom of an amount equal to its current market or surrender value in that country". The UC paragraph now carries both reg 49(2) limbs and applies reg 49(1) to "either value". The first paragraph's claims hold at the head: "gross", "including time-shares" (overseas_property_value.py:6-10), "other real estate with unspecified geography and property type" (other_property_value.py:6-10), no SECURED_DEBT entry. |
| P3-2 changelog | Closed | changelog.d/2577.fixed.md:1, second sentence, is review 995's text. |
| P3-3 means-test paragraph | Closed | The body carries the replacement. Comment 6449451323 says "37,916 of 61,214 benefit units carry a non-negative value" and names TOTCAPB4. "docstring" no longer appears in the body; the Tests opening says "the pension_credit_reported_capital documentation string". |
| P3-4 stored totals | Closed at the head (P2-1 after the merge) | _legacy.py:76-81 tests uc_assessable_capital. legacy_council_tax_reduction is called by exactly the Merton, Westminster, Newham and Kingston upon Thames formulas (grep -l). |
| P3-5 test comments | Closed | (a) test_isa_balances.py:62-65 and (b) test_property_wealth_components.py:515-522 are review 995's text. The body's invariant bullet says "off share_like_capital, which holds row 9's corporate_wealth, and so change…". The author changed "which changes" to "and so change", which is grammatical and keeps the facts. |
| P3-6 stacking sentence | Closed; still holds | git merge-base --is-ancestor b4e872273 80e0d344f succeeds. git diff b4e872273 80e0d344f -- test_isa_balances.py test_stored_household_totals.py shows one reworded comment in each file and nothing else. |
| P3-7 aggregate citation | Closed (P3-3 nit) | gh api …/actions/runs/<id>:- 41604662583: head_sha 1aacd327b…; its job log checks out "Merge 1aacd32… into 52b3080…".- 41627481670: 0d760fccd…; "Merge 0d760fc… into 1d4d020…".- 41588793376: b4e872273… (uc-capital-sale-expenses); "Merge b4e8722… into 52b3080…".All three are Aggregate impact runs that succeeded. Today's report (run 41651533635) gives the body's figures: UC +£222.8m in 2025-26 and +£1.53bn in 2029-30, Pension Credit +£0.7m to +£1.1m, and Housing Benefit +£0.3m to +£42.6m. |
| P3-8 UC rate description | Closed | rate.yaml:1-9 is review 995's text. CountryTaxBenefitSystem() loads it, and the rate gives 0.1 on 2026-01-01. |
| P3-9 engineering page | Closed | (a) :157-161 and (b) :239-241 carry review 995's text (P3-3 nit on line length). |
| P3-10 legacy citations | Closed | Each of the five lists cites the regulation for capital outside the United Kingdom right after the UK rule it pairs with: ESA reg 114, JSA reg 112, IS reg 50 and SPC reg 20 in second place; HB reg 48 after reg 47(a), and HB (SPC) reg 46 after reg 45(a), in fourth place. Each href matches the law file's Source line. CountryTaxBenefitSystem() loads all five, and each list's metadata references put the new regulation second (HB: reg 48 second). Each list still contains overseas_property_value. The body's Law section names and links the six. |
P2-1 witness
pc_years.py: one person aged 75 in London with £40,000 of other_residential_property_value. The dataset cases are a UKSingleYearDataset for fiscal year 2025 with £1,000 of savings, with and without a stored pension_credit_reported_capital of 1,000.
| Case | Year | pension_credit_reported_capital |
pension_credit_assessable_capital |
|---|---|---|---|
| 2025 dataset that stores 1,000 | 2025 | 1,000.00 | 1,000.00 |
| 2026 | 1,040.00 | 1,040.00 | |
| 2032 | 1,268.69 | 1,268.69 | |
| 2033 | -1.00 | 45,627.87 | |
| 2034 | -1.00 | 46,890.17 | |
| Same dataset without the column | 2026 | -1.00 | 37,792.48 |
| Situation that sets 1,000 for 2026, property for 2026 only | 2026 | 1,000.00 | 1,000.00 |
| 2027 | -1.00 | 36,878.33 | |
| Situation that sets 1,000 for 2026, property for 2026 and 2027 | 2026 | 1,000.00 | 1,000.00 |
| 2027 | -1.00 | 36,000.00 |
Every row in the hand-off and the body reproduces, including 2033's -1 and the 2027 row that needs the property set for 2027 (P3-2).
Tests at the head
Each run with uv run --frozen pytest <path> -n 4:
test_parameter_descriptions.py: 24 passedtest_isa_balances.py: 2 passed- That pair together with
test_property_wealth_components.py: 87 passed, sotest_property_wealth_components.pygives 61 policyengine_uk/tests/code_health: 3,115 passed (214 s)
Fix evidence
I ran .github/check_fix_pr.py the way pr_fix_evidence.yaml:53-79 runs it. main's checker at 9dd44e142 equals the head's.
- Inputs: the live title and body written without a trailing newline, empty labels, and
git diff --name-status --no-renames -zfrom the merge base52b30809fto the head. I ran it withuv run --no-project --with pyyaml==6.0.2and--head-sha 80e0d344f…. - Output: "Fix evidence: PASS ('Add overseas and other real estate to property wealth and capital') - Fix PR has valid reproducer evidence. Changed executable tests: 31."
CI at the head
The REST check-runs list for 80e0d344f has 22 runs, all completed and successful on 3 Nov between 14:13Z and 15:31Z:
- Changes, Lint, Changelog fragments, Test documentation builds, Documentation build, Release build records the commit;
- the four smoke imports (py3.11 to py3.14);
- Fix evidence, twice;
- the seven Python shards, Test / Result and Test / YAML;
- Aggregate impact.
The Test / Result log says "11963 tests collected; … (11900 passed, 61 skipped, 2 xfailed) … dataset present", and the Test / YAML log says "5601 passed".
Merges
- With Value means-tested capital less 10% sale expenses and secured debt #1969
b4e872273.git merge-tree --write-tree --name-only 80e0d344f b4e872273exits 0 with treebbe2c571, the head's own tree (Value means-tested capital less 10% sale expenses and secured debt #1969 is an ancestor). - With
main9dd44e142. The merge conflicts in exactly the hub's eight files:pension_credit_income.yamltest_savings_credit_only_receipt_properties.pyesa_income_assessable_capital.pyhousing_benefit_assessable_capital.pyincome_support_assessable_capital.pyjsa_income_assessable_capital.pypension_credit_assessable_capital.pyuc_assessable_capital.py
- Where they come from.
0d760fccdand Value means-tested capital less 10% sale expenses and secured debt #1969'sb4e872273each give the same eight against9dd44e142. None of the delta's 11 files is among them, so none comes from the delta. The sources aremain's Count each person's ISAs in their own benefit unit's capital #2839 and Treat income derived from capital as capital in the legacy means tests #1995 (P2-1). - The body. Its "merges … with
mainat1d4d020aawithout conflict" names that SHA and still holds for it. Conflicts against today'smainare Value means-tested capital less 10% sale expenses and secured debt #1969's to resolve first, which fits "Value means-tested capital less 10% sale expenses and secured debt #1969 must merge first".
Writing rules on the delta's text
- Documentation string, changelog, engineering page, test comments and rate description. Active voice with named actors ("a user sets", "the model uprates", "the formula returns", "the data producer must move", "the formulas deduct"), British spelling, and no fix history.
- Body. The new and changed sentences name actors ("We cite", "the decision maker", "The certified file's producer stores"), and none names a reviewer, hub or session.
- Add parameter for marriage allowance-eligible tax bands #395 note. P3-1(b) is the only wording I'd change there.
What I couldn't check
- rulespec-uk#395. It is outside this session's GitHub scope. I couldn't confirm that the posted comment equals
RS395_NOTE.md, what "(table above)" lists, or whether the issue meetsgithub-prs.md's queued-issue requirements. - DMG 29614's own footnote. It isn't in the extract, so the Income Support and JSA scope rests on the page's other footnote and the repository's references.
- Regulations beyond the law files. I can't say what legislation.gov.uk gives beyond the quoted law, for example where the regulations put time-shares or who decides Housing Benefit claims.
- The merged tree. It has conflicts, so I couldn't run the P2-1 gap. The finding rests on the merge-tree's file contents and
main's formula. - The certified file and the uk-data repository. I couldn't rerun the 37,916 count or the land figures.
- Full suite and survey-dataset tests. I didn't run them locally; CI ran them at the head.
Witness scripts
Each was run as head/.venv/bin/python -I <script>, with HUGGING_FACE_TOKEN and POLICYENGINE_UK_DEFAULT_DATASET unset.
| Script | What it does |
|---|---|
exec_check.py <base> <head> |
For each file in git diff --name-only, compares ast.dump at both commits, as is and with string constants masked. For YAML, compares yaml.safe_load with description and reference masked. |
pc_years.py |
The P2-1 table above: a UKSingleYearDataset (fiscal year 2025) with and without the stored column, run through Microsimulation, and two situations run through Simulation. |
load.py |
Loads CountryTaxBenefitSystem() and prints, for each changed parameter file, whether its list holds overseas_property_value, its first three reference hrefs, and the UC rate and its description. |
Generated by Claude Code
|
Picking this up for the UK hub. Reply here if you're mid-change and I'll stop. Generated by Claude Code |
Resolve the six assessable-capital formulas so each values every household source through valued_capital after subtracting the ISA balances main's person-capital pool moves to their holders, and values each moved stocks and shares ISA balance as its household source. Rework main's ISA and savings-credit cases by hand for the 10% sale-expense deduction, and deduct the debt secured on land in council tax reduction's own household capital. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LwQq6K8DguS6nS1jwFZ8Zi
The pound-counts-once and worked-example checks in test_person_isa_capital.py now value share_like_capital, and each stocks and shares ISA balance moved out of it, less the programme's sale-expense rate, and allow for each unit's penny rounding. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LwQq6K8DguS6nS1jwFZ8Zi
|
Picking this up for the UK hub. Reply here if you're mid-change and I'll stop. Generated by Claude Code |
Git merged main without conflict. Since af93611, main's #2744, #2760, #2763, #2768, #2769, #2786, #2787, #2788, #2794, #2809 and #2810 change no file this branch changes, no capital source list and no assessable-capital formula. #2788's legacy tariff tests set assessable capital directly, so the sale-expense valuation leaves them unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H3DAwvyhPgY7sU1r3AiycF
uprating_indices.yaml grows owned_land_secured_debt with per capita GDP, but the growth factors page left it out of that list. The page's list now matches the index file's per capita GDP entry. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H3DAwvyhPgY7sU1r3AiycF
#1969's head merges main at af93611 and 95dfc62. Git stopped on the documentation strings of pension_credit_assessable_capital and uc_assessable_capital. Each keeps #1969's text and this branch's own sentences: Pension Credit's explicit report replaces the whole assessment and a financial-only report keeps separately valued land and property, and UC's financial-only report leaves domestic, overseas and unclassified other property counted. Git merged both formulas' code, valued_capital's sources argument, and the six capital source lists without conflict; each list keeps every entry from both sides. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H3DAwvyhPgY7sU1r3AiycF
…apital council_tax_reduction_assessable_capital's household route, which applies outside the Pension Credit, Universal Credit and passported routes, now counts overseas_property_value and other_property_value at 90% of their value. Both lists cite each scheme's rule for capital outside the United Kingdom: SI 2012/2885 Sch 1 para 33, SI 2012/2886 Sch para 66, WSI 2013/3029 Sch 1 para 27 and Sch 6 para 29, SSI 2012/319 reg 43, SSI 2012/303 reg 47 and SSI 2021/249 reg 70(2). Each takes off 10%, where there would be expenses attributable to sale, and any encumbrance under either limb; the data hold no secured debt for either input, so SECURED_DEBT stays as it was. The new cases pin the household route at 0.9 times each input for a working-age applicant without Universal Credit and a pensioner without Pension Credit in England, Wales and Scotland, and pin a working-age applicant with a Universal Credit award at uc_assessable_capital. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H3DAwvyhPgY7sU1r3AiycF
Each legacy and Pension Credit sale_expenses/rate.yaml now cites, after its UK valuation rule, the regulation that takes the same 10% off capital outside the United Kingdom, as each programme's sale_expenses/sources.yaml already does: IS reg 50, JSA reg 112, ESA reg 114, HB reg 48, HB (SPC) reg 46 and SPC reg 20. Rewrap the engineering page's last paragraph to 80 columns. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H3DAwvyhPgY7sU1r3AiycF
uprating_indices.yaml grows overseas_property_value and other_property_value with per capita GDP, but the growth factors page left both out of that list. The page's list now matches the index file's per capita GDP entry. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H3DAwvyhPgY7sU1r3AiycF
A Hypothesis property checks, for any float32 value and savings, both ages and England, Wales and Scotland, that council tax reduction's household route adds 90% of overseas_property_value or other_property_value to the capital it counts, and never less capital than without the input. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H3DAwvyhPgY7sU1r3AiycF
|
Generated by Claude Code |
|
Ready for hub review Head:
Findings and how I resolved them
Tests at
|
| Change | council_tax_reduction_capital_valuation.yaml |
Property test |
|---|---|---|
overseas_property_value out of the sale-expense list |
3 failed, 5 passed: [100,000, 100,000] against [90,000, 90,000] | overseas half fails: 100,000 against 90,000 |
overseas_property_value out of the capital list |
3 failed: [0, 0] | overseas half fails: 0 against 90,000 |
other_property_value out of the sale-expense list |
3 failed: [50,000, 50,000] against [45,000, 45,000] | not run |
other_property_value out of the capital list |
3 failed: [0, 0] | not run |
| Both lists as at the merge commit | 6 failed, 2 passed (the land and UC cases pass) | both halves fail: 0 against 90,000 |
Skipped
- Tests that need a survey dataset, the full suite and the documentation build: CI covers them once the branch moves.
- The Aggregate impact citation, as the brief says.
Judgment calls
- The household-route cases put both ages in each country, in separate households, so each case pins both the pensioner and the working-age rule; the brief asked for the countries. The UC case adds £1,000 of land so that its result (13,500) differs from what the household route would give (14,400).
- I added the property test because the repository's rules ask for one beside example tests when a parameter changes.
f6dcf990cis outside the brief's list: the growth factors page lists every per capita GDP variable inuprating_indices.yamlexcept the two new inputs (script comparison), so I added them. Drop it if you'd rather not.- Council tax reduction's
sale_expenses/rate.yamlstill cites only the UK rules. The brief asked for the two lists, so I left the rate file; the same P3-3 reasoning would add the seven outside-UK provisions there too. - I rebuilt this local branch once, before its first push, when Value means-tested capital less 10% sale expenses and secured debt #1969 gained
3dbdb0027; I rewrote nothing on GitHub.
Generated by Claude Code
|
Ready for hub review #1969 — Verdict: APPROVE WITH NITS at #2688 — Verdict: APPROVE WITH NITS at Scope: No P1 and no P2 on either PR. Every number I could rerun matches, and both mutation tables reproduce. The nits are text: one citation gap in council tax reduction's FindingsP1None. P2None. P3-1. Council tax reduction's
|
| Mutant | Result |
|---|---|
Savings-credit-only branch of council_tax_reduction_assessable_capital returns household_capital in place of pension_credit_assessable_capital |
killed, 2 failed: assert np.float32(15960.0) == 16410 |
corporate_wealth added to council tax reduction's capital/sources.yaml |
killed, 2 failed: assert np.float32(16460.0) == 15960 |
Owner's shares set to 0 (to see why the merge adds them) |
2 failed (the known families stop differing as the test needs, and the savings credit moves by 20.80) |
| Unmutated | 2 passed |
4. #2688's merge of #1969 (d7caedad2)
git show --remerge-diff d7caedad2 has two hunks, both in documentation strings: Pension Credit's and UC's. Each sentence the session kept matches its formula at 8bfd46a9f:
- Pension Credit. "An explicit nonnegative pension_credit_reported_capital replaces the whole assessment":
where(reported_capital >= 0, reported_capital, …). "Otherwise a nonnegative benunit_reported_capital replaces financial household and person-level proxies, retaining separately valued land and property with the same pension-age-adult allocation":where(financial_report >= 0, financial_report + property_share, household_capital_proxy + person_capital), whereproperty_shareisvalued_capital(…, sources=p.property_sources)×benunit_pension_age_adults / adult_divisor. - UC. "…replaces financial household and person-level proxies; it does not replace separately valued domestic, overseas or unclassified other property":
financial_reportreplacesfinancial_proxy + person_capital, anduc_property_capitalis added outside it. The UCproperty_sources.yamllists the fouruc_*_property_capitalvariables.
Lists after the merge. The same script, with parents 80e0d344f and 3dbdb0027, passes all 22 files at d7caedad2. At 8bfd46a9f, the only additions are overseas_property_value and other_property_value in council tax reduction's two lists.
5. Council tax reduction's lists (b6549961a)
Citations. Each new title and href matches the paragraph and "Source:" line in ctr-capital-valuation.txt: 2885 Sch 1 para 33, 2886 Sch para 66, WSI 2013/3029 Sch 1 para 27 and Sch 6 para 29, SSI 2012/319 reg 43, SSI 2012/303 reg 47 (/2022-03-31) and SSI 2021/249 reg 70. Each title's "(capital outside the United Kingdom)" matches the heading. The sale-expense list pairs para 33 with 32(a), Sch 6 para 29 with 28(a), and reg 70(2) with 70(1)(a). The other four have no UK rule in that list (main's choice), as the hand-off says. Descriptions: see P3-2 and P3-5.
The seven cases, by hand:
- 100,000 − 10,000 = 90,000, and 50,000 − 5,000 = 45,000, for both ages in all three countries.
- The UC case: 0.9 × 10,000 + 0.9 × 5,000 = 13,500 from
uc_assessable_capital, which counts no land. The household route would add 0.9 × 1,000 = 900, giving 14,400. So that case tells the routes apart.
The two qualifications. universal_credit_route = working_age & council_tax_reduction_universal_credit_award & uses_universal_credit_assessment, and uses_universal_credit_assessment = ~scotland | p_scotland.uses_universal_credit_capital_assessment. uses_universal_credit_capital_assessment.yaml gives 2013-04-01: true and 2022-04-01: false.
Who reads the variable (grep over variables/gov/local_authorities):
- Merton, Newham, Kingston upon Thames and Westminster call
legacy_council_tax_reduction, which readscouncil_tax_reduction_assessable_capital(_legacy.py:98-102). Kingston (:30) and Westminster (:28) also read it directly. - Oxford reads it at
oxford_council_tax_reduction.py:49, and the national formula atsimulated_council_tax_reduction_benunit.py:87. council_tax_reduction_tariff_income.py:71reads it too.
The body's sentence holds. test_stored_household_totals.py:74-82 lists all seven means tests, including council tax reduction, as the body says.
6. The property test (8bfd46a9f)
It can fail. It runs at BASE_YEAR 2024, over float32 values up to £10m, England (London), Wales and Scotland, and ages 40 and 70, with an explicit example of £100,000 in Scotland at 40. I reran the hand-off's table on a scratch copy, with policyengine-core test on council_tax_reduction_capital_valuation.yaml and pytest -n 2 on both parametrisations:
| Change | YAML file | Property test |
|---|---|---|
| None | 8 passed | 2 passed |
overseas_property_value out of the sale-expense list |
3 failed, 5 passed: [100000. 100000.] differs from [90000. 90000.] |
overseas half fails: 100000.0 == 90000.0 ± 0.11 |
overseas_property_value out of the capital list |
3 failed: [0. 0.] |
overseas half fails: 0.0 == 90000.0 |
other_property_value out of the sale-expense list |
3 failed: [50000. 50000.] against [45000. 45000.] |
other half fails: 100000.0 == 90000.0 (the hand-off didn't run this) |
other_property_value out of the capital list |
3 failed: [0. 0.] |
other half fails: 0.0 == 90000.0 (the hand-off didn't run this) |
Both lists as at d7caedad2 |
6 failed, 2 passed | both halves fail |
7. Citations (2567d1cac)
Each of the six new entries (IS reg 50, JSA reg 112, ESA reg 114, HB reg 48, HB (SPC) reg 46, SPC reg 20) sits after its UK rule in sale_expenses/rate.yaml. Its title and href are byte-for-byte the ones in the same programme's sale_expenses/sources.yaml (diff of the grep -A1 output, five files, all identical). Each href matches its law file's Source line. The engineering page's rewrap changes only line breaks (git diff 80e0d344f 8bfd46a9f -- docs/engineering/property-wealth-components.md). Council tax reduction's rate file: P3-1.
8. Documentation lists
A script compares gov.economic_assumptions.yoy_growth.obr.per_capita.gdp in uprating_indices.yaml with the backticked names in the growth factors page's "We use per capita GDP for these variables" line:
3dbdb0027: 42 and 42, no difference, sorted, no duplicates.8bfd46a9f: 44 and 44, withother_property_valueandoverseas_property_valueadded.main47a2817e2: 41 and 41, so the page matched before either branch.
9. #2131
#2131's head is ce91e92ea (base uc-capital-sale-expenses), and b4e872273 is its ancestor. git merge-tree --write-tree --name-only ce91e92ea 3dbdb0027 exits 1, with conflicts in exactly docs/book/assumptions/growthfactors.md and uc_assessable_capital.py.
I made a scratch merge with -X theirs, which takes this head's side of the conflicting hunks and keeps #2131's other changes. The only difference left in uc_assessable_capital.py against the head is #2131's own comment on HOLDINGS_SOURCE. With the head's virtualenv and PYTHONPATH on the scratch tree (I checked policyengine_uk.__file__):
person_isa_capital.yaml: 10 failed, 11 passed.test_person_isa_capital.py: 24 failed, 8 passed.
The cause is as the body says. #2131's six sources.yaml lists swap share_like_capital for share_like_capital_with_sale_expenses and share_like_capital_without_sale_expenses. counted_person_sources keeps stocks_and_shares_isa_countable_capital only while sources lists HOUSEHOLD_SOURCE_OF[...] == "share_like_capital", so the balances stay in the pool.
The suggested map target fits #2131's code. share_like_capital_without_sale_expenses is share_like_capital less share_like_capital_with_sale_expenses, and the latter takes out the without_sale_expenses list (trusts and stocks and shares ISAs) wherever itemised holdings exist. Person balances feed stocks_and_shares_isa through adds. The body hedges this with "for example", which is right.
10. The bodies
Fix evidence. I ran .github/check_fix_pr.py from main 47a2817e2, as pr_fix_evidence.yaml runs it:
- Inputs: the live title and body from the REST API, written without a trailing newline; empty labels;
git diff --name-status --no-renames -z <merge-base> <head>;uv run --no-project --with pyyaml==6.0.2. - Base: the event base
95dfc62f8and today's47a2817e2. The merge base is95dfc62f8for both heads. - Value means-tested capital less 10% sale expenses and secured debt #1969: "Fix evidence: PASS ('Value means-tested capital less 10% sale expenses and secured debt') - Fix PR has valid reproducer evidence. Changed executable tests: 26."
- Add overseas and other real estate to property wealth and capital #2688: "Fix evidence: PASS ('Add overseas and other real estate to property wealth and capital') … Changed executable tests: 34."
Numbers the delta touched. Each of these matches my rerun or my working by hand:
- Value means-tested capital less 10% sale expenses and secured debt #1969's moved-test values: Case 4; Case 3; the four UC and one PC worked figures; 16,410, 15,960 and 621.82; and £634.345.
- The land case: 25,000, and 45,000 without the
SECURED_DEBTentry. - The Value share-like capital by asset type for the sale-expense deduction #2131 failure counts.
- Add overseas and other real estate to property wealth and capital #2688's 90,000, 45,000, 13,500 and 900.
- The mutation results.
- Every count in both Validation and Tests lists (below).
#2688's land paragraph. git diff --stat 798491cb7 8bfd46a9f over household_land_value.py, the five inputs it reads, region, parameters/household/wealth/land and tests/certified_dataset.yaml is empty. So "shows no change to household_land_value, the inputs it reads, the land intensity parameters or the certified file pin" holds. pyproject.toml and uv.lock change only the package version and policyengine-core (3.33.12 to 3.34.1).
Titles and writing. Both titles are unchanged; #1969's is 66 characters. Apart from P3-2 to P3-5, the delta's new text uses named actors (the formula, the council, the decision maker, "we") and British spelling. It names no reviewer, hub or session, and the docstrings and YAML descriptions carry no fix history.
11. Tests
Each command ran on its own in a clean worktree after uv sync --frozen --extra dev, with no survey data.
| Command | 3dbdb0027 (hand-off) |
8bfd46a9f (hand-off) |
|---|---|---|
policyengine-core test …/gov/dwp |
997 passed (997) | 1,019 passed (1,019) |
policyengine-core test …/finance/benefit/family |
1,761 passed (1,761) | 1,810 passed (1,810) |
policyengine-core test …/gov/local_authorities/council_tax_reduction |
352 passed (352) | 359 passed (359) |
pytest -n 4 test_capital_valuation_properties.py |
18 passed (18) | 18 passed (18) |
pytest -n 4 test_stock_capital_periods.py |
2 passed (2) | 2 passed (2) |
pytest -n 4 test_uc_property_capital.py |
65 passed (65) | 65 passed (65) |
pytest -n 4 test_uc_property_capital_mock.py |
26 passed (26) | 26 passed (26) |
pytest -n 4 test_person_isa_capital.py |
32 passed (32) | 32 passed (32) |
pytest -n 4 test_savings_credit_only_receipt_properties.py |
6 passed (6) | 6 passed (6) |
pytest -n 4 test_stored_household_totals.py |
42 passed (42) | 42 passed (42) |
pytest -n 4 test_property_wealth_components.py |
— | 63 passed (63) |
pytest -n 4 test_isa_balances.py |
— | 2 passed (2) |
pytest -n 4 policyengine_uk/tests/code_health |
3,181 passed (3,181) | 3,186 passed (3,186) |
uvx ruff@0.17.2 format --check . |
2,229 files already formatted | 2,236 files already formatted |
uvx ruff@0.17.2 check . and check --select F811,F821 policyengine_uk |
All checks passed | All checks passed |
git diff --check (b4e872273..3dbdb0027, 80e0d344f..8bfd46a9f) |
clean | clean |
Nothing failed or was skipped.
12. Merges and CI
Merges.
git merge-tree --write-tree --name-only 3dbdb0027 47a2817e2: exit 0, tree97377406c.… 8bfd46a9f 47a2817e2: exit 0, tree7d2ec3eda.… 8bfd46a9f 3dbdb0027: exit 0, tree73ce18261, the head's own tree, because3dbdb0027is an ancestor (merge-base --is-ancestorsucceeds).maingained only Count Maternity Allowance in the benefit cap #2811 (4d6c027f) and a version commit after95dfc62f8.
Order. #1969's body says #2688 lands after it, and #2688's body says "#1969 must merge first". #2131's base is uc-capital-sale-expenses, and 3dbdb0027 isn't yet in #2131 (its merge-base --is-ancestor fails). So #1969 still has to land before #2688 and #2131.
CI (commits/<sha>/check-runs, read at 07:06Z on 5 Nov). Each head has 22 runs, all completed:
3dbdb0027. Every job passed: Changes, Lint, Changelog fragments, Test documentation builds, Documentation build, Release build records the commit, the four smoke imports, the seven Python shards,Test / YAML,Test / Result(06:00:22Z) and Fix evidence. Aggregate impact finishedsuccessat 06:08:53Z. One Fix evidence run (04:46:08Z to 04:46:09Z) iscancelled; a second started 6 s later and passed, so the second superseded it.8bfd46a9f. The same: every job passed, includingTest / Result(05:59:42Z) and Aggregate impact (06:09:38Z). One Fix evidence run was cancelled and superseded the same way.
Both bodies still cite older aggregate-impact runs; the hub replaces those from the job comments, so I don't report the "CI hasn't run" sentences.
What I couldn't check
- ADM H1674, H1656, H1665 and H1675. They aren't quoted here, so I can't say how the decision maker treats a stocks and shares ISA's withdrawal. The 2(a) answer rests on reg 49(1)'s text alone.
- Welsh scheme years.
ctr-capital-valuation.txtdoesn't say when WSI 2013/3029 came into force, which instrument governed Welsh schemes for 2013-14, or whether Sch 6 binds every Welsh authority's working-age scheme. Both council tax reduction lists date the entries2013-04-01and cite only WSI 2013/3029, asmain's list already did. - English local working-age schemes. I can't say which councils made their own scheme, or how each values capital outside the United Kingdom.
- The full suite, survey-dataset tests and the documentation build. I didn't run them locally; CI's shards cover them (above).
- Hand-off claims that rest only on the hub's machine.
ctr_compare.py's float32 and rounding rows (14,400.8994 and 900.045) andpc_years.py. I reworked the logic behind the negative-input rows from the code (P3-3), but I didn't rerun the script. - The aggregate-impact figures. Both bodies cite older runs, and the hub replaces them from the job's comment.
Generated by Claude Code
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The rate's references now list each scheme's rule for capital outside the United Kingdom after its UK valuation rule, as the legacy rates do. The sale-expense list's description now says that each rule's closing words follow both limbs, and that under SSI 2021/249 councils take regulation 70(1)'s deductions off the value regulation 70(2) gives. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Your follow-up at |
Fixes #2577
Reproducer: policyengine_uk/tests/test_property_wealth_components.py::test_financial_reports_preserve_separately_valued_property; policyengine_uk/tests/test_property_wealth_components.py::test_household_land_value_is_at_least_direct_uk_land; policyengine_uk/tests/test_property_wealth_components.py::test_new_inputs_at_zero_reproduce_main; policyengine_uk/tests/test_uc_company_owner_properties.py::test_household_capital_is_allocated_and_conserved_across_benefit_units
axiom: TheAxiomFoundation/rulespec-uk#395 queued
We stacked this PR on #1969: it merges #1969's head
59a1c4ad2, keeps #1969's history, and #1969 must merge first. Where both branches fixed the same thing, this branch takes #1969's version and rewords one comment in each of #1969'stest_isa_balances.pyandtest_stored_household_totals.py, sogit diff 59a1c4ad2..HEADholds only this PR's own change. Companion data #543 follows data #501; the model and the component data release must land together with a compatible dependency pin.What the code does
Inputs. The PR adds the WAS household stock inputs
overseas_property_valueandother_property_value. Both carry GDP-per-capita uprating, as a class attribute for situations and inpolicyengine_uk/data/uprating_indices.yamlundergov.economic_assumptions.yoy_growth.obr.per_capita.gdpfor datasets.Wealth.
property_wealthaddsresidential_property_value,non_residential_property_value,owned_land,overseas_property_valueandother_property_value.total_wealthaddsproperty_wealthonce, beside corporate, pension and savings wealth. A savedproperty_wealthcolumn still overrides the formula; the data release must stop storing it.Household land.
household_land_valueapplies the regional land intensity toresidential_property_value + non_residential_property_value + other_property_value, summed directly, then addsowned_landat full value. Direct UK land therefore counts once, identified overseas property contributes no UK land, and unclassified other real estate keeps the regional approximation. The formula no longer reads the savedproperty_wealthtotal, so on a dataset whose producer imputed the components and the saved total separately, household land moves by the regional intensity times their difference. For a London household in 2026 withmain_residence_valueof £200,000 and a savedproperty_wealthof £300,000, household land falls from £255,000 onmainto £170,000 here: London's 0.85 intensity times the £100,000 gap.On the certified file at data-year values, household land rises from about £5,113bn to £5,521bn (+£408bn, +8.0%), with 12,353 households changing (measurement at
798491cb7).git diff 798491cb7 8bfd46a9fshows no change tohousehold_land_value, the inputs it reads, the land intensity parameters or the certified file pin (policyengine-uk-data 1.58.1). The check against the MHCLG land target stays open.Means tests. Every means test that counts household property reaches both new inputs:
uc_overseas_property_capitalanduc_other_property_capitalincapital/property_sources.yamlless_sale_expenses)benunit_reported_capitalreplaces the financial pool only; the property pool stays countedcapital/sources.yamlandcapital/property_sources.yamlcapital/sale_expenses/sources.yamlpension_credit_assessable_capitaladds the report to separately valued land and property, including existing domestic categoriesmeans_test/capital/sources.yamlsale_expenses/sources.yamlcapital/sources.yamlsale_expenses/sources.yamlcapital/sources.yamlon the household route; Pension Credit's capital on a savings-credit-only award; Universal Credit's capital for a working-age applicant with an award, outside Scotland's scheme from April 2022capital/sale_expenses/sources.yamlA complete
uc_reported_capitaloverride still replaces ordinary UC capital before the regulation 77 company substitution. A nonnegativepension_credit_reported_capitalstill replaces the whole Pension Credit assessment; its formula returns -1, which means no complete override. If a dataset stores a financial-only survey total inpension_credit_reported_capitalrather than inbenunit_reported_capital, thepension_credit_assessable_capitalformula treats that total as a complete override and counts none of the household's land or property for Pension Credit. The certified file's producer stores FRSTOTCAPB4in that column, and 37,916 of the file's 61,214 benefit units carry a nonnegative value (measurement). For those units the formula counts no land or property, in the data year or any year the model projects the file to, until the producer moves the total tobenunit_reported_capital(pension_credit_reported_capitaldocumentation;docs/engineering/property-wealth-components.md). The uprating index forpension_credit_reported_capitaluprates programme observations that a dataset stores directly in that column. The model infers no new secured-debt inputs for the two new categories.Universal Credit's
uc_property_capitalreads each variable in itsproperty_sourceslist directly. Thepension_credit_assessable_capitalformula passes Pension Credit's list tovalued_capital(..., sources=...), which skips any name missing fromcapital.sources. A test checks each name in that list againstcapital.sourceson every date either list changes, and each name in UC's list against the model's variables.Council tax reduction. Since
main's #1995, thecouncil_tax_reduction_assessable_capitalformula takes a household route for an applicant outside the guarantee credit, savings-credit-only, Universal Credit and passported routes. That route sums the sources ingov.local_authorities.council_tax_reduction.capital.sources, takes 10% off those in itssale_expenses.sources, and deducts the debt secured on other residential property, non-residential property and, through #1969, land. This branch addsoverseas_property_valueandother_property_valueto both lists. Under each rule cited below, the council values capital outside the United Kingdom at its market value there or, where that country prohibits transferring that value, at what it would realise if sold in the United Kingdom to a willing buyer, and takes 10%, where there would be expenses attributable to sale, and any encumbrance secured on it off the value under either limb. In England, the default scheme's rule (SI 2012/2886 Sch para 66) applies to a working-age applicant only where the council has not made its own scheme; the model applies it to every English working-age applicant on the household route. The data hold no secured debt for either input, so the route'sSECURED_DEBTstays as it was, and the route counts each input at 90% of its value.council_tax_reduction_capital_valuation.yamlpins that at £90,000 for £100,000 of overseas property and £45,000 for £50,000 of other real estate, for a working-age applicant without Universal Credit and a pensioner without Pension Credit in England, Wales and Scotland. A seventh case gives a working-age applicant in England with a Universal Credit award £10,000 of overseas property, £5,000 of other real estate and £1,000 of land:council_tax_reduction_assessable_capitalequalsuc_assessable_capital, £13,500, where the household route would add the land at £900, which Universal Credit leaves out.Main. This branch gets
mainat95dfc62f8through #1969's3dbdb0027, which mergesmainataf9361114and then at95dfc62f8. Between52b30809fandaf9361114,mainmerged #2839, under which the formulas count each person's cash and stocks and shares ISAs in their own benefit unit, and #1995, under which the council tax reduction formula takes the household route above; #1969's description explains how its formulas combine its valuation with #2839's pooling. Inuprating_indices.yaml, this branch keeps #1969's file, addsother_property_valueandoverseas_property_value, and rewrites the comment onpension_credit_reported_capital. Git merged3dbdb0027into this branch with conflicts only in the documentation strings ofpension_credit_assessable_capitalanduc_assessable_capital; each keeps #1969's text and this branch's sentences on financial-only reports.git merge-tree --write-treemerges this head withmainat040e9a5f0without conflict.Stored totals.
test_stored_household_totals.py::test_stored_household_totals_grow_past_the_projection_horizon, whichmain's #2781 added, failed in CI ond0648474a, GitHub's merge off40cb894cintomainat0080274d: UC capital came to £5,800 against an expected £6,000, because #1969's formula values the £2,000 stocks-and-shares ISA at market value less 10%. #1969'sc11d7c1d9makes the test expect that deduction, and this branch now carries it, so the file passes here (42 passed). That test reads Universal Credit capital;test_stored_totals_and_person_balances_agree_in_every_year, in the same file, reads all seven means tests' assessable capital. Both new inputs reach Pension Credit and the four legacy programmes through each one'scapital/sources.yaml, Universal Credit through itscapital/property_sources.yaml, andcouncil_tax_reduction_assessable_capitalthrough its owncapital/sources.yaml, through Pension Credit's capital on a savings-credit-only award, and through Universal Credit's capital for a working-age applicant with a Universal Credit award, outside Scotland's scheme from April 2022. The national council tax reduction formula and the Merton, Kingston upon Thames, Newham, Westminster and Oxford formulas test that variable, so both inputs reach those schemes as well. No dataset in the file stores either input, so both default to zero and leave every figure unchanged.Law
sale_expenses/rate.yamlcites the same regulation after its UK valuation rule.capital/sources.yaml,sale_expenses/sources.yamlandsale_expenses/rate.yamlall cite: SI 2012/2885 Sch 1 para 33 (pensioners in England), SI 2012/2886 Sch para 66 (England's default scheme), WSI 2013/3029 Sch 1 para 27 and Sch 6 para 29 (Wales), SSI 2012/319 reg 43 (Scotland, pension age), SSI 2012/303 reg 47 (Scotland, working age to 31 March 2022) and SSI 2021/249 reg 70 (Scotland, working age from 1 April 2022). In the first six, the council takes off 10% and any encumbrance under closing words that follow both limbs; under SSI 2021/249, it takes reg 70(1)'s deductions off the value reg 70(2) gives.Invariants
property_wealthequals the sum of its saved components for every nonnegative input.total_wealth.household_land_valuenever falls belowowned_land, and an overseas-only household has zero UK land. A negative input breaks the bound:other_property_valueof -1 withowned_landof £20,000.50 in London gives £19,999.65.council_tax_reduction_assessable_capital, within float32 rounding of the total, and never reduces it.property_sourceslist appears in itscapital.sourceson each date either list changes, and every name in UC's list names a model variable.main's wealth, land and capital outputs on a hand-built grid pinned frommainat2da212ab. For the two rows with other residential, non-residential or corporate wealth, the grid pins wealth, land and Universal Credit capital on the first and wealth and land only on the second, because Value means-tested capital less 10% sale expenses and secured debt #1969's formulas take sale expenses off other residential and non-residential property and offshare_like_capital, which holds row 9'scorporate_wealth, and so change their other capital outputs independently of the new inputs. Row 8's UC capital comes to £207,000 onmainat2da212aband6c6e229a, atf40cb894cand at1aacd327.Tests
The current head,
7849e11283d4813d133a0f8ec0d0d3b3c69d929d, changes three text files from8bfd46a9f: it merges #1969's59a1c4ad2, which rewords #1969's changelog fragment, and it adds six citations to council tax reduction'ssale_expenses/rate.yamland rewords the last sentences ofsale_expenses/sources.yaml's description. No other file changes, and it merges withmainat040e9a5f0and with #1969's head without conflict.8bfd46a9fmerges #1969's3dbdb0027into80e0d344f(d7caedad2) and adds four commits: council tax reduction's lists and cases (b6549961a), the legacy sale-expense rate citations and the engineering page's rewrapped paragraph (2567d1cac), the growth factors page (f6dcf990c) and the council tax reduction property test (8bfd46a9f). We ran each command below on its own at8bfd46a9f7fcfa1aa5fc23aab8ca78d74fd4fa13, on a clean checkout afteruv sync --frozen --extra dev, and loaded no survey data:uv run --frozen policyengine-core test <directory> -c policyengine_uk:gov/dwp1,019 passed,finance/benefit/family1,810 passed, council tax reduction 359 passed.uv run --frozen pytest <path> -n 4:test_capital_valuation_properties.py18 passed,test_stock_capital_periods.py2 passed,test_uc_property_capital.py65 passed,test_uc_property_capital_mock.py26 passed,test_person_isa_capital.py32 passed,test_savings_credit_only_receipt_properties.py6 passed,test_stored_household_totals.py42 passed,test_property_wealth_components.py63 passed andtest_isa_balances.py2 passed, andpolicyengine_uk/tests/code_health3,186 passed.uvx ruff@0.17.2 format --check .: 2,236 files already formatted.uvx ruff@0.17.2 check .anduvx ruff@0.17.2 check --select F811,F821 policyengine_uk: all checks passed.git diff --check 80e0d344f..8bfd46a9f: clean..github/check_fix_pr.pyon this title and body, with the diff from the merge base95dfc62f8: PASS, 34 changed executable tests.git merge-tree --write-tree --name-onlymerges8bfd46a9fwithmainat95dfc62f8and with Value means-tested capital less 10% sale expenses and secured debt #1969's head3dbdb0027without conflict.Mutation checks on the council tax reduction lists, each in a scratch copy of
8bfd46a9f:overseas_property_valuein the sale-expense list, the three overseas cases incouncil_tax_reduction_capital_valuation.yamlfail at £100,000 against £90,000, and so does the property test's overseas half. Without it in the capital list, the same cases and the same half fail at £0.other_property_valuein the sale-expense list, the three other real estate cases fail at £50,000 against £45,000, and so does the property test's other real estate half; without it in the capital list, the same cases and the same half fail at £0.d7caedad2, the six household-route cases fail at £0 and both halves of the property test fail; the land case and the Universal Credit case pass.CI at
7849e1128passed every job, including the seven Python shards, the YAML job andTest / Result.80e0d344fchanges only text on top of0d760fccd: thepension_credit_reported_capitaldocumentation string, the changelog fragment, two test comments, the Universal Credit sale-expense rate description, five sale-expense reference lists and the engineering page. In Python it changes only comments and one string constant; in YAML, only descriptions and references. We ran each command below on its own at80e0d344f, on a clean checkout afteruv sync --frozen --extra dev, and loaded no survey data:uv run --frozen pytest <path> -n 4:test_parameter_descriptions.py24 passed,test_isa_balances.py2 passed,test_property_wealth_components.py61 passed,policyengine_uk/tests/code_health3,115 passed.CountryTaxBenefitSystem()loads every changed parameter file, and the Universal Credit sale-expense rate still gives 0.1 on 2026-01-01.uvx ruff@0.17.2 format --check .: 2,184 files already formatted.uvx ruff@0.17.2 check .anduvx ruff@0.17.2 check --select F811,F821 policyengine_uk: all checks passed.git diff --check 0d760fccd..80e0d344f: clean..github/check_fix_pr.pyon this title and body, with the diff from the merge base52b30809f: PASS.For one person aged 75 in London with £40,000 of other residential property, we built a 2025 dataset that also holds £1,000 of savings and stores 1,000 in
pension_credit_reported_capital. From it the model givespension_credit_assessable_capitalof 1,000.00 in 2025, 1,040.00 in 2026 and 1,268.69 in 2032, while the column reads the uprated stored value, and 45,627.87 in 2033, when the formula returns -1. Without the column, the model gives 37,792.48 in 2026. From a situation that sets 1,000 for 2026 and the £40,000 of property for 2026 and 2027, it gives 1,000.00 in 2026 and 36,000.00 in 2027, when the column reads -1. The model gives the same figures at8bfd46a9f.0d760fccdchanges only text: the changelog fragment, test comments, thepension_credit_reported_capitaldocumentation string, one parameter description and the engineering page. We ran each command below on its own, on a clean checkout of1aacd327b615b20ecfe43562cf890c032e1d4dd5afteruv sync --frozen --extra dev, and loaded no survey data.We ran each Python file on its own with
uv run --frozen pytest <file> -n 4:test_stored_household_totals.py: 42 passed (208 s)test_property_wealth_components.py: 61 passedtest_isa_balances.py: 2 passedtest_uc_company_owner_properties.py: 7 passedtest_pension_credit_reported_capital_properties.py: 4 passedpolicy/baseline/finance/benefit/family/universal_credit/test_uc_capital_allocation.py: 1 passedtest_benunit_reported_capital_properties.py: 7 passedtest_capital_valuation_properties.py: 18 passedtest_projection_horizon.py: 14 passedtest_savings_credit_only_receipt_properties.py: 6 passedtest_stock_capital_periods.py: 2 passedtest_uc_company_override_properties.py: 11 passedtest_uc_property_capital.py: 65 passedtest_uc_property_capital_mock.py: 26 passedtest_uprating_inputs_reach_data.py: 18 passedtest_savings_income_uprating.py: 134 passedtest_capital_share_components.py: 14 passed (2,238 s)Of the test files
mainchanged between2da212aband52b30809f, onlytest_stored_household_totals.pyreadsproperty_wealth,household_land_value,total_wealth, a capital source list or an assessable-capital variable.YAML files, each with
uv run --frozen policyengine-core test <file> -c policyengine_uk:esa_income_capital.yaml: 18 passedhousing_benefit_capital.yaml: 21 passedincome_support_capital.yaml: 18 passedjsa_income_capital.yaml: 17 passeduc_assessable_capital.yaml: 23 passedpension_credit_reported_capital.yaml: 31 passedbenunit_reported_capital.yaml: 6 passedhousehold_land_value.yaml: 15 passedproperty_wealth_components.yaml: 13 passedcapital_valuation.yaml: 26 passedpension_credit_income.yaml: 10 passedcouncil_tax_reduction_pension_credit.yaml: 23 passedYAML directories, with the same command:
household/wealth: 45 passedgov/dwp: 856 passedWith
main's version oftest_stored_household_totals.pyat1aacd327b,test_stored_household_totals_grow_past_the_projection_horizonfails with £5,800.00 against £6,000.00 ± 0.06, as it failed in CI ond0648474a, and the merged version passes.We also ran these checks at
1aacd327b:uv run --frozen pytest policyengine_uk/tests/code_health -n 4: 3,115 passed.uvx ruff@0.17.2 format --check .: 2,184 files already formatted.uvx ruff@0.17.2 check .anduvx ruff@0.17.2 check --select F811,F821 policyengine_uk: all checks passed.git diff --check f40cb894c..1aacd327b: clean..github/check_fix_pr.pyon the title and body, with the diff from the merge base withmain: PASS, 31 changed executable tests.git merge-tree --write-tree --name-only 1aacd327b origin/mainat52b30809f: no conflict.git diff --stat b4e872273..1aacd327b: 45 files changed, 2,631 insertions, 85 deletions.Runs at
f40cb894con clean checkouts.We ran each Python file on its own with
uv run --frozen pytest <file> -n 4:test_property_wealth_components.py: 61 passedtest_isa_balances.py: 2 passedtest_uc_company_owner_properties.py: 7 passedtest_pension_credit_reported_capital_properties.py: 4 passedpolicy/baseline/finance/benefit/family/universal_credit/test_uc_capital_allocation.py: 1 passedtest_benunit_reported_capital_properties.py: 7 passedtest_capital_valuation_properties.py: 8 passedtest_projection_horizon.py: 14 passedtest_savings_credit_only_receipt_properties.py: 6 passedtest_stock_capital_periods.py: 2 passedtest_uc_company_override_properties.py: 11 passedtest_uc_property_capital.py: 65 passedtest_uc_property_capital_mock.py: 26 passedtest_uprating_inputs_reach_data.py: 18 passedtest_savings_income_uprating.py: 134 passedtest_capital_share_components.py: 14 passedYAML directories at
f40cb894c, withuv run --frozen policyengine-core test <directory> -c policyengine_uk:household/wealth: 45 passedgov/dwp: 851 passedMutation checks at
f40cb894c:test_new_inputs_at_zero_reproduce_mainfails whenoverseas_property_valuedefaults to £1,000 instead of zero: 9 failed, 9 passed, one failure for each omitted-input row.uc_non_residential_property_capitalfrom UC'sproperty_sources.yaml: 2 failed, 16 passed. Row 8's UC capital falls to £135,000.test_household_capital_is_allocated_and_conserved_across_benefit_unitsfails whenuc_assessable_capitalskips its final penny rounding, and when one benefit unit loses a penny.test_property_source_lists_name_configured_sourcesfails when a mutant misspells one name in Pension Credit's list, and again in UC's list.Checks at
f40cb894c:policyengine_uk/tests/code_health: 3,111 passed.git diff --checkand the fix-evidence check pass.main(d0648474a): the seven Python shards gave 1 failed, 11,642 passed, the failure being the stored-totals test above (shard 5/7), and the YAML job 5,526 passed.We didn't run the tests that need a survey dataset, the full suite or the documentation build locally. CI ran all three at
7849e1128and passed. CI's Aggregate impact job (run 41987980384) compared7849e1128merged intomain(8540b4fd0) withmainat47a2817e2on the certifiedenhanced_frs_2024_25.h5(policyengine-uk-data 1.58.1). Universal Credit spending rises by £222.8m in 2025-26 and by up to £1.53bn in 2029-30, Pension Credit by £0.7m to £1.1m a year and Housing Benefit by £0.3m to £42.6m a year. On the same base and file, the same job gave #1969 the same figures (run 41987975597), so on that file this PR changes none of them. The effect with the component data release stays unmeasured until that release exists.🤖 Generated with Claude Code