Skip to content

Add overseas and other real estate to property wealth and capital - #2688

Open
MaxGhenis wants to merge 65 commits into
mainfrom
property-wealth-overseas-other
Open

MaxGhenis wants to merge 65 commits into
mainfrom
property-wealth-overseas-other

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Oct 26, 2026 •

Copy link
Copy Markdown
Collaborator

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's test_isa_balances.py and test_stored_household_totals.py, so git diff 59a1c4ad2..HEAD holds 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_value and other_property_value. Both carry GDP-per-capita uprating, as a class attribute for situations and in policyengine_uk/data/uprating_indices.yaml under gov.economic_assumptions.yoy_growth.obr.per_capita.gdp for datasets.

Wealth. property_wealth adds residential_property_value, non_residential_property_value, owned_land, overseas_property_value and other_property_value. total_wealth adds property_wealth once, beside corporate, pension and savings wealth. A saved property_wealth column still overrides the formula; the data release must stop storing it.

Household land. household_land_value applies the regional land intensity to residential_property_value + non_residential_property_value + other_property_value, summed directly, then adds owned_land at 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 saved property_wealth total, 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 with main_residence_value of £200,000 and a saved property_wealth of £300,000, household land falls from £255,000 on main to £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 8bfd46a9f shows no change to household_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:

Programme Capital sources Sale expenses Financial-only report
Universal Credit uc_overseas_property_capital and uc_other_property_capital in capital/property_sources.yaml each formula deducts 10% once (less_sale_expenses) benunit_reported_capital replaces the financial pool only; the property pool stays counted
Pension Credit capital/sources.yaml and capital/property_sources.yaml capital/sale_expenses/sources.yaml pension_credit_assessable_capital adds the report to separately valued land and property, including existing domestic categories
Housing Benefit means_test/capital/sources.yaml sale_expenses/sources.yaml the savings-credit-only route inherits Pension Credit's total
Income Support, income-based JSA, income-related ESA each capital/sources.yaml each sale_expenses/sources.yaml none replaces its source pool
Council tax reduction its own capital/sources.yaml on 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 2022 its own capital/sale_expenses/sources.yaml the household route has none; the other two routes follow Pension Credit and Universal Credit

A complete uc_reported_capital override still replaces ordinary UC capital before the regulation 77 company substitution. A nonnegative pension_credit_reported_capital still replaces the whole Pension Credit assessment; its formula returns -1, which means no complete override. If a dataset stores a financial-only survey total in pension_credit_reported_capital rather than in benunit_reported_capital, the pension_credit_assessable_capital formula 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 FRS TOTCAPB4 in 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 to benunit_reported_capital (pension_credit_reported_capital documentation; docs/engineering/property-wealth-components.md). The uprating index for pension_credit_reported_capital uprates 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_capital reads each variable in its property_sources list directly. The pension_credit_assessable_capital formula passes Pension Credit's list to valued_capital(..., sources=...), which skips any name missing from capital.sources. A test checks each name in that list against capital.sources on every date either list changes, and each name in UC's list against the model's variables.

Council tax reduction. Since main's #1995, the council_tax_reduction_assessable_capital formula takes a household route for an applicant outside the guarantee credit, savings-credit-only, Universal Credit and passported routes. That route sums the sources in gov.local_authorities.council_tax_reduction.capital.sources, takes 10% off those in its sale_expenses.sources, and deducts the debt secured on other residential property, non-residential property and, through #1969, land. This branch adds overseas_property_value and other_property_value to 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's SECURED_DEBT stays as it was, and the route counts each input at 90% of its value. council_tax_reduction_capital_valuation.yaml pins 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_capital equals uc_assessable_capital, £13,500, where the household route would add the land at £900, which Universal Credit leaves out.

Main. This branch gets main at 95dfc62f8 through #1969's 3dbdb0027, which merges main at af9361114 and then at 95dfc62f8. Between 52b30809f and af9361114, main merged #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. In uprating_indices.yaml, this branch keeps #1969's file, adds other_property_value and overseas_property_value, and rewrites the comment on pension_credit_reported_capital. Git merged 3dbdb0027 into this branch with conflicts only in the documentation strings of pension_credit_assessable_capital and uc_assessable_capital; each keeps #1969's text and this branch's sentences on financial-only reports. git merge-tree --write-tree merges this head with main at 040e9a5f0 without conflict.

Stored totals. test_stored_household_totals.py::test_stored_household_totals_grow_past_the_projection_horizon, which main's #2781 added, failed in CI on d0648474a, GitHub's merge of f40cb894c into main at 0080274d: 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's c11d7c1d9 makes 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's capital/sources.yaml, Universal Credit through its capital/property_sources.yaml, and council_tax_reduction_assessable_capital through its own capital/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

  • UC Regulations 2013 reg. 49: the Secretary of State values capital at market value less 10% where a sale would incur expenses, and less secured debt; Schedule 10 lists the disregards. Reg. 6(1) sets penny rounding.
  • State Pension Credit Regulations 2002 reg. 19 values Pension Credit capital the same way.
  • We cite each legacy programme's own valuation regulation in its parameter files (JSA reg. 111, ESA reg. 113, IS reg. 49, HB reg. 47). We also cite, in each legacy and Pension Credit sale-expense list, the regulation under which the decision maker takes 10% off capital outside the United Kingdom where there would be expenses attributable to sale: IS reg. 50, JSA reg. 112, ESA reg. 114, HB reg. 48, HB (SPC) reg. 46 and SPC reg. 20. Each programme's sale_expenses/rate.yaml cites the same regulation after its UK valuation rule.
  • Council tax reduction: each scheme's rule for capital outside the United Kingdom, which council tax reduction's capital/sources.yaml, sale_expenses/sources.yaml and sale_expenses/rate.yaml all 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_wealth equals the sum of its saved components for every nonnegative input.
  • Each pound of each component, and of corporate, pension and savings wealth, counts once in total_wealth.
  • When every property input is nonnegative, household_land_value never falls below owned_land, and an overseas-only household has zero UK land. A negative input breaks the bound: other_property_value of -1 with owned_land of £20,000.50 in London gives £19,999.65.
  • A nonnegative financial-only report never reduces counted property for UC or Pension Credit.
  • On council tax reduction's household route, in England, Wales and Scotland and at working age or pension age, each pound of overseas or other real estate adds 90p to council_tax_reduction_assessable_capital, within float32 rounding of the total, and never reduces it.
  • Every name in Pension Credit's property_sources list appears in its capital.sources on each date either list changes, and every name in UC's list names a model variable.
  • On the pinned company-capital cases, each benefit unit's UC capital equals the float32 conversion of the penny-rounded oracle.
  • Leaving both new inputs at zero, or omitting them, reproduces main's wealth, land and capital outputs on a hand-built grid pinned from main at 2da212ab. 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 off share_like_capital, which holds row 9's corporate_wealth, and so change their other capital outputs independently of the new inputs. Row 8's UC capital comes to £207,000 on main at 2da212ab and 6c6e229a, at f40cb894c and at 1aacd327.

Tests

The current head, 7849e11283d4813d133a0f8ec0d0d3b3c69d929d, changes three text files from 8bfd46a9f: it merges #1969's 59a1c4ad2, which rewords #1969's changelog fragment, and it adds six citations to council tax reduction's sale_expenses/rate.yaml and rewords the last sentences of sale_expenses/sources.yaml's description. No other file changes, and it merges with main at 040e9a5f0 and with #1969's head without conflict.

8bfd46a9f merges #1969's 3dbdb0027 into 80e0d344f (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 at 8bfd46a9f7fcfa1aa5fc23aab8ca78d74fd4fa13, on a clean checkout after uv sync --frozen --extra dev, and loaded no survey data:

  • uv run --frozen policyengine-core test <directory> -c policyengine_uk: gov/dwp 1,019 passed, finance/benefit/family 1,810 passed, council tax reduction 359 passed.
  • uv run --frozen pytest <path> -n 4: test_capital_valuation_properties.py 18 passed, test_stock_capital_periods.py 2 passed, test_uc_property_capital.py 65 passed, test_uc_property_capital_mock.py 26 passed, test_person_isa_capital.py 32 passed, test_savings_credit_only_receipt_properties.py 6 passed, test_stored_household_totals.py 42 passed, test_property_wealth_components.py 63 passed and test_isa_balances.py 2 passed, and policyengine_uk/tests/code_health 3,186 passed.
  • uvx ruff@0.17.2 format --check .: 2,236 files already formatted. uvx ruff@0.17.2 check . and uvx ruff@0.17.2 check --select F811,F821 policyengine_uk: all checks passed. git diff --check 80e0d344f..8bfd46a9f: clean.
  • .github/check_fix_pr.py on this title and body, with the diff from the merge base 95dfc62f8: PASS, 34 changed executable tests.
  • git merge-tree --write-tree --name-only merges 8bfd46a9f with main at 95dfc62f8 and with Value means-tested capital less 10% sale expenses and secured debt #1969's head 3dbdb0027 without conflict.

Mutation checks on the council tax reduction lists, each in a scratch copy of 8bfd46a9f:

  • Without overseas_property_value in the sale-expense list, the three overseas cases in council_tax_reduction_capital_valuation.yaml fail 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.
  • Without other_property_value in 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.
  • With both lists as at 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 7849e1128 passed every job, including the seven Python shards, the YAML job and Test / Result.

80e0d344f changes only text on top of 0d760fccd: the pension_credit_reported_capital documentation 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 at 80e0d344f, on a clean checkout after uv sync --frozen --extra dev, and loaded no survey data:

  • uv run --frozen pytest <path> -n 4: test_parameter_descriptions.py 24 passed, test_isa_balances.py 2 passed, test_property_wealth_components.py 61 passed, policyengine_uk/tests/code_health 3,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 . and uvx ruff@0.17.2 check --select F811,F821 policyengine_uk: all checks passed. git diff --check 0d760fccd..80e0d344f: clean.
  • .github/check_fix_pr.py on this title and body, with the diff from the merge base 52b30809f: 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 gives pension_credit_assessable_capital of 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 at 8bfd46a9f.

0d760fccd changes only text: the changelog fragment, test comments, the pension_credit_reported_capital documentation string, one parameter description and the engineering page. We ran each command below on its own, on a clean checkout of 1aacd327b615b20ecfe43562cf890c032e1d4dd5 after uv 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 passed
  • test_isa_balances.py: 2 passed
  • test_uc_company_owner_properties.py: 7 passed
  • test_pension_credit_reported_capital_properties.py: 4 passed
  • policy/baseline/finance/benefit/family/universal_credit/test_uc_capital_allocation.py: 1 passed
  • test_benunit_reported_capital_properties.py: 7 passed
  • test_capital_valuation_properties.py: 18 passed
  • test_projection_horizon.py: 14 passed
  • test_savings_credit_only_receipt_properties.py: 6 passed
  • test_stock_capital_periods.py: 2 passed
  • test_uc_company_override_properties.py: 11 passed
  • test_uc_property_capital.py: 65 passed
  • test_uc_property_capital_mock.py: 26 passed
  • test_uprating_inputs_reach_data.py: 18 passed
  • test_savings_income_uprating.py: 134 passed
  • test_capital_share_components.py: 14 passed (2,238 s)

Of the test files main changed between 2da212ab and 52b30809f, only test_stored_household_totals.py reads property_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 passed
  • housing_benefit_capital.yaml: 21 passed
  • income_support_capital.yaml: 18 passed
  • jsa_income_capital.yaml: 17 passed
  • uc_assessable_capital.yaml: 23 passed
  • pension_credit_reported_capital.yaml: 31 passed
  • benunit_reported_capital.yaml: 6 passed
  • household_land_value.yaml: 15 passed
  • property_wealth_components.yaml: 13 passed
  • capital_valuation.yaml: 26 passed
  • pension_credit_income.yaml: 10 passed
  • council_tax_reduction_pension_credit.yaml: 23 passed

YAML directories, with the same command:

  • household/wealth: 45 passed
  • gov/dwp: 856 passed
  • Universal Credit: 580 passed
  • ESA income: 74 passed
  • Housing Benefit: 593 passed
  • Income Support: 140 passed
  • JSA income: 40 passed
  • council tax reduction: 331 passed

With main's version of test_stored_household_totals.py at 1aacd327b, test_stored_household_totals_grow_past_the_projection_horizon fails with £5,800.00 against £6,000.00 ± 0.06, as it failed in CI on d0648474a, 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 . and uvx ruff@0.17.2 check --select F811,F821 policyengine_uk: all checks passed.
  • git diff --check f40cb894c..1aacd327b: clean.
  • .github/check_fix_pr.py on the title and body, with the diff from the merge base with main: PASS, 31 changed executable tests.
  • git merge-tree --write-tree --name-only 1aacd327b origin/main at 52b30809f: no conflict.
  • git diff --stat b4e872273..1aacd327b: 45 files changed, 2,631 insertions, 85 deletions.

Runs at f40cb894c on clean checkouts.

We ran each Python file on its own with uv run --frozen pytest <file> -n 4:

  • test_property_wealth_components.py: 61 passed
  • test_isa_balances.py: 2 passed
  • test_uc_company_owner_properties.py: 7 passed
  • test_pension_credit_reported_capital_properties.py: 4 passed
  • policy/baseline/finance/benefit/family/universal_credit/test_uc_capital_allocation.py: 1 passed
  • test_benunit_reported_capital_properties.py: 7 passed
  • test_capital_valuation_properties.py: 8 passed
  • test_projection_horizon.py: 14 passed
  • test_savings_credit_only_receipt_properties.py: 6 passed
  • test_stock_capital_periods.py: 2 passed
  • test_uc_company_override_properties.py: 11 passed
  • test_uc_property_capital.py: 65 passed
  • test_uc_property_capital_mock.py: 26 passed
  • test_uprating_inputs_reach_data.py: 18 passed
  • test_savings_income_uprating.py: 134 passed
  • test_capital_share_components.py: 14 passed

YAML directories at f40cb894c, with uv run --frozen policyengine-core test <directory> -c policyengine_uk:

  • household/wealth: 45 passed
  • gov/dwp: 851 passed
  • Universal Credit: 580 passed
  • ESA income: 74 passed
  • Housing Benefit: 593 passed
  • Income Support: 140 passed
  • JSA income: 40 passed
  • council tax reduction: 331 passed

Mutation checks at f40cb894c:

  • test_new_inputs_at_zero_reproduce_main fails when overseas_property_value defaults to £1,000 instead of zero: 9 failed, 9 passed, one failure for each omitted-input row.
  • It also fails when a mutant removes uc_non_residential_property_capital from UC's property_sources.yaml: 2 failed, 16 passed. Row 8's UC capital falls to £135,000.
  • test_household_capital_is_allocated_and_conserved_across_benefit_units fails when uc_assessable_capital skips its final penny rounding, and when one benefit unit loses a penny.
  • test_property_source_lists_name_configured_sources fails 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.
  • Ruff format and lint pass.
  • The F811/F821 check, git diff --check and the fix-evidence check pass.
  • CI on the PR merged with 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 7849e1128 and passed. CI's Aggregate impact job (run 41987980384) compared 7849e1128 merged into main (8540b4fd0) with main at 47a2817e2 on the certified enhanced_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

MaxGhenis and others added 24 commits September 30, 2026 17:28
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>
@github-actions

github-actions Bot commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Aggregate impact on certified data: pass

Base 47a2817e2d against head 8540b4fd01, both on enhanced_frs_2024_25.h5 (policyengine-uk-data 1.58.1, sha256 056e80fd8e91), fiscal years 2025-26 to 2031-32. Each year is a full microsimulation run on each side.

Checks: 0 broken by this change, 1 failing on both sides, 0 fixed. Moves flagged for review: 204 (thresholds in report_spec.yaml; a flag does not fail the job).

Workflow run and full report

Notes

  • In the years a figure from fewer than 10 records is suppressed (spending.disabled_students_allowance), every total containing it, and the decile incomes, are rebuilt from their other components, so no published total includes it. Those totals are marked †. A change is withheld where the two sides leave out different figures and can't be lined up.

Moves flagged for review

Measure Year Base Head Change
Universal Credit 2029-30 £86.11bn £87.64bn +£1.53bn
Universal Credit 2028-29 £84.96bn £86.22bn +£1.26bn
Universal Credit 2031-32 £88.21bn £89.30bn +£1.09bn
Universal Credit 2030-31 £87.61bn £88.69bn +£1.08bn
Decile 5: mean household_net_income per household 2029-30 £47,713 £48,141 +£428
Decile 3: mean household_net_income per household 2031-32 £38,182 £38,480 +£298
Decile 2: mean household_net_income per household 2028-29 £29,017 £29,212 +£196
Decile 5: mean household_net_income per household 2028-29 £46,961 £47,271 +£310
Absolute poverty, before housing costs: children 2031-32 23.5% 22.9% -0.56pp
Decile 1: mean household_net_income per household 2031-32 £18,718 £18,821 +£103
Universal Credit 2027-28 £82.95bn £83.49bn +£543.0m
Decile 8: mean household_net_income per household 2029-30 £73,886 £73,498 -£388
Decile 8: mean household_net_income per household 2030-31 £75,319 £74,927 -£392
Decile 7: mean household_net_income per household 2026-27 £56,569 £56,291 -£277
Decile 6: mean household_net_income per household 2025-26 £47,931 £48,163 +£232
Absolute poverty, before housing costs: children 2031-32 3,549k 3,464k -84.8k
Decile 6: mean household_net_income per household 2026-27 £49,202 £49,435 +£233
Decile 1: mean household_net_income per household 2028-29 £18,152 £18,068 -£83
Decile 8: mean household_net_income per household 2028-29 £73,512 £73,176 -£335
Decile 4: mean household_net_income per household 2030-31 £44,130 £44,330 +£200
Relative poverty, after housing costs: children 2028-29 31.0% 30.6% -0.44pp
Decile 2: mean household_net_income per household 2029-30 £29,280 £29,409 +£129
Decile 7: mean household_net_income per household 2025-26 £55,659 £55,415 -£244
Decile 4: mean household_net_income per household 2028-29 £42,728 £42,908 +£179
Decile 7: mean household_net_income per household 2030-31 £59,661 £59,911 +£250
Decile 3: mean household_net_income per household 2029-30 £37,195 £37,349 +£155
Absolute poverty, before housing costs: children 2028-29 22.9% 22.5% -0.39pp
Decile 2: mean household_net_income per household 2027-28 £28,715 £28,826 +£111
Decile 8: mean household_net_income per household 2027-28 £71,864 £72,142 +£278
Decile 1: mean household_net_income per household 2030-31 £18,533 £18,605 +£71
Relative poverty, after housing costs: children 2027-28 31.2% 30.8% -0.38pp
Absolute poverty, before housing costs: children 2027-28 23.3% 22.9% -0.37pp
Relative poverty, before housing costs: children 2027-28 23.3% 22.9% -0.37pp
Decile 5: mean household_net_income per household 2030-31 £48,725 £48,907 +£182
Decile 9: mean household_net_income per household 2027-28 £80,859 £80,565 -£294
Relative poverty, before housing costs: children 2031-32 23.5% 23.1% -0.36pp
Relative poverty, after housing costs: children 2029-30 31.1% 30.7% -0.35pp
Absolute poverty, after housing costs: children 2031-32 34.2% 33.9% -0.35pp
Absolute poverty, before housing costs: children 2028-29 3,445k 3,386k -58.4k
Reduce Universal Credit taper rate to 20% 2028-29 £-22.00bn £-22.72bn -£725.7m

164 more flags are in the report artifact.

Receipts by tax head

Measure 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
VAT on private school fees 0 0 -£0.4m -£1.6m -£0.7m 0 +£2.0m
TV licence fee -£0.2m -£0.2m -£0.3m -£0.3m -£0.2m -£0.3m -£0.1m

Spending by benefit and service

Measure 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Pension Credit +£1.0m +£1.0m +£1.1m +£1.0m +£0.9m +£0.8m +£0.7m
Universal Credit +£222.8m +£324.3m +£543.0m +£1.26bn +£1.53bn +£1.08bn +£1.09bn
Housing Benefit +£0.9m +£0.3m +£0.4m +£2.8m +£19.9m +£42.6m +£16.1m
Council Tax Reduction +£0.8m +£0.8m +£0.3m +£0.3m +£1.1m -£29,889 -£31,042
Employment and Support Allowance (income-related) +£0.4m +£0.4m +£0.4m +£2.6m +£0.4m +£0.4m +£0.5m
Jobseeker's Allowance (income-based) +£13,640 0 0 0 0 0 0
Income Support +£43,929 0 0 0 0 0 0
Scottish Child Payment +£0.3m 0 0 0 +£6.0m +£11.0m +£24.1m
Tax-Free Childcare -£0.6m -£1.4m -£0.7m 0 -£10.2m -£11.2m -£1.7m
Targeted childcare entitlement 0 0 0 0 0 0 +£7.0m
Disabled Students' Allowance suppressed suppressed suppressed suppressed suppressed suppressed suppressed

Caseloads

Measure 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Pension Credit +941 +943 +2.8k +2.8k +964 +1.5k +567
Universal Credit +12.5k +35.0k +51.0k +91.4k +96.8k +73.8k +69.3k
Housing Benefit +94 0 0 +188 +2.0k +4.7k +1.6k
Council Tax Reduction +514 +515 -45 -45 +549 -53 -53
Employment and Support Allowance (income-related) 0 0 0 +188 0 0 0
Scottish Child Payment +213 0 0 0 +3.7k +6.7k +14.4k
Tax-Free Childcare -426 -16.9k -16.3k 0 -11.9k -14.9k -7.1k
Targeted childcare entitlement 0 0 0 0 0 0 +3.6k
Disabled Students' Allowance suppressed suppressed suppressed suppressed suppressed suppressed suppressed

Government balance, each component counted once

Measure 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Government receipts, each component once -£0.2m -£0.2m -£0.7m -£1.9m -£0.8m -£0.3m +£1.9m
Government spending +£225.5m † +£325.5m † +£544.5m † +£1.27bn † +£1.55bn † +£1.13bn † +£1.14bn †
Government balance, each receipt once -£225.7m † -£325.7m † -£545.2m † -£1.27bn † -£1.55bn † -£1.13bn † -£1.14bn †

Model totals

Measure 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
gov_tax -£0.2m -£0.2m -£0.7m -£1.9m -£0.8m -£0.3m +£1.9m
household_tax -£0.2m -£0.2m -£0.3m -£0.3m -£0.2m -£0.3m -£0.1m
pre_budget_change_household_tax -£0.2m -£0.2m -£0.3m -£0.3m -£0.2m -£0.3m -£0.1m
gov_spending +£225.5m † +£325.5m † +£544.5m † +£1.27bn † +£1.55bn † +£1.13bn † +£1.14bn †
household_benefits +£225.5m † +£325.5m † +£544.5m † +£1.27bn † +£1.55bn † +£1.13bn † +£1.14bn †
gov_balance -£225.7m † -£325.7m † -£545.2m † -£1.27bn † -£1.55bn † -£1.13bn † -£1.14bn †

Household income totals

Measure 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Household net income +£225.7m † +£325.7m † +£544.8m † +£1.27bn † +£1.55bn † +£1.13bn † +£1.14bn †
Household net income (HBAI definition) +£226.7m +£328.2m +£549.3m +£1.27bn +£1.55bn +£1.13bn +£1.13bn

Poverty rates

Measure 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Absolute poverty, before housing costs: everyone -0.05pp -0.07pp -0.14pp -0.14pp -0.11pp -0.04pp -0.21pp
Absolute poverty, before housing costs: children -0.14pp -0.20pp -0.37pp -0.39pp -0.29pp -0.07pp -0.56pp
Absolute poverty, before housing costs: working-age adults -0.03pp -0.05pp -0.09pp -0.10pp -0.07pp -0.03pp -0.15pp
Absolute poverty, before housing costs: pensioners -0.00pp 0 0 0 -0.02pp -0.03pp -0.00pp
Absolute poverty, after housing costs: everyone -0.01pp -0.07pp -0.08pp -0.15pp -0.15pp -0.09pp -0.14pp
Absolute poverty, after housing costs: children -0.01pp -0.17pp -0.21pp -0.27pp -0.28pp -0.09pp -0.35pp
Absolute poverty, after housing costs: working-age adults -0.01pp -0.05pp -0.06pp -0.14pp -0.15pp -0.11pp -0.10pp
Absolute poverty, after housing costs: pensioners -0.00pp 0 0 0 -0.00pp -0.03pp -0.02pp
Relative poverty, before housing costs: everyone -0.06pp -0.11pp -0.14pp -0.08pp -0.10pp +0.05pp -0.14pp
Relative poverty, before housing costs: children -0.13pp -0.32pp -0.37pp -0.24pp -0.29pp +0.14pp -0.36pp
Relative poverty, before housing costs: working-age adults -0.05pp -0.07pp -0.09pp -0.07pp -0.06pp +0.03pp -0.10pp
Relative poverty, before housing costs: pensioners -0.00pp 0 0 +0.12pp +0.00pp +0.01pp +0.01pp
Relative poverty, after housing costs: everyone -0.01pp -0.11pp -0.13pp -0.20pp -0.17pp -0.09pp -0.13pp
Relative poverty, after housing costs: children -0.01pp -0.30pp -0.38pp -0.44pp -0.35pp -0.08pp -0.32pp
Relative poverty, after housing costs: working-age adults -0.01pp -0.08pp -0.11pp -0.17pp -0.16pp -0.11pp -0.09pp
Relative poverty, after housing costs: pensioners +0.01pp 0 +0.08pp +0.04pp +0.04pp -0.04pp -0.01pp

People in poverty

Measure 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Absolute poverty, before housing costs: everyone -33.6k -52.0k -95.1k -100.5k -77.5k -27.5k -149.5k
Absolute poverty, before housing costs: children -21.3k -29.7k -56.0k -58.4k -43.6k -11.1k -84.8k
Absolute poverty, before housing costs: working-age adults -12.3k -22.3k -39.1k -42.1k -31.9k -12.8k -64.4k
Absolute poverty, before housing costs: pensioners -8 0 0 0 -2.0k -3.7k -347
Absolute poverty, after housing costs: everyone -3.6k -47.6k -57.7k -103.7k -105.5k -63.5k -97.0k
Absolute poverty, after housing costs: children -1.2k -26.1k -30.8k -41.0k -41.9k -13.9k -53.1k
Absolute poverty, after housing costs: working-age adults -2.3k -21.4k -26.9k -62.8k -63.3k -45.9k -41.9k
Absolute poverty, after housing costs: pensioners -94 0 0 0 -368 -3.7k -2.0k
Relative poverty, before housing costs: everyone -41.1k -77.3k -95.1k -54.9k -69.6k +35.4k -96.7k
Relative poverty, before housing costs: children -18.7k -47.4k -56.0k -36.6k -43.6k +21.5k -54.0k
Relative poverty, before housing costs: working-age adults -22.2k -29.9k -39.1k -31.3k -26.0k +13.0k -43.6k
Relative poverty, before housing costs: pensioners -226 0 0 +13.0k +110 +852 +839
Relative poverty, after housing costs: everyone -4.1k -78.2k -93.1k -136.1k -118.3k -64.2k -90.0k
Relative poverty, after housing costs: children -1.1k -44.8k -56.4k -66.6k -53.1k -12.3k -47.7k
Relative poverty, after housing costs: working-age adults -3.6k -33.4k -46.0k -73.5k -69.3k -47.4k -40.6k
Relative poverty, after housing costs: pensioners +595 0 +9.3k +4.0k +4.1k -4.5k -1.7k

Inequality

Measure 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Gini coefficient, equivalised HBAI income before housing costs -0.0001 -0.0002 -0.0004 -0.0006 -0.0008 -0.0003 -0.0008

Mean household net income in each side's own deciles

Measure 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Decile 1: mean household_net_income per household +£20 † -£24 † -£5.20 † -£83 † -£23 † +£71 † +£103 †
Decile 2: mean household_net_income per household +£52 † +£39 † +£111 † +£196 † +£129 † -£13 † -£5.99 †
Decile 3: mean household_net_income per household -£11 † +£2.75 † +£74 † +£4.41 † +£155 † +£31 † +£298 †
Decile 4: mean household_net_income per household +£27 † +£58 † +£76 † +£179 † +£85 † +£200 † +£79 †
Decile 5: mean household_net_income per household +£6.78 † +£3.62 † -£65 † +£310 † +£428 † +£182 † +£1.52 †
Decile 6: mean household_net_income per household +£232 † +£233 † +£39 † +£72 † -£82 † -£154 † -£4.30 †
Decile 7: mean household_net_income per household -£244 † -£277 † +£4.53 † +£76 † +£1.61 † +£250 † -£14 †
Decile 8: mean household_net_income per household -£132 † -£45 † +£278 † -£335 † -£388 † -£392 † -£33 †
Decile 9: mean household_net_income per household +£1.77 † +£3.78 † -£294 † +£2.05 † -£0.46 † -£0.45 † +£0.14 †
Decile 10: mean household_net_income per household +£0.31 † +£0.31 † +£0.16 † +£0.31 † +£0.30 † +£0.30 † +£0.30 †

Standard reform costings (change in gov_balance)

Measure 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Raise basic rate by 1pp -£0.9m -£3.2m -£4.2m -£24.7m -£3.6m -£3.1m -£3.0m
Raise higher rate by 1pp +£71 +£6 -£0.7m +£1.1m +£0.7m -£922 -£0.2m
Raise personal allowance by ~800GBP/year +£1.4m +£2.0m +£1.4m +£2.8m +£3.4m +£3.0m +£1.2m
Raise child benefit by 25GBP/week per additional child +£0.5m +£7,467 +£0.5m -£73,942 +£1.9m +£10.6m -£1.7m
Reduce Universal Credit taper rate to 20% -£192.0m -£467.5m -£519.8m -£725.7m -£539.1m -£419.2m -£346.1m
Raise Class 1 main employee NICs rate to 10% -£1.9m -£3.7m -£6.3m -£11.8m -£11.3m +£1.4m -£9.3m
Raise VAT standard rate by 2pp -£129 -£79 -£43,926 -£0.2m -£66,030 +£110 +£0.2m
Raise additional rate by 3pp +£0 0 -£0 -£58,955 +£0.6m 0 +£0
Raise the Universal Credit standard allowance by 10% -£6.8m -£21.6m -£33.8m -£59.0m -£49.5m -£40.6m -£46.4m
Raise the State Pension by 10% -£4.5m +£1.7m +£1.4m +£2.7m +£3.0m +£4.2m -£0.8m

Standard reforms: change in each reform's target

Measure 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Reduce Universal Credit taper rate to 20%: change in universal_credit +£188.7m +£465.2m +£519.3m +£713.8m +£536.2m +£418.8m +£344.8m
Raise the Universal Credit standard allowance by 10%: change in universal_credit +£7.1m +£21.5m +£34.3m +£59.1m +£59.2m +£41.5m +£44.4m

Household net income by decile

Mean 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.

Base decile 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Decile 1 +£24 (+0.1%) +£17 (+0.1%) +£64 (+0.4%) +£168 (+0.9%) +£191 (+1.0%) +£85 (+0.5%) +£190 (+1.0%)
Decile 2 +£8 (+0.0%) +£48 (+0.2%) +£50 (+0.2%) +£32 (+0.1%) +£39 (+0.1%) +£25 (+0.1%) +£93 (+0.3%)
Decile 3 +£12 (+0.0%) +£10 (+0.0%) +£18 (+0.1%) +£9 (+0.0%) +£11 (+0.0%) +£13 (+0.0%) +£19 (+0.0%)
Decile 4 +£6 (+0.0%) +£7 (+0.0%) +£7 (+0.0%) +£9 (+0.0%) +£68 (+0.2%) +£70 (+0.2%) +£13 (+0.0%)
Decile 5 +£5 (+0.0%) +£5 (+0.0%) +£10 (+0.0%) +£156 (+0.3%) +£149 (+0.3%) +£147 (+0.3%) +£7 (+0.0%)
Decile 6 +£5 (+0.0%) +£5 (+0.0%) +£2 (+0.0%) +£2 (+0.0%) +£10 (+0.0%) +£11 (+0.0%) +£6 (+0.0%)
Decile 7 +£4 (+0.0%) +£3 (+0.0%) +£5 (+0.0%) +£4 (+0.0%) +£4 (+0.0%) +£4 (+0.0%) +£2 (+0.0%)
Decile 8 +£2 (+0.0%) +£3 (+0.0%) +£6 (+0.0%) +£3 (+0.0%) +£2 (+0.0%) +£2 (+0.0%) +£2 (+0.0%)
Decile 9 +£4 (+0.0%) +£6 (+0.0%) +£3 (+0.0%) +£3 (+0.0%) 0 0 0
Decile 10 fewer than 10 households change fewer than 10 households change fewer than 10 households change fewer than 10 households change fewer than 10 households change fewer than 10 households change fewer than 10 households change
all +£7 (+0.0%) +£10 (+0.0%) +£17 (+0.0%) +£41 (+0.1%) +£49 (+0.1%) +£36 (+0.1%) +£36 (+0.1%)
Year People gaining over £1 People losing over £1
2025-26 3.7% 0.0%
2026-27 3.7% 0.0%
2027-28 3.8% 0.0%
2028-29 3.8% 0.0%
2029-30 3.7% 0.0%
2030-31 3.6% 0.0%
2031-32 3.5% 0.0%

No change in any year: population.

Checks failing on both base and head (1)
  • VAT excluding private school fees is never negative (sign:vat): 2025-26: 76 units below zero, a weighted £-53,888,684 in total; 2026-27: 76 units below zero, a weighted £-55,777,260 in total; 2027-28: 76 units below zero, a weighted £-57,727,917 in total
Counted-once check

Each 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.

  • widowed_parents_allowance: pension_credit responds only through other heads in fewer than 10 households (other heads that moved there: carers_allowance; gov_spending; not a failure)
  • widowed_parents_allowance: pension_credit responds only through other heads in fewer than 10 households (other heads that moved there: carers_allowance; household_benefits; not a failure)
Provenance
Side Commit Runtime Environment hash Partition hash Time
base main 47a2817e2d uk 2.175.2 (files 97879027d30c), core 3.34.1, py 3.13.18 aarch64 f8b95f5110a2 3bcfb3473960 2511.43s
head #2688 merged into main 8540b4fd01 uk 2.175.2 (files 139232cc5389), core 3.34.1, py 3.13.18 aarch64 f8b95f5110a2 3bcfb3473960 2603.76s

Dataset: hf://policyengine/policyengine-uk-data-private/enhanced_frs_2024_25.h5@2526edbbb687a3b54dbb11272e5389e4b62d2ac2, sha256 056e80fd8e9151d78f432a25cde863340baee81304c6216a37fb575ce8563784 (verified on both sides), policyengine-uk-data 1.58.1.

Receipts by tax head: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Final charge on cash interest in non-cash ISAs £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Lifetime ISA withdrawal charge £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Income tax £294.46bn £315.23bn £335.82bn £351.28bn £366.96bn £385.36bn £399.99bn
National Insurance (employee and self-employed) £51.70bn £54.14bn £56.13bn £57.73bn £59.76bn £61.64bn £63.51bn
National Insurance (employer) £148.71bn £155.37bn £160.88bn £165.09bn £172.06bn £177.36bn £182.57bn
Employer NI change borne by capital £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Employer NI change borne by consumers £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Capital gains tax £12.60bn £13.15bn £13.60bn £14.11bn £14.67bn £15.24bn £15.80bn
Student loan repayments £7.82bn £8.13bn £8.39bn £8.58bn £8.79bn £9.04bn £9.29bn
VAT excluding private school fees £249.37bn £256.87bn £266.33bn £272.70bn £278.97bn £285.24bn £291.59bn
Change in VAT from its baseline £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
VAT on private school fees £1.95bn £2.01bn £2.09bn £2.14bn £2.20bn £2.26bn £2.31bn
Change in private school VAT from its baseline £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Fuel duties £24.71bn £24.13bn £26.00bn £26.83bn £26.51bn £25.78bn £24.63bn
Alcohol duties £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Tobacco duties £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Vehicle excise duties £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Betting and gaming duties £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
TV licence fee £4.26bn £4.40bn £4.41bn £4.42bn £4.43bn £4.44bn £4.45bn
Council tax £49.21bn £52.16bn £54.89bn £57.95bn £61.16bn £64.47bn £67.91bn
High value council tax surcharge £0.00bn £0.00bn £0.00bn £0.48bn £0.49bn £0.50bn £0.51bn
Northern Ireland domestic rates £0.55bn £0.58bn £0.60bn £0.63bn £0.66bn £0.69bn £0.72bn
Business rates £34.68bn £36.20bn £38.00bn £39.46bn £42.27bn £43.25bn £44.29bn
Stamp duty land tax on the household's own purchases £6.88bn £7.32bn £7.66bn £8.06bn £8.47bn £8.91bn £9.35bn
Stamp duty land tax paid by companies £4.72bn £4.57bn £5.29bn £6.03bn £6.37bn £6.92bn £7.45bn
Land transaction tax (Wales) £0.07bn £0.08bn £0.08bn £0.09bn £0.10bn £0.10bn £0.11bn
Land and buildings transaction tax (Scotland) £0.31bn £0.32bn £0.33bn £0.35bn £0.36bn £0.38bn £0.40bn
Unspecified tax change borne by capital £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Unspecified tax change borne by consumers £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Wealth tax £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Wealth tax excluding the main residence £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Land value tax £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Carbon tax £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Spending by benefit and service: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Lifetime ISA bonus £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
State Pension £121.68bn £125.92bn £126.72bn £129.48bn £132.98bn £136.65bn £140.41bn
Pension Credit £6.21bn £6.46bn £6.68bn £6.82bn £6.89bn £7.07bn £7.22bn
Winter Fuel Payment £1.86bn £1.85bn £1.79bn £1.77bn £1.78bn £1.78bn £1.78bn
Pension Age Winter Heating Payment (Scotland) £0.19bn £0.20bn £0.20bn £0.21bn £0.21bn £0.22bn £0.22bn
Universal Credit £75.70bn £80.84bn £83.49bn £86.22bn £87.64bn £88.69bn £89.30bn
Housing Benefit £8.56bn £8.89bn £9.23bn £9.46bn £9.71bn £10.04bn £10.29bn
Council Tax Reduction £2.07bn £2.20bn £2.32bn £2.45bn £2.59bn £2.73bn £2.87bn
Employment and Support Allowance (income-related) £0.79bn £0.82bn £0.84bn £0.86bn £0.88bn £0.90bn £0.92bn
Employment and Support Allowance (contribution-based) £5.20bn £5.37bn £5.56bn £5.69bn £5.82bn £5.95bn £6.09bn
Jobseeker's Allowance (income-based) £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Jobseeker's Allowance (contribution-based) £0.29bn £0.30bn £0.31bn £0.31bn £0.32bn £0.33bn £0.34bn
Income Support £0.11bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Working Tax Credit £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Child Tax Credit £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Incapacity Benefit £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Personal Independence Payment £26.95bn £28.03bn £29.05bn £30.09bn £30.75bn £31.51bn £32.21bn
Disability Living Allowance £7.49bn £7.79bn £8.07bn £8.36bn £8.55bn £8.76bn £8.95bn
Attendance Allowance £8.68bn £9.03bn £9.36bn £9.69bn £9.90bn £10.15bn £10.38bn
Carer's Allowance £4.48bn £4.66bn £4.83bn £5.00bn £5.11bn £5.27bn £5.42bn
Carer Support Payment (Scotland) £0.31bn £0.32bn £0.33bn £0.34bn £0.35bn £0.36bn £0.37bn
Carer's Allowance Supplement (Scotland) £0.00bn £0.04bn £0.04bn £0.05bn £0.05bn £0.05bn £0.05bn
Severe Disablement Allowance £0.03bn £0.03bn £0.03bn £0.03bn £0.03bn £0.03bn £0.03bn
Industrial Injuries Disablement Benefit £0.50bn £0.52bn £0.54bn £0.55bn £0.56bn £0.58bn £0.59bn
Armed Forces Compensation Scheme £0.61bn £0.63bn £0.65bn £0.66bn £0.68bn £0.69bn £0.71bn
War Widow's or Widower's Pension £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Child Benefit £16.19bn £16.82bn £17.39bn £18.00bn £18.39bn £18.84bn £19.25bn
Scottish Child Payment £0.50bn £0.52bn £0.53bn £0.55bn £0.57bn £0.58bn £0.59bn
Two Child Limit Payment (Scotland, counterfactual) £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Healthy Start vouchers £0.05bn £0.05bn £0.05bn £0.05bn £0.05bn £0.05bn £0.05bn
Maternity Allowance £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Statutory Maternity Pay £3.30bn £3.41bn £3.54bn £3.62bn £3.70bn £3.78bn £3.87bn
Statutory Paternity Pay £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Statutory Sick Pay £1.12bn £1.16bn £1.20bn £1.23bn £1.26bn £1.29bn £1.32bn
Sure Start Maternity Grant £0.01bn £0.01bn £0.01bn £0.01bn £0.01bn £0.01bn £0.01bn
Bereavement Support Payment £0.67bn £0.69bn £0.71bn £0.73bn £0.75bn £0.76bn £0.78bn
Widowed Parent's Allowance £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Tax-Free Childcare £0.56bn £0.56bn £0.57bn £0.58bn £0.59bn £0.60bn £0.59bn
Extended childcare entitlement £2.99bn £3.50bn £3.59bn £3.71bn £3.78bn £3.86bn £3.93bn
Universal childcare entitlement £2.06bn £2.22bn £2.29bn £2.37bn £2.43bn £2.48bn £2.53bn
Targeted childcare entitlement £0.50bn £0.51bn £0.52bn £0.54bn £0.55bn £0.57bn £0.57bn
Childcare Offer for Wales £0.26bn £0.28bn £0.28bn £0.29bn £0.30bn £0.31bn £0.32bn
Care to Learn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Childcare Grant £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Parents' Learning Allowance £0.20bn £0.20bn £0.20bn £0.20bn £0.20bn £0.20bn £0.20bn
Adult Dependants' Grant £0.06bn £0.06bn £0.04bn £0.04bn £0.04bn £0.04bn £0.04bn
Travel Grant £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Disabled Students' Allowance suppressed suppressed suppressed suppressed suppressed suppressed suppressed
16 to 19 Bursary Fund £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
NHS spending £203.89bn £204.30bn £204.56bn £204.91bn £205.34bn £205.85bn £206.35bn
State education spending £0.02bn £0.02bn £0.02bn £0.02bn £0.02bn £0.02bn £0.02bn
Transport subsidy spending £25.05bn £25.10bn £25.14bn £25.18bn £25.23bn £25.30bn £25.36bn
Cost-of-living support payment £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Energy Bills Rebate £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Household Energy Payment £0.00bn £2.11bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Energy Price Guarantee subsidy £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Basic income £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Disability basic income £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Other public spending budget change £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Caseloads: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Lifetime ISA bonus 0k 0k 0k 0k 0k 0k 0k
State Pension 11,264k 11,145k 10,819k 10,700k 10,723k 10,750k 10,776k
Pension Credit 1,490k 1,485k 1,479k 1,457k 1,453k 1,459k 1,466k
Winter Fuel Payment 7,844k 7,783k 7,473k 7,375k 7,391k 7,409k 7,427k
Pension Age Winter Heating Payment (Scotland) 803k 802k 800k 797k 799k 801k 802k
Universal Credit 6,355k 6,382k 6,387k 6,389k 6,388k 6,344k 6,292k
Housing Benefit 1,249k 1,245k 1,244k 1,228k 1,229k 1,234k 1,233k
Council Tax Reduction 3,180k 3,201k 3,206k 3,219k 3,234k 3,245k 3,252k
Employment and Support Allowance (income-related) 71k 71k 71k 71k 71k 71k 71k
Employment and Support Allowance (contribution-based) 719k 720k 721k 722k 724k 726k 728k
Jobseeker's Allowance (income-based) 0k 0k 0k 0k 0k 0k 0k
Jobseeker's Allowance (contribution-based) 67k 67k 67k 67k 67k 68k 68k
Income Support 25k 0k 0k 0k 0k 0k 0k
Working Tax Credit 0k 0k 0k 0k 0k 0k 0k
Child Tax Credit 0k 0k 0k 0k 0k 0k 0k
Incapacity Benefit 0k 0k 0k 0k 0k 0k 0k
Personal Independence Payment 3,567k 3,575k 3,579k 3,585k 3,593k 3,602k 3,611k
Disability Living Allowance 1,291k 1,294k 1,296k 1,298k 1,300k 1,304k 1,307k
Attendance Allowance 1,695k 1,698k 1,700k 1,703k 1,707k 1,711k 1,715k
Carer's Allowance 1,034k 1,037k 1,038k 1,040k 1,042k 1,060k 1,062k
Carer Support Payment (Scotland) 71k 71k 71k 72k 72k 72k 72k
Carer's Allowance Supplement (Scotland) 0k 71k 71k 72k 72k 72k 72k
Severe Disablement Allowance 4k 4k 4k 4k 4k 4k 4k
Industrial Injuries Disablement Benefit 137k 137k 137k 138k 138k 138k 139k
Armed Forces Compensation Scheme 93k 93k 94k 94k 94k 94k 94k
War Widow's or Widower's Pension 0k 0k 0k 0k 0k 0k 0k
Child Benefit 8,123k 8,120k 8,120k 8,130k 8,145k 8,161k 8,178k
Scottish Child Payment 351k 352k 352k 353k 354k 355k 352k
Two Child Limit Payment (Scotland, counterfactual) 0k 0k 0k 0k 0k 0k 0k
Healthy Start vouchers 200k 200k 200k 201k 201k 201k 202k
Maternity Allowance 0k 0k 0k 0k 0k 0k 0k
Statutory Maternity Pay 333k 333k 334k 334k 335k 336k 337k
Statutory Paternity Pay 0k 0k 0k 0k 0k 0k 0k
Statutory Sick Pay 181k 182k 182k 182k 182k 183k 183k
Sure Start Maternity Grant 26k 26k 26k 26k 26k 26k 26k
Bereavement Support Payment 98k 98k 98k 98k 98k 99k 99k
Widowed Parent's Allowance 0k 0k 0k 0k 0k 0k 0k
Tax-Free Childcare 1,043k 1,010k 998k 1,009k 999k 994k 986k
Extended childcare entitlement 795k 800k 796k 797k 798k 798k 792k
Universal childcare entitlement 1,071k 1,072k 1,072k 1,073k 1,076k 1,079k 1,074k
Targeted childcare entitlement 165k 156k 154k 154k 155k 155k 154k
Childcare Offer for Wales 39k 39k 39k 39k 39k 39k 39k
Care to Learn 0k 0k 0k 0k 0k 0k 0k
Childcare Grant 0k 0k 0k 0k 0k 0k 0k
Parents' Learning Allowance 98k 98k 98k 99k 99k 99k 99k
Adult Dependants' Grant 18k 18k 12k 12k 12k 12k 12k
Travel Grant 0k 0k 0k 0k 0k 0k 0k
Disabled Students' Allowance suppressed suppressed suppressed suppressed suppressed suppressed suppressed
16 to 19 Bursary Fund 0k 0k 0k 0k 0k 0k 0k
Cost-of-living support payment 0k 0k 0k 0k 0k 0k 0k
Energy Bills Rebate 0k 0k 0k 0k 0k 0k 0k
Household Energy Payment 0k 21,065k 0k 0k 0k 0k 0k
Energy Price Guarantee subsidy 0k 0k 0k 0k 0k 0k 0k
Basic income 0k 0k 0k 0k 0k 0k 0k
Disability basic income 0k 0k 0k 0k 0k 0k 0k
Government balance, each component counted once: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Government receipts, each component once £892.00bn £934.66bn £980.50bn £1,015.93bn £1,054.24bn £1,091.56bn £1,124.89bn
Government spending £529.16bn † £545.42bn † £550.70bn † £560.12bn † £568.03bn † £576.31bn † £583.92bn †
Government balance, each receipt once £362.84bn † £389.24bn † £429.80bn † £455.81bn † £486.20bn † £515.25bn † £540.97bn †
Benefits in household_benefits but not in gov_spending £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Model totals: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
gov_tax £892.00bn £934.66bn £980.50bn £1,015.93bn £1,054.24bn £1,091.56bn £1,124.89bn
household_tax £491.97bn £520.41bn £551.21bn £576.00bn £601.00bn £626.69bn £648.42bn
pre_budget_change_household_tax £484.15bn £512.28bn £542.82bn £566.95bn £591.72bn £617.16bn £638.61bn
gov_spending £529.16bn † £545.42bn † £550.70bn † £560.12bn † £568.03bn † £576.31bn † £583.92bn †
household_benefits £529.16bn † £545.42bn † £550.70bn † £560.12bn † £568.03bn † £576.31bn † £583.92bn †
gov_balance £362.84bn † £389.24bn † £429.80bn † £455.81bn † £486.20bn † £515.25bn † £540.97bn †
Household income totals: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Household market income £1,637.96bn £1,702.77bn £1,757.35bn £1,806.41bn £1,855.97bn £1,912.43bn £1,970.44bn
Household net income £1,636.47bn † £1,687.58bn † £1,715.39bn † £1,748.10bn † £1,779.53bn † £1,817.38bn † £1,860.01bn †
Household net income (HBAI definition) £1,449.35bn £1,499.59bn £1,530.80bn £1,565.79bn £1,598.73bn £1,635.96bn £1,676.93bn
Population: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
People 69,245k 69,383k 69,473k 69,592k 69,738k 69,912k 70,080k
Benefit units 35,110k 35,181k 35,226k 35,286k 35,360k 35,449k 35,534k
Households 31,046k 31,108k 31,149k 31,201k 31,267k 31,345k 31,420k
Poverty rates: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Absolute poverty, before housing costs: everyone 16.2% 15.5% 16.3% 16.2% 16.3% 16.4% 16.4%
Absolute poverty, before housing costs: children 24.5% 21.9% 22.9% 22.5% 22.8% 23.1% 22.9%
Absolute poverty, before housing costs: working-age adults 14.0% 13.8% 14.6% 14.6% 14.7% 14.8% 14.8%
Absolute poverty, before housing costs: pensioners 13.7% 13.4% 14.0% 13.8% 13.9% 13.9% 13.9%
Absolute poverty, after housing costs: everyone 21.6% 21.0% 22.0% 22.4% 22.7% 23.0% 23.3%
Absolute poverty, after housing costs: children 33.3% 31.1% 32.1% 32.3% 32.6% 33.3% 33.9%
Absolute poverty, after housing costs: working-age adults 19.9% 19.8% 20.8% 21.4% 21.7% 21.9% 22.2%
Absolute poverty, after housing costs: pensioners 12.7% 12.4% 13.1% 13.2% 13.3% 13.4% 13.6%
Relative poverty, before housing costs: everyone 16.9% 15.9% 16.3% 16.3% 16.3% 16.4% 16.5%
Relative poverty, before housing costs: children 25.8% 22.5% 22.9% 22.7% 22.7% 23.0% 23.1%
Relative poverty, before housing costs: working-age adults 14.5% 14.0% 14.5% 14.6% 14.7% 14.7% 14.9%
Relative poverty, before housing costs: pensioners 14.5% 14.0% 13.9% 13.9% 13.8% 13.9% 13.9%
Relative poverty, after housing costs: everyone 21.5% 20.9% 21.1% 21.1% 21.2% 21.4% 21.6%
Relative poverty, after housing costs: children 33.1% 30.8% 30.8% 30.6% 30.7% 31.1% 31.5%
Relative poverty, after housing costs: working-age adults 19.9% 19.7% 20.1% 20.2% 20.3% 20.5% 20.7%
Relative poverty, after housing costs: pensioners 12.5% 12.4% 12.3% 11.8% 11.7% 11.8% 12.0%
People in poverty: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Absolute poverty, before housing costs: everyone 11,244k 10,726k 11,307k 11,265k 11,398k 11,486k 11,501k
Absolute poverty, before housing costs: children 3,666k 3,284k 3,440k 3,386k 3,437k 3,491k 3,464k
Absolute poverty, before housing costs: working-age adults 5,953k 5,871k 6,269k 6,320k 6,388k 6,417k 6,457k
Absolute poverty, before housing costs: pensioners 1,624k 1,571k 1,597k 1,558k 1,573k 1,578k 1,580k
Absolute poverty, after housing costs: everyone 14,940k 14,552k 15,252k 15,617k 15,831k 16,077k 16,329k
Absolute poverty, after housing costs: children 4,974k 4,656k 4,806k 4,847k 4,903k 5,026k 5,126k
Absolute poverty, after housing costs: working-age adults 8,461k 8,440k 8,944k 9,281k 9,428k 9,538k 9,664k
Absolute poverty, after housing costs: pensioners 1,505k 1,456k 1,501k 1,489k 1,501k 1,513k 1,539k
Relative poverty, before housing costs: everyone 11,709k 11,007k 11,292k 11,309k 11,346k 11,451k 11,557k
Relative poverty, before housing costs: children 3,853k 3,376k 3,440k 3,408k 3,422k 3,475k 3,497k
Relative poverty, before housing costs: working-age adults 6,136k 5,983k 6,262k 6,331k 6,362k 6,402k 6,479k
Relative poverty, before housing costs: pensioners 1,719k 1,648k 1,590k 1,571k 1,561k 1,573k 1,581k
Relative poverty, after housing costs: everyone 14,860k 14,474k 14,684k 14,673k 14,754k 14,948k 15,142k
Relative poverty, after housing costs: children 4,943k 4,615k 4,617k 4,597k 4,625k 4,695k 4,758k
Relative poverty, after housing costs: working-age adults 8,427k 8,407k 8,665k 8,750k 8,804k 8,920k 9,022k
Relative poverty, after housing costs: pensioners 1,489k 1,452k 1,402k 1,327k 1,325k 1,333k 1,362k
Inequality: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Gini coefficient, equivalised HBAI income before housing costs 0.3077 0.3048 0.3053 0.3049 0.3053 0.3063 0.3074
Mean household net income in each side's own deciles: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Decile 1: mean household_net_income per household £17,702 † £17,973 † £17,910 † £18,068 † £18,284 † £18,605 † £18,821 †
Decile 2: mean household_net_income per household £27,994 † £29,065 † £28,826 † £29,212 † £29,409 † £29,685 † £30,150 †
Decile 3: mean household_net_income per household £34,305 † £35,483 † £36,143 † £36,611 † £37,349 † £37,764 † £38,480 †
Decile 4: mean household_net_income per household £38,961 † £40,743 † £41,802 † £42,908 † £43,416 † £44,330 † £44,511 †
Decile 5: mean household_net_income per household £43,242 † £45,585 † £45,948 † £47,271 † £48,141 † £48,907 † £50,297 †
Decile 6: mean household_net_income per household £48,163 † £49,435 † £50,929 † £52,008 † £52,853 † £53,666 † £54,659 †
Decile 7: mean household_net_income per household £55,415 † £56,291 † £57,442 † £57,719 † £58,734 † £59,911 † £61,073 †
Decile 8: mean household_net_income per household £68,653 † £70,758 † £72,142 † £73,176 † £73,498 † £74,927 † £77,144 †
Decile 9: mean household_net_income per household £78,891 † £81,016 † £80,565 † £82,647 † £85,462 † £86,944 † £89,508 †
Decile 10: mean household_net_income per household £116,861 † £120,161 † £124,546 † £126,420 † £125,753 † £128,368 † £130,694 †
Standard reform costings (change in gov_balance): head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Raise basic rate by 1pp £6.41bn £6.66bn £6.63bn £6.83bn £7.03bn £7.25bn £7.48bn
Raise higher rate by 1pp £3.61bn £3.87bn £4.00bn £4.22bn £4.44bn £4.71bn £4.83bn
Raise personal allowance by ~800GBP/year £-3.81bn £-3.93bn £-3.99bn £-4.05bn £-4.11bn £-4.23bn £-1.95bn
Raise child benefit by 25GBP/week per additional child £-1.99bn £-1.75bn £-1.55bn £-1.38bn £-1.30bn £-1.17bn £-1.07bn
Reduce Universal Credit taper rate to 20% £-20.96bn £-21.79bn £-22.27bn £-22.72bn £-22.89bn £-23.17bn £-23.28bn
Raise Class 1 main employee NICs rate to 10% £10.89bn £11.30bn £11.70bn £12.00bn £12.30bn £12.62bn £12.99bn
Raise VAT standard rate by 2pp £24.89bn £25.71bn £26.63bn £27.24bn £27.87bn £28.50bn £29.14bn
Raise additional rate by 3pp £2.90bn £3.15bn £3.22bn £3.41bn £3.61bn £3.85bn £4.14bn
Raise the Universal Credit standard allowance by 10% £-3.06bn £-3.21bn £-3.29bn £-3.41bn £-3.49bn £-3.53bn £-3.54bn
Raise the State Pension by 10% £-7.76bn £-7.99bn £-8.22bn £-8.45bn £-8.79bn £-8.53bn £-8.87bn
Standard reforms: change in each reform's target: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Raise basic rate by 1pp: change in income_tax £6.55bn £6.86bn £6.79bn £7.00bn £7.20bn £7.44bn £7.66bn
Raise higher rate by 1pp: change in income_tax £3.61bn £3.92bn £4.01bn £4.23bn £4.45bn £4.72bn £4.84bn
Raise personal allowance by ~800GBP/year: change in income_tax £-3.93bn £-4.07bn £-4.13bn £-4.18bn £-4.22bn £-4.40bn £-1.75bn
Raise child benefit by 25GBP/week per additional child: change in child_benefit £2.33bn £2.14bn £1.94bn £1.76bn £1.65bn £1.52bn £1.40bn
Reduce Universal Credit taper rate to 20%: change in universal_credit £20.93bn £21.78bn £22.31bn £22.76bn £22.91bn £23.20bn £23.33bn
Raise Class 1 main employee NICs rate to 10%: change in national_insurance £11.15bn £11.63bn £12.00bn £12.29bn £12.61bn £12.94bn £13.31bn
Raise VAT standard rate by 2pp: change in vat £24.69bn £25.51bn £26.42bn £27.03bn £27.65bn £28.28bn £28.91bn
Raise additional rate by 3pp: change in income_tax £2.90bn £3.15bn £3.22bn £3.42bn £3.62bn £3.86bn £4.12bn
Raise the Universal Credit standard allowance by 10%: change in universal_credit £3.07bn £3.21bn £3.31bn £3.43bn £3.49bn £3.53bn £3.55bn
Raise the State Pension by 10%: change in state_pension £10.68bn £11.17bn £11.28bn £11.60bn £12.03bn £12.46bn £12.96bn

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

@juaristi22 juaristi22 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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

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 ID 121824890358, completed 2026-10-26T21:33:19Z: FAILURE.
  • Test / Result, check ID 121862213237, completed 2026-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.

MaxGhenis and others added 2 commits October 27, 2026 07:34
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>
@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

0d760fccd and the description now carry the fixes:

  • P2-1. The changelog fragment names the Pension Credit categories that a financial-only report no longer replaces, and says what the household land formula reads.
  • P2-2, P3-1 to P3-6 and P3-12. The description takes your text. Its local runs and checks name 1aacd327b, since 0d760fccd changes only text, and the means-test paragraph drops the dataset-as-actor sentence that P3-9 flags in the docstring.
  • P3-7. Both comments name uc_assessable_capital. pe-uk merges pull requests with merge commits, so Value means-tested capital less 10% sale expenses and secured debt #1969 lands as it stands and this PR changes the two comments after it.
  • P3-8. The grid comment keeps your facts, with the formulas as actors.
  • P3-9 to P3-11. As you wrote them; the rate description also drops "is deducted".
  • P3-13. axiom: TheAxiomFoundation/rulespec-uk#395 queued, with a note on #395 on the two categories and the overseas valuation rules.

P3-14 needs no change here.

@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

Ready for hub review

Verdict: REQUEST_CHANGES at 0d760fccdd5131dedc709fbe8a5b4b0293f01420 (property-wealth-overseas-other)

0d760fccd changes only text, and with the new description it closes every P2 and P3 of review 867. All 21 CI checks at this head pass. CI's aggregate figures match those at 1aacd327b, and #1969's too. Both merges are clean, and the new axiom: line is a standard option of the template at main.

One finding needs a change before this is ready:

  • The pension_credit_reported_capital documentation (P2-1). It says "An override set directly applies only in the supplied assessment year". It says nothing about stored values, although the sentence after it, which this round rewrote, is about a dataset that stores a total in that column. The model uprates such a stored value as a complete override through 2032. By Vahid's measurement, the certified file stores one for 37,916 of its 61,214 benefit units.

The P3s are text:

  • The Add parameter for marriage allowance-eligible tax bands  #395 note. Its quotation, its paraphrase of the two limbs, its Universal Credit sentence, its claim about the survey and its actors.
  • The changelog fragment. Its Pension Credit list can be read to include the main residence.
  • The description. Four sentences: the stored Pension Credit total, council tax reduction, the stacking sentence and the aggregate citation.
  • Comments, YAML and docs. Two test comments, the UC rate description and two engineering-page sentences.
  • Outside this round's delta. The legacy sale-expense lists don't cite the regulations for capital outside the United Kingdom.

No formula or test expectation needs to change.

I ran everything below on detached worktrees, with uv sync --frozen --extra dev and with HUGGING_FACE_TOKEN and POLICYENGINE_UK_DEFAULT_DATASET unset:

git status --porcelain in the head checkout stayed empty. "Body line N" means line N of the live body's Markdown source. That source is byte-identical to the hub's PR_BODY.md (gh api repos/PolicyEngine/policyengine-uk/pulls/2688 --jq .body, then diff). The witness scripts are listed at the end.

P1

None.

P2

P2-1. The pension_credit_reported_capital documentation lets a reader confine a stored override to the dataset's year, but the model carries it to 2032

policyengine_uk/variables/gov/dwp/pension_credit/pension_credit_reported_capital.py:17-18 says: "An override set directly applies only in the supplied assessment year."

  • What it covers. The codebase uses "set directly" for a value set in a situation, and for that the sentence holds. test_benunit_reported_capital_properties.py:27-30 says a programme variable "set directly applies only to the years it is set". :24-26 says a dataset that stores "either programme variable directly" "projects it to later years".
  • What it leaves out. The string never says what happens to a stored value, yet its next sentence, which this round rewrote, is about a dataset that stores a financial-only total "in this column". A reader of the documentation sees neither the tests nor uprating_indices.yaml, so they would confine that stored total, and the property it drops, to the dataset's year.
  • What the model does.
    • uprating_indices.yaml:143-147 lists the column, so Simulation uprates a single-year dataset's stored value into each projected year up to PROJECTION_END_YEAR, which is 2032 (data/economic_assumptions.py:172).
    • After that, the formula's -1 applies (:163-168).
    • Through all those years, the value replaces the whole Pension Credit assessment.

Witness at the head (pc_years.py; one person aged 75 in London with £40,000 of other residential property, plus £1,000 of savings in the dataset cases):

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 TOTCAPB4 in 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 at b4e872273 has nothing like it. It copies main's uc_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 HpropWR8 measure "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.py at the head gives pension_credit_assessable_capital 64,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_wealth of £300,000 gives household_land_value 170,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_capital for the property to be counted" (docs/engineering/property-wealth-components.md:160-161). The thread says the fix is "either a data-release move to benunit_reported_capital or 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 FRS TOTCAPB4 in that column. It also says: "On enhanced_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 with owned_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 documentation attribute: the class has no docstring (an AST check gives None). 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 TOTCAPB4 in 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 to benunit_reported_capital (pension_credit_reported_capital documentation; 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_reduction tests uc_assessable_capital for 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_value 0: uc_assessable_capital 0.00 and newham_council_tax_reduction 1,260.00.
    • With 8,000: uc_assessable_capital 7,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_reduction falls to 0.00, while council_tax_reduction_assessable_capital stays at 0.00.

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 its capital/property_sources.yaml, and council_tax_reduction_assessable_capital through Pension Credit's capital on a savings-credit-only award. The Merton, Kingston upon Thames, Newham and Westminster council tax reduction formulas also test uc_assessable_capital for 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_capital adds stocks_and_shares_isa to its itemised sum (variables/household/wealth/share_like_capital.py:41-55).
  • UC's sale_expenses/sources.yaml:41-42 lists share_like_capital.
  • isa_data.py rebuilds the test's dataset and prints, for 2027:
    • corporate_wealth 0.0000;
    • stocks_and_shares_isa and share_like_capital both 2,144.4800;
    • uc_assessable_capital 6,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.

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.yaml list share_like_capital, and UC's lists only share_like_capital.
  • In row 9, share_like_capital equals the supplied corporate_wealth: 10,000.00 at 2da212abc and 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."

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.py and test_stored_household_totals.py, so git diff b4e872273..HEAD holds 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 1aacd327b merged into main with main at 52b30809ff on the certified enhanced_frs_2024_25.h5 (policyengine-uk-data 1.58.1), and its run at 0d760fccd against main at 1d4d020aa (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_sources formulas applies p.sale_expenses.rate: uc_other_residential_property_capital.py:27, uc_non_residential_property_capital.py:27, uc_overseas_property_capital.py:22-26 and uc_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 gives uc_other_residential_property_capital 40,000.00, not 45,000.
  • Constraints. policyengine_uk/tests/test_parameter_descriptions.py:66-76 requires a description that ends with a full stop, and docs/engineering/parameter-descriptions.md:45-48 asks 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_capital rather than in benunit_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 to benunit_reported_capital before 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 docs at the head finds nothing.
  • docs/engineering/skills/model-structure.md:28 says "Cite durable policy sources in reference fields 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/20

The 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.dump is identical for the three test files, so only comments changed. pension_credit_reported_capital.py differs only in one string constant, its documentation attribute: with string constants masked, the trees are identical.
  • YAML. rate.yaml loads to the same data once description is 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 . and uvx ruff@0.17.2 check --select F811,F821 policyengine_uk print "All checks passed!". git diff --check 1aacd327b 0d760fccd is clean.
  • The changed parameter. CountryTaxBenefitSystem().parameters.gov.dwp.universal_credit.means_test.capital.sale_expenses.rate gives 0.1 on 2026-01-01, with the new description.
  • Code health. uv run --frozen pytest policyengine_uk/tests/code_health -n 4 gives 3,115 passed (288 s). No code-health test constrains a parameter description's wording; test_parameter_descriptions.py requires only a closing full stop.
  • The three touched test files, each with -n 4:
    • test_property_wealth_components.py: 61 passed
    • test_isa_balances.py: 2 passed
    • test_stored_household_totals.py: 42 passed

The description

  • Live body. The live title and body equal the hub's PR_TITLE.txt and PR_BODY.md (diff is 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_capital and less_sale_expenses.
    • capital.sources, which is code at capital_valuation.py:47-49.
    • household/wealth and gov/dwp, which are YAML test directories under policyengine_uk/tests/policy/baseline/.
    • enhanced_frs_2024_25.h5, which is certified_dataset.yaml:10.
    • Every SHA. d0648474a is GitHub's merge commit, which the REST API resolves.
  • Fix evidence. .github/check_fix_pr.py, run the way pr_fix_evidence.yaml runs it, prints "Fix evidence: PASS … Changed executable tests: 31". I ran it from main's checkout at 1d4d020aa, whose checker equals 52b30809f's, with:

    • the live title and body;
    • empty labels;
    • git diff --name-status --no-renames -z from the merge base 52b30809f;
    • --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 0d760fccd changes 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_capital of 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 1aacd327b and review 867's evidence.

  • 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 1aacd327b gives 45 files, 2,631 insertions and 85 deletions.

The Axiom line and the #395 note

  • The template. .github/PULL_REQUEST_TEMPLATE.md at main 1d4d020aa, identical at the head, offers "axiom: encoded-correct | encoded | queued | n/a: ". It keeps axiom: needed for external contributors. axiom: TheAxiomFoundation/rulespec-uk#395 queued is the <rulespec-uk issue> queued option, 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-158 asks that a queued issue be "dispatch-ready and labelled pe-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_value and other_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 from main's Disregard WTC before the Housing Benefit childcare deduction #2776, which adds three Python test files and housing_benefit_wtc_disregard.yaml (13 tests). The body's counts are 1aacd327b'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 main 1d4d020aa1 with 83e479b06b on the same file and sha256. A diff against the 1aacd327b report 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 new main.

Merges

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 (labelled pe-parity, the blocker recorded, a module path, the verbatim law, required outputs and companion tests).
  • 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=123 is 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-2688 files 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.py and test_stored_household_totals.py.

    I left test_capital_share_components.py to 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

@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

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
@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

PR_TITLE.txt

Add overseas and other real estate to property wealth and capital

Generated by Claude Code

@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

PR_BODY.md

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 draft on [#1969](https://github.com/PolicyEngine/policyengine-uk/pull/1969): it merges #1969's head `b4e872273`, 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's `test_isa_balances.py` and `test_stored_household_totals.py`, so `git diff b4e872273..HEAD` holds only this PR's own change. Companion [data #543](https://github.com/PolicyEngine/policyengine-uk-data/pull/543) follows [data #501](https://github.com/PolicyEngine/policyengine-uk-data/pull/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_value` and `other_property_value`. Both carry GDP-per-capita uprating, as a class attribute for situations and in `policyengine_uk/data/uprating_indices.yaml` under `gov.economic_assumptions.yoy_growth.obr.per_capita.gdp` for datasets.

**Wealth.** `property_wealth` adds `residential_property_value`, `non_residential_property_value`, `owned_land`, `overseas_property_value` and `other_property_value`. `total_wealth` adds `property_wealth` once, beside corporate, pension and savings wealth. A saved `property_wealth` column still overrides the formula; the data release must stop storing it.

**Household land.** `household_land_value` applies the regional land intensity to `residential_property_value + non_residential_property_value + other_property_value`, summed directly, then adds `owned_land` at 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 saved `property_wealth` total, 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 with `main_residence_value` of £200,000 and a saved `property_wealth` of £300,000, household land falls from £255,000 on `main` to £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](https://github.com/PolicyEngine/policyengine-uk/pull/2688#issuecomment-6449462413) at `798491cb7`). `git diff 798491cb7 80e0d344f` shows no change to `household_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:

| Programme | Capital sources | Sale expenses | Financial-only report |
| --- | --- | --- | --- |
| Universal Credit | `uc_overseas_property_capital` and `uc_other_property_capital` in `capital/property_sources.yaml` | each formula deducts 10% once (`less_sale_expenses`) | `benunit_reported_capital` replaces the financial pool only; the property pool stays counted |
| Pension Credit | `capital/sources.yaml` and `capital/property_sources.yaml` | `capital/sale_expenses/sources.yaml` | `pension_credit_assessable_capital` adds the report to separately valued land and property, including existing domestic categories |
| Housing Benefit | `means_test/capital/sources.yaml` | `sale_expenses/sources.yaml` | the savings-credit-only route inherits Pension Credit's total |
| Income Support, income-based JSA, income-related ESA | each `capital/sources.yaml` | each `sale_expenses/sources.yaml` | none replaces its source pool |

A complete `uc_reported_capital` override still replaces ordinary UC capital before the regulation 77 company substitution. A nonnegative `pension_credit_reported_capital` still replaces the whole Pension Credit assessment; its formula returns -1, which means no complete override. If a dataset stores a financial-only survey total in `pension_credit_reported_capital` rather than in `benunit_reported_capital`, the `pension_credit_assessable_capital` formula 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 FRS `TOTCAPB4` in that column, and 37,916 of the file's 61,214 benefit units carry a nonnegative value ([measurement](https://github.com/PolicyEngine/policyengine-uk/pull/2688#issuecomment-6449451323)), 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 to `benunit_reported_capital` (`pension_credit_reported_capital` documentation; `docs/engineering/property-wealth-components.md`). The uprating index for `pension_credit_reported_capital` uprates 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_capital` reads each variable in its `property_sources` list directly. The `pension_credit_assessable_capital` formula passes Pension Credit's list to `valued_capital(..., sources=...)`, which skips any name missing from `capital.sources`. A test checks each name in that list against `capital.sources` on every date either list changes, and each name in UC's list against the model's variables.

**Main.** This branch gets `main` at `52b30809f` through #1969, whose head `b4e872273` merges it. Between `2da212ab` and `52b30809f`, `main` changed no capital source list and no assessable-capital formula. `main`'s #2781 makes `Simulation` grow stored `cash_isa`, `stocks_and_shares_isa` and `household_lifetime_isa_balance` totals past the projection horizon. In `uprating_indices.yaml`, this branch keeps #1969's file, adds `other_property_value` and `overseas_property_value`, and rewrites the comment on `pension_credit_reported_capital`. `git merge-tree` merges this head with `main` at `1d4d020aa` without conflict.

**Stored totals.** `test_stored_household_totals.py::test_stored_household_totals_grow_past_the_projection_horizon`, which `main`'s #2781 added, failed in CI on `d0648474a`, GitHub's merge of `f40cb894c` into `main` at `0080274d`: 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's `c11d7c1d9` makes 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's `capital/sources.yaml`, Universal Credit through its `capital/property_sources.yaml`, and `council_tax_reduction_assessable_capital` through Pension Credit's capital on a savings-credit-only award. The Merton, Kingston upon Thames, Newham and Westminster council tax reduction formulas also test `uc_assessable_capital` for an applicant with a Universal Credit award, 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

- [UC Regulations 2013 reg. 49](https://www.legislation.gov.uk/uksi/2013/376/regulation/49): the Secretary of State values capital at market value less 10% where a sale would incur expenses, and less secured debt; [Schedule 10](https://www.legislation.gov.uk/uksi/2013/376/schedule/10) lists the disregards. [Reg. 6(1)](https://www.legislation.gov.uk/uksi/2013/376/regulation/6) sets penny rounding.
- [State Pension Credit Regulations 2002 reg. 19](https://www.legislation.gov.uk/uksi/2002/1792/regulation/19) values Pension Credit capital the same way.
- We cite each legacy programme's own valuation regulation in its parameter files (JSA reg. 111, ESA reg. 113, IS reg. 49, HB reg. 47). For capital outside the United Kingdom, we also cite in each legacy and Pension Credit sale-expense list the regulation under which the decision maker also takes 10% off where there would be expenses attributable to sale: [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).

## Invariants

- `property_wealth` equals the sum of its saved components for every nonnegative input.
- Each pound of each component, and of corporate, pension and savings wealth, counts once in `total_wealth`.
- When every property input is nonnegative, `household_land_value` never falls below `owned_land`, and an overseas-only household has zero UK land. A negative input breaks the bound: `other_property_value` of -1 with `owned_land` of £20,000.50 in London gives £19,999.65.
- A nonnegative financial-only report never reduces counted property for UC or Pension Credit.
- Every name in Pension Credit's `property_sources` list appears in its `capital.sources` on each date either list changes, and every name in UC's list names a model variable.
- On the pinned company-capital cases, each benefit unit's UC capital equals the float32 conversion of the penny-rounded oracle.
- Leaving both new inputs at zero, or omitting them, reproduces `main`'s wealth, land and capital outputs on a hand-built grid pinned from `main` at `2da212ab`. 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 #1969's formulas take sale expenses off other residential and non-residential property and off `share_like_capital`, which holds row 9's `corporate_wealth`, and so change their other capital outputs independently of the new inputs. Row 8's UC capital comes to £207,000 on `main` at `2da212ab` and `6c6e229a`, at `f40cb894c` and at `1aacd327`.

## Tests

`80e0d344f` changes only text on top of `0d760fccd`: the `pension_credit_reported_capital` documentation 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 at `80e0d344f`, on a clean checkout after `uv sync --frozen --extra dev`, and loaded no survey data:
- `uv run --frozen pytest <path> -n 4`: `test_parameter_descriptions.py` **24 passed**, `test_isa_balances.py` **2 passed**, `test_property_wealth_components.py` **61 passed**, `policyengine_uk/tests/code_health` **3,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 .` and `uvx ruff@0.17.2 check --select F811,F821 policyengine_uk`: all checks passed. `git diff --check 0d760fccd..80e0d344f`: clean.
- `.github/check_fix_pr.py` on this title and body, with the diff from the merge base `52b30809f`: **PASS**.
- `git merge-tree --write-tree --name-only` merges `80e0d344f` with `main` at `1d4d020aa` and with #1969's head `b4e872273` without conflict.

For one person aged 75 in London with £40,000 of other residential property, a 2025 dataset that also holds £1,000 of savings and stores 1,000 in `pension_credit_reported_capital` gives Pension Credit capital of 1,000.00 in 2025, 1,040.00 in 2026 and 1,268.69 in 2032, when the column reads the uprated stored value, and 45,627.87 in 2033, when the formula returns -1. Without the column, the same dataset gives 37,792.48 in 2026. A situation that sets 1,000 for 2026 gives 1,000.00 in 2026 and 36,000.00 in 2027, when the column reads -1.

`0d760fccd` changes only text: the changelog fragment, test comments, the `pension_credit_reported_capital` documentation string, one parameter description and the engineering page. We ran each command below on its own, on a clean checkout of `1aacd327b615b20ecfe43562cf890c032e1d4dd5` after `uv 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 passed**
- `test_isa_balances.py`: **2 passed**
- `test_uc_company_owner_properties.py`: **7 passed**
- `test_pension_credit_reported_capital_properties.py`: **4 passed**
- `policy/baseline/finance/benefit/family/universal_credit/test_uc_capital_allocation.py`: **1 passed**
- `test_benunit_reported_capital_properties.py`: **7 passed**
- `test_capital_valuation_properties.py`: **18 passed**
- `test_projection_horizon.py`: **14 passed**
- `test_savings_credit_only_receipt_properties.py`: **6 passed**
- `test_stock_capital_periods.py`: **2 passed**
- `test_uc_company_override_properties.py`: **11 passed**
- `test_uc_property_capital.py`: **65 passed**
- `test_uc_property_capital_mock.py`: **26 passed**
- `test_uprating_inputs_reach_data.py`: **18 passed**
- `test_savings_income_uprating.py`: **134 passed**
- `test_capital_share_components.py`: **14 passed** (2,238 s)

Of the test files `main` changed between `2da212ab` and `52b30809f`, only `test_stored_household_totals.py` reads `property_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 passed**
- `housing_benefit_capital.yaml`: **21 passed**
- `income_support_capital.yaml`: **18 passed**
- `jsa_income_capital.yaml`: **17 passed**
- `uc_assessable_capital.yaml`: **23 passed**
- `pension_credit_reported_capital.yaml`: **31 passed**
- `benunit_reported_capital.yaml`: **6 passed**
- `household_land_value.yaml`: **15 passed**
- `property_wealth_components.yaml`: **13 passed**
- `capital_valuation.yaml`: **26 passed**
- `pension_credit_income.yaml`: **10 passed**
- `council_tax_reduction_pension_credit.yaml`: **23 passed**

YAML directories, with the same command:
- `household/wealth`: **45 passed**
- `gov/dwp`: **856 passed**
- Universal Credit: **580 passed**
- ESA income: **74 passed**
- Housing Benefit: **593 passed**
- Income Support: **140 passed**
- JSA income: **40 passed**
- council tax reduction: **331 passed**

With `main`'s version of `test_stored_household_totals.py` at `1aacd327b`, `test_stored_household_totals_grow_past_the_projection_horizon` fails with £5,800.00 against £6,000.00 ± 0.06, as it failed in CI on `d0648474a`, 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 .` and `uvx ruff@0.17.2 check --select F811,F821 policyengine_uk`: all checks passed.
- `git diff --check f40cb894c..1aacd327b`: clean.
- `.github/check_fix_pr.py` on the title and body, with the diff from the merge base with `main`: **PASS**, 31 changed executable tests.
- `git merge-tree --write-tree --name-only 1aacd327b origin/main` at `52b30809f`: no conflict.
- `git diff --stat b4e872273..1aacd327b`: 45 files changed, 2,631 insertions, 85 deletions.

Runs at `f40cb894c` on clean checkouts.

We ran each Python file on its own with `uv run --frozen pytest <file> -n 4`:
- `test_property_wealth_components.py`: **61 passed**
- `test_isa_balances.py`: **2 passed**
- `test_uc_company_owner_properties.py`: **7 passed**
- `test_pension_credit_reported_capital_properties.py`: **4 passed**
- `policy/baseline/finance/benefit/family/universal_credit/test_uc_capital_allocation.py`: **1 passed**
- `test_benunit_reported_capital_properties.py`: **7 passed**
- `test_capital_valuation_properties.py`: **8 passed**
- `test_projection_horizon.py`: **14 passed**
- `test_savings_credit_only_receipt_properties.py`: **6 passed**
- `test_stock_capital_periods.py`: **2 passed**
- `test_uc_company_override_properties.py`: **11 passed**
- `test_uc_property_capital.py`: **65 passed**
- `test_uc_property_capital_mock.py`: **26 passed**
- `test_uprating_inputs_reach_data.py`: **18 passed**
- `test_savings_income_uprating.py`: **134 passed**
- `test_capital_share_components.py`: **14 passed**

YAML directories at `f40cb894c`, with `uv run --frozen policyengine-core test <directory> -c policyengine_uk`:
- `household/wealth`: **45 passed**
- `gov/dwp`: **851 passed**
- Universal Credit: **580 passed**
- ESA income: **74 passed**
- Housing Benefit: **593 passed**
- Income Support: **140 passed**
- JSA income: **40 passed**
- council tax reduction: **331 passed**

Mutation checks at `f40cb894c`:
- `test_new_inputs_at_zero_reproduce_main` fails when `overseas_property_value` defaults to £1,000 instead of zero: **9 failed, 9 passed**, one failure for each omitted-input row.
- It also fails when a mutant removes `uc_non_residential_property_capital` from UC's `property_sources.yaml`: **2 failed, 16 passed**. Row 8's UC capital falls to £135,000.
- `test_household_capital_is_allocated_and_conserved_across_benefit_units` fails when `uc_assessable_capital` skips its final penny rounding, and when one benefit unit loses a penny.
- `test_property_source_lists_name_configured_sources` fails 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**.
- Ruff format and lint pass.
- The F811/F821 check, `git diff --check` and the fix-evidence check pass.
- CI on the PR merged with `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 `1aacd327b`: 11,796 of 11,859 collected Python tests passed (61 skipped, 2 xfailed) with the dataset present, 5,588 YAML tests passed, and the documentation build passed. CI's Aggregate impact job ([run 41604662583](https://github.com/PolicyEngine/policyengine-uk/actions/runs/41604662583)) compared `1aacd327b` merged into `main` with `main` at `52b30809ff` on the certified `enhanced_frs_2024_25.h5` (policyengine-uk-data 1.58.1), and its run at `0d760fccd` against `main` at `1d4d020aa` ([run 41627481670](https://github.com/PolicyEngine/policyengine-uk/actions/runs/41627481670)) gives the same figures. 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 41588793376](https://github.com/PolicyEngine/policyengine-uk/actions/runs/41588793376)), 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](https://claude.com/claude-code)


Generated by Claude Code

@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

RS395_NOTE.md

## Extension for policyengine-uk#2688: overseas and other real estate

In policyengine-uk#2688, PolicyEngine adds 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. The formulas read no secured-debt input for either category and apply no disregard specific to either, so they deduct no encumbrance from them.

The Secretary of State sets the legacy and Pension Credit rules for capital outside the United Kingdom (table above) in [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 Secretary of State places the closing words after limb (b), at the level of the whole regulation. In IS reg 50 the Secretary of State words them "less, where there would be expenses attributable to sale, 10 per cent and the amount of any incumbrance secured on it", and in the other five changes only "10 per cent" and "incumbrance": "10 per cent." in JSA reg 112, "10%" in ESA reg 114, "10 per cent." and "encumbrances" in HB reg 48, "10 per cent." and "encumbrance" in HB (SPC) reg 46, and "10 per cent.," and "encumbrance" in SPC reg 20. So an encoder of these regulations needs the module 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. Under limb (a), the decision maker takes 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. Under limb (b), where that country prohibits that transfer, the decision maker takes the price the capital "would realise if sold in the United Kingdom to a willing buyer". For Income Support and JSA, DWP tells its decision makers to apply 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, the encoder needs the module 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 encoder then needs the module 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)).

The policyengine-uk formulas read no input for a transfer prohibition, so they value all overseas property at the limb (a) value, or at the reg 49(2)(a) value for Universal Credit.


Generated by Claude Code

@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

Ready for hub review

Head: 80e0d344fd578042da5be96b48dcf11c2f453aed on claude/pension-credit-text-fixes-0manua, one commit on 0d760fccdd5131dedc709fbe8a5b4b0293f01420. I didn't push to property-wealth-overseas-other. The new PR_TITLE.txt (unchanged), PR_BODY.md and RS395_NOTE.md are in the three comments above. I posted nothing on rulespec-uk#395.

Findings

  • P2-1. pension_credit_reported_capital.py:17-21 now carries the review's text word for word. Witness at the head (pc_years.py: one person aged 75 in London with £40,000 of other residential property, plus £1,000 of savings in the dataset cases):

    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
    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

    Every row matches the review. The last row needs the property set for 2027 as well. With the property set only for 2026, the model uprates it and 2027 gives 36,878.33. The text holds against every row: the situation value applies only to 2026, the stored value carries through 2032, and the formula returns -1 in 2033.

  • P3-1. I rewrote RS395_NOTE.md from the review's replacement:

    • Quotations. I checked every quotation character for character against the law-2688 files. IS reg 50's closing words appear in full. The other five appear only as their differing words, each in the regulation's own spelling ("incumbrance" or "encumbrance(s)", "10 per cent", "10 per cent.", "10 per cent.,", "10%"). The limb (b) phrases are "would realise if sold in the United Kingdom to a willing buyer" for the six and "would raise if sold in the United Kingdom to a willing buyer" for UC reg 49(2)(b).
    • Links. All six links stay, and UC reg 49 now has a link at reg 49(2) and again at reg 49(1).
    • Actors. PolicyEngine, the formulas, the Secretary of State, the decision maker, DWP and an encoder. No PR, survey, benefit, document or limb acts.
    • Judgment calls.
      • I narrowed the DMG sentence to "For Income Support and JSA, DWP tells its decision makers…". The review couldn't confirm that DMG 29614 covers all six regulations, and this repository cites chapter 29 only for IS and JSA, and for HB by analogy.
      • "The model has / the survey records" became "The formulas read no secured-debt input… and apply no disregard specific to either". That is true at the head, and the Housing Benefit passports still disregard all capital.
  • P3-2. changelog.d/2577.fixed.md: the second sentence is the review's text.

  • P3-3. Body, means-test paragraph: I used the review's replacement sentence, including the TOTCAPB4 measurement link (I confirmed comment 6449451323 says 37,916 of 61,214). Line 54 (Tests) now reads "the pension_credit_reported_capital documentation string". I kept the next sentence, which says the uprating index uprates stored observations.

  • P3-4. Body, Stored totals: the review's two sentences.

  • P3-5.

    • (a) test_isa_balances.py:62-65: the review's comment.
    • (b) test_property_wealth_components.py:515-522: the review's comment.
    • Body, Invariants, last bullet: "off share_like_capital, which holds row 9's corporate_wealth, and so change…". I changed "which changes" to "and so change" to avoid a second "which".
  • P3-6. Body line 7: the review's text.

  • P3-7. Last body paragraph: the review's two replacements, with Actions links to runs 41604662583 (1aacd327b), 41627481670 (0d760fccd) and 41588793376 (b4e872273). I checked each run's head SHA through the REST API.

  • P3-8. universal_credit/.../sale_expenses/rate.yaml:1-9: the review's description. It starts with the quantity and ends with a full stop, and test_parameter_descriptions.py gives 24 passed.

  • P3-9.

    • (a) docs/engineering/property-wealth-components.md:157-161: the review's text.
    • (b) :239-241: "Existing land target definitions and target weights stay the same…". I couldn't find out which weights the sentence means. This session's GitHub scope covers only policyengine-uk, so I didn't read policyengine-uk-data, and PyPI's mirror stops at uk-data 1.11.1. So I used the review's wording.
  • P3-10. I added the six regulations to the five sale-expense lists, after each file's first reference:

    • IS reg. 50, JSA reg. 112, ESA reg. 114 and SPC reg. 20.
    • In the HB file, HB reg. 48 after reg. 47(a) and HB (SPC) reg. 46 after HB (SPC) reg. 45(a). I paired each with its own regulation's reg. 47/45 entry instead of placing both after the first reference.

    I added no Northern Ireland reference. The body's Law section now names and links the six regulations.

Text only

  • Python and YAML. git diff 0d760fccd..HEAD: 11 files, 41 insertions, 24 deletions. A script compared each changed file at both commits:
    • Both test files have identical ast.dump.
    • pension_credit_reported_capital.py differs only in string constants.
    • All six YAML files load to the same data once description and reference are masked.
  • Tests. No test expectation changed.

Tests at 80e0d344f

Clean uv sync --frozen --extra dev, with HUGGING_FACE_TOKEN and POLICYENGINE_UK_DEFAULT_DATASET unset.

  • Python tests, with -n 4:
    • test_parameter_descriptions.py: 24 passed
    • test_isa_balances.py: 2 passed
    • test_property_wealth_components.py: 61 passed
    • policyengine_uk/tests/code_health: 3,115 passed
  • Changed parameter files. CountryTaxBenefitSystem() loads every one. The UC rate gives 0.1 on 2026-01-01, and each sale-expense list's references load with the new entries in second place.
  • Ruff and diff check.
    • uvx ruff@0.17.2 format --check .: 2,184 files already formatted.
    • ruff check . and --select F811,F821 policyengine_uk: all checks passed.
    • git diff --check 0d760fccd..HEAD: clean.
  • Fix evidence. I ran .github/check_fix_pr.py the way pr_fix_evidence.yaml runs it, with the posted title and body, empty labels, the diff from merge base 52b30809f and --head-sha 80e0d344f. It gives PASS, 31 changed executable tests. The checker here equals main's.
  • Merges (git merge-tree --write-tree --name-only HEAD <tree>):

Skipped

  • No revert or mutation checks. The change touches no executable line.
  • No survey-dataset tests and no full suite. They need the certified data, so CI is the evidence for them.
  • No uk-data reads. policyengine-uk-data on GitHub falls outside this session's scope (P3-9(b)).
  • No PR update. I moved no PR branch and opened no PR.

Judgment calls

  • Pickup. The last push to property-wealth-overseas-other was 0d760fccd itself (11:21Z), within the six-hour window. It is the brief's stated base, and no commit or comment followed it except the review. So I treated it as the hub's own push and went ahead after posting the pickup comment.
  • Tests section. I added a paragraph for 80e0d344f, with its runs and the P2-1 witness, and kept the earlier runs as they were.

Generated by Claude Code

@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

Ready for hub review

Verdict: APPROVE WITH NITS at 80e0d344fd578042da5be96b48dcf11c2f453aed (property-wealth-overseas-other)

0d760fccd..80e0d344f changes only text, and it closes P2-1 and every P3 of review 995 (comment 6605058417). It changes no formula and no test expectation. All 22 check runs at the head passed on 3 Nov. Every number I could rerun matches. #1969 is still an ancestor of the head, and the stacking sentence still holds.

The findings below need no change to this commit's code:

  • P2-1. After the merge with main, council tax reduction's household route won't count either new input. main has moved since 1d4d020aa. main's Treat income derived from capital as capital in the legacy means tests #1995 adds a council tax reduction capital list without either input. The merge doesn't conflict in that file, so resolving the eight conflicts won't bring the gap to light. The same main change makes the council tax reduction sentences this delta added to the body out of date.
  • P3-1. The Add parameter for marriage allowance-eligible tax bands  #395 note. The DMG sentence matches DMG 29614, but DMG 29650 to 29652 give decision makers a different rule for a share in a capital asset outside the United Kingdom. The note also says the Secretary of State "spells" what are really differences in wording.
  • P3-2. The body's witness paragraph leaves out that the 2027 situation row needs the property set for 2027.
  • P3-3. Small text nits. The body could cite the Aggregate impact run at this head, and the legacy sale_expenses/rate.yaml files still cite only the UK valuation rule.

I ran everything below on a detached worktree of the head, after uv sync --frozen --extra dev and with HUGGING_FACE_TOKEN and POLICYENGINE_UK_DEFAULT_DATASET unset. git status --porcelain stayed empty. "Body" means the live PR body (gh api repos/PolicyEngine/policyengine-uk/pulls/2688 --jq .body).

P1

None.

P2

P2-1. Once the branch merges with main at 9dd44e142, council_tax_reduction_assessable_capital's household route counts neither new input, and the body's council tax reduction sentences no longer describe the code

This isn't a defect at the head. It is a gap that the resolution of the eight conflicts won't surface, so I'm raising it now.

What main changed. Two merges since 1d4d020aa matter:

  • main's Treat income derived from capital as capital in the legacy means tests #1995 (8717d5a2) adds gov.local_authorities.council_tax_reduction.capital.sources and capital/sale_expenses/sources.yaml.
  • council_tax_reduction_assessable_capital at 9dd44e142 now values a household route itself. It loops over that list and takes 10% off the property in the sale-expense list (council_tax_reduction_assessable_capital.py:117-138 at 9dd44e142). The route applies to every applicant outside the guarantee credit, savings-credit-only, Universal Credit and passported branches.
  • main's Count the UC award before debt deductions in four councils' CTR #2822 (f2589c5d) makes _legacy.py test council_tax_reduction_assessable_capital (_legacy.py:98-102 at 9dd44e142). At the head, _legacy.py tests uc_assessable_capital (_legacy.py:78). council_tax_reduction_assessable_capital now takes uc_assessable_capital for an applicant with a Universal Credit award.

What the merge gives. git merge-tree --write-tree --name-only 80e0d344f 9dd44e142 gives tree 5b736d61. In that tree, parameters/gov/local_authorities/council_tax_reduction/capital/sources.yaml lists savings, owned_land, other_residential_property_value and non_residential_property_value, and its sale_expenses/sources.yaml lists the three property names. Neither file names overseas_property_value or other_property_value. The head has no such files (git ls-tree prints nothing), so git merges them in without conflict.

Why it matters.

  • After the merge, a working-age applicant without Universal Credit, or a pensioner without Pension Credit, would have their other residential property, non-residential property and land counted for council tax reduction, but not their overseas or other real estate.
  • That contradicts the body's "Every means test that counts household property reaches both new inputs" and the changelog's "every means-tested capital assessment that includes household property".
  • main's own list description says every scheme counts "land and property other than the home".
  • No test at the head would fail. test_stored_household_totals.py:81 names council_tax_reduction_assessable_capital, but no dataset in that file stores either input (review 995).

The body's council tax reduction sentences. This delta added them (review 995's P3-4) under Stored totals:

…and council_tax_reduction_assessable_capital through Pension Credit's capital on a savings-credit-only award. The Merton, Kingston upon Thames, Newham and Westminster council tax reduction formulas also test uc_assessable_capital for an applicant with a Universal Credit award…

Both hold at the head. After the merge, council_tax_reduction_assessable_capital itself takes UC capital, and the four schemes reach it through that variable.

Where the conflicts come from. The brief names #2806, but #2806 (b6235f4f) touches none of the eight conflicted files: its diff covers only UC education files. The conflicts come from two other merges:

What to change when #1969 and this branch take main. No change is needed at 80e0d344f itself.

  • Add overseas_property_value and other_property_value to gov/local_authorities/council_tax_reduction/capital/sources.yaml and to its sale_expenses/sources.yaml.
  • Add a YAML case that pins council_tax_reduction_assessable_capital at 0.9 × each input for an applicant on neither Pension Credit nor Universal Credit.
  • Replace the body's two sentences with:

Both new inputs reach Pension Credit and the four legacy programmes through each one's capital/sources.yaml, Universal Credit through its capital/property_sources.yaml, and council_tax_reduction_assessable_capital through its own capital/sources.yaml, through Pension Credit's capital on a savings-credit-only award, and through Universal Credit's capital for an applicant with a Universal Credit award.

Check this sentence against the merged code before using it. I couldn't run the merged tree because it has conflicts.

P3

P3-1. The #395 note: the DMG sentence omits DMG 29650 to 29652, and "spells" misdescribes the differences

I checked RS395_NOTE.md against the law-2688 files. Each quotation is right, character for character (grep -F over copies of the law files):

  • IS reg 50's closing words appear only in uksi-1987-1967-regulation-50.txt.
  • "would realise if sold in the United Kingdom to a willing buyer" appears in all six legacy and Pension Credit files.
  • "would raise if sold in the United Kingdom to a willing buyer" appears only in UC reg 49.
  • grep -o "10[^a-z]*\(per cent[.,]*\|%\) and the amount of any [a-z]*" gives the differing words the note lists:
    • JSA: "10 per cent. … incumbrance"
    • ESA: "10% … incumbrance"
    • HB 48: "10 per cent. … encumbrances"
    • HB (SPC) 46: "10 per cent. … encumbrance"
    • SPC 20: "10 per cent., … encumbrance"

The links match each file's Source line, and the DMG link matches the file's PDF and page 123. The note's actors are the Secretary of State, PolicyEngine, the formulas, the decision maker, DWP and an encoder. I found no regulation, survey, PR or document acting.

(a) The DMG sentence. "For Income Support and JSA, DWP tells its decision makers to apply both deductions to the value under either limb (DMG 29614)."

  • It matches DMG 29614. That paragraph gives "1. its current market or surrender value…" or "2. the price people get for it if sold to a willing buyer in the UK…", then "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". Both deductions follow both items.
  • The scope. The narrowing to Income Support and JSA fits the footnote printed on that page ("JSA Regs, reg 111; IS (Gen) Regs, reg 49"). That footnote belongs to 29612-29613, though: the extract doesn't hold 29614's own footnote, so the scope rests on the page and on the repository's DMG chapter 29 references.
  • What it leaves out. DMG 29650 to 29652 tell decision makers something different for "a deemed or actual share in a capital asset outside the UK":
    • Where money can be transferred, the value is "the market value of the deemed or actual share less … 10% if there would be expenses of sale" (29651). No incumbrance is mentioned.
    • Where the country won't allow the transfer, the value is "the price the owner of the deemed or actual share would get from a willing buyer in the UK" (29652). No deduction is mentioned.
  • Why it matters. An encoder reading "either limb" without 29650 to 29652 would apply both deductions to a jointly owned overseas home.

Replace the sentence with:

For Income Support and JSA, DWP tells its decision makers to apply both deductions to the value of capital outside the United Kingdom under either limb (DMG 29614). For a deemed or actual share in a capital asset outside the United Kingdom, DWP tells them to take only 10%, where there would be expenses of sale, off the share's market value where the country allows money to be transferred to the United Kingdom, and to use the price a willing buyer in the United Kingdom would pay for the share where it does not (DMG 29650 to 29652).

(b) "spells". "In the other five, the Secretary of State spells only "10 per cent" and "incumbrance" differently". The differences aren't all spelling:

  • "10%" is a different form.
  • SPC reg 20's "10 per cent.," adds a comma.
  • HB reg 48's "encumbrances" is a plural.
  • JSA reg 112 and ESA reg 114 keep "incumbrance".

Replace with:

In the other five, the Secretary of State words only "10 per cent" or "incumbrance" differently:

(c) The first paragraph's last sentence. "The formulas read no secured-debt input for either category and apply no disregard specific to either, so they deduct no encumbrance from them." The disregard clause doesn't support the conclusion about encumbrances. SECURED_DEBT (utils/capital_valuation.py:25-29) names neither input, which is the actual reason. Replace with:

The formulas read no secured-debt input for either category, so they deduct no encumbrance from them, and they apply no disregard specific to either.

P3-2. The body's situation witness leaves out the 2027 property

Body, Tests, second paragraph: "From a situation that sets 1,000 for 2026, it gives 1,000.00 in 2026 and 36,000.00 in 2027, when the column reads -1."

  • That paragraph's case sets "£40,000 of other residential property" without a year.
  • With the property set only for 2026, the model uprates it and 2027 gives 36,878.33, not 36,000.00 (pc_years.py below). The hand-off says the same, but the body doesn't.

Replace with:

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.

P3-3. Nits

  • The citation of the aggregate report. The body's last paragraph is right as written: runs 41604662583, 41627481670 and 41588793376 ran at the heads it names (see below). CI's bot has since rewritten comment 6425530679 again, at 15:31:19Z. It now holds run 41651533635 at 80e0d344f against main 1d4d020aa, with the same figures. The paragraph could add: "Its run at 80e0d344f (run 41651533635) gave the same figures."
  • The legacy sale_expenses/rate.yaml files. The 10% that reg 50, 112, 114, 48, 46 and 20 apply reaches overseas property through each programme's sale_expenses/rate.yaml. Those rate files still cite only reg 49(a), 111(a), 113(a), 47(a)/45(a) and 19(a). The numbers don't change. If you want the rate files cited like the lists, add the same six entries after each file's first reference.
  • Line length on the engineering page. At docs/engineering/property-wealth-components.md:241 the reflow leaves "losses and fitted geographic weights can change. Production rebuilding, recalibration" at 85 characters, while the lines around it wrap at 80.

Checked and sound

Text only

git diff --stat 0d760fccd 80e0d344f: 11 files, 41 insertions, 24 deletions. exec_check.py (below) compares each changed file at both commits:

changelog.d/2577.fixed.md not code
docs/engineering/property-wealth-components.md not code
…/ESA/income/capital/sale_expenses/sources.yaml identical with description/reference masked
…/JSA/income/capital/sale_expenses/sources.yaml identical with description/reference masked
…/housing_benefit/means_test/capital/sale_expenses/sources.yaml identical with description/reference masked
…/income_support/means_test/capital/sale_expenses/sources.yaml identical with description/reference masked
…/pension_credit/income/capital/sale_expenses/sources.yaml identical with description/reference masked
…/universal_credit/means_test/capital/sale_expenses/rate.yaml identical with description/reference masked
policyengine_uk/tests/test_isa_balances.py ast identical
policyengine_uk/tests/test_property_wealth_components.py ast identical
…/pension_credit/pension_credit_reported_capital.py identical with strings masked

So no formula, parameter value or test expectation changed.

Review 995's findings

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 passed
  • test_isa_balances.py: 2 passed
  • That pair together with test_property_wealth_components.py: 87 passed, so test_property_wealth_components.py gives 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 -z from the merge base 52b30809f to the head. I ran it with uv run --no-project --with pyyaml==6.0.2 and --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

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 meets github-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

@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

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
@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

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
@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

PR_BODY_2688.md

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 draft on [#1969](https://github.com/PolicyEngine/policyengine-uk/pull/1969): it merges #1969's head `3dbdb0027`, 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's `test_isa_balances.py` and `test_stored_household_totals.py`, so `git diff 3dbdb0027..HEAD` holds only this PR's own change. Companion [data #543](https://github.com/PolicyEngine/policyengine-uk-data/pull/543) follows [data #501](https://github.com/PolicyEngine/policyengine-uk-data/pull/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_value` and `other_property_value`. Both carry GDP-per-capita uprating, as a class attribute for situations and in `policyengine_uk/data/uprating_indices.yaml` under `gov.economic_assumptions.yoy_growth.obr.per_capita.gdp` for datasets.

**Wealth.** `property_wealth` adds `residential_property_value`, `non_residential_property_value`, `owned_land`, `overseas_property_value` and `other_property_value`. `total_wealth` adds `property_wealth` once, beside corporate, pension and savings wealth. A saved `property_wealth` column still overrides the formula; the data release must stop storing it.

**Household land.** `household_land_value` applies the regional land intensity to `residential_property_value + non_residential_property_value + other_property_value`, summed directly, then adds `owned_land` at 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 saved `property_wealth` total, 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 with `main_residence_value` of £200,000 and a saved `property_wealth` of £300,000, household land falls from £255,000 on `main` to £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](https://github.com/PolicyEngine/policyengine-uk/pull/2688#issuecomment-6449462413) at `798491cb7`). `git diff 798491cb7 8bfd46a9f` shows no change to `household_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:

| Programme | Capital sources | Sale expenses | Financial-only report |
| --- | --- | --- | --- |
| Universal Credit | `uc_overseas_property_capital` and `uc_other_property_capital` in `capital/property_sources.yaml` | each formula deducts 10% once (`less_sale_expenses`) | `benunit_reported_capital` replaces the financial pool only; the property pool stays counted |
| Pension Credit | `capital/sources.yaml` and `capital/property_sources.yaml` | `capital/sale_expenses/sources.yaml` | `pension_credit_assessable_capital` adds the report to separately valued land and property, including existing domestic categories |
| Housing Benefit | `means_test/capital/sources.yaml` | `sale_expenses/sources.yaml` | the savings-credit-only route inherits Pension Credit's total |
| Income Support, income-based JSA, income-related ESA | each `capital/sources.yaml` | each `sale_expenses/sources.yaml` | none replaces its source pool |
| Council tax reduction | its own `capital/sources.yaml` on 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 2022 | its own `capital/sale_expenses/sources.yaml` | the household route has none; the other two routes follow Pension Credit and Universal Credit |

A complete `uc_reported_capital` override still replaces ordinary UC capital before the regulation 77 company substitution. A nonnegative `pension_credit_reported_capital` still replaces the whole Pension Credit assessment; its formula returns -1, which means no complete override. If a dataset stores a financial-only survey total in `pension_credit_reported_capital` rather than in `benunit_reported_capital`, the `pension_credit_assessable_capital` formula 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 FRS `TOTCAPB4` in that column, and 37,916 of the file's 61,214 benefit units carry a nonnegative value ([measurement](https://github.com/PolicyEngine/policyengine-uk/pull/2688#issuecomment-6449451323)). 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 to `benunit_reported_capital` (`pension_credit_reported_capital` documentation; `docs/engineering/property-wealth-components.md`). The uprating index for `pension_credit_reported_capital` uprates 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_capital` reads each variable in its `property_sources` list directly. The `pension_credit_assessable_capital` formula passes Pension Credit's list to `valued_capital(..., sources=...)`, which skips any name missing from `capital.sources`. A test checks each name in that list against `capital.sources` on every date either list changes, and each name in UC's list against the model's variables.

**Council tax reduction.** `main`'s #1995 gives `council_tax_reduction_assessable_capital` a household route for an applicant outside the guarantee credit, savings-credit-only, Universal Credit and passported routes. That route sums the sources in `gov.local_authorities.council_tax_reduction.capital.sources`, takes 10% off those in its `sale_expenses.sources`, and deducts the debt secured on other residential property, non-residential property and, through #1969, land. This branch adds `overseas_property_value` and `other_property_value` to both lists. Under each scheme, 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. The data hold no secured debt for either input, so the route's `SECURED_DEBT` stays as it was, and the route counts each input at 90% of its value. `council_tax_reduction_capital_valuation.yaml` pins 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_capital` equals `uc_assessable_capital`, £13,500, where the household route would add the land at £900, which Universal Credit leaves out.

**Main.** This branch gets `main` at `95dfc62f8` through #1969, whose head `3dbdb0027` merges `main` at `af9361114` and then at `95dfc62f8`. Between `52b30809f` and `af9361114`, `main` merged #2839, which counts each person's cash and stocks and shares ISAs in their own benefit unit, and #1995, which gives council tax reduction the household route above; #1969's description says how its formulas combine its valuation with #2839's pooling. In `uprating_indices.yaml`, this branch keeps #1969's file, adds `other_property_value` and `overseas_property_value`, and rewrites the comment on `pension_credit_reported_capital`. Git merged #1969's head into this branch with conflicts only in the documentation strings of `pension_credit_assessable_capital` and `uc_assessable_capital`; each keeps #1969's text and this branch's sentences on financial-only reports. `git merge-tree --write-tree` merges this head with `main` at `95dfc62f8` without conflict.

**Stored totals.** `test_stored_household_totals.py::test_stored_household_totals_grow_past_the_projection_horizon`, which `main`'s #2781 added, failed in CI on `d0648474a`, GitHub's merge of `f40cb894c` into `main` at `0080274d`: 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's `c11d7c1d9` makes 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's `capital/sources.yaml`, Universal Credit through its `capital/property_sources.yaml`, and `council_tax_reduction_assessable_capital` through its own `capital/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

- [UC Regulations 2013 reg. 49](https://www.legislation.gov.uk/uksi/2013/376/regulation/49): the Secretary of State values capital at market value less 10% where a sale would incur expenses, and less secured debt; [Schedule 10](https://www.legislation.gov.uk/uksi/2013/376/schedule/10) lists the disregards. [Reg. 6(1)](https://www.legislation.gov.uk/uksi/2013/376/regulation/6) sets penny rounding.
- [State Pension Credit Regulations 2002 reg. 19](https://www.legislation.gov.uk/uksi/2002/1792/regulation/19) values Pension Credit capital the same way.
- We cite each legacy programme's own valuation regulation in its parameter files (JSA reg. 111, ESA reg. 113, IS reg. 49, HB reg. 47). We also cite, in each legacy and Pension Credit sale-expense list, the regulation under which the decision maker takes 10% off capital outside the United Kingdom where there would be expenses attributable to sale: [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). Each programme's `sale_expenses/rate.yaml` cites the same regulation after its UK valuation rule.
- Council tax reduction: each scheme's rule for capital outside the United Kingdom, which council tax reduction's `capital/sources.yaml` and `sale_expenses/sources.yaml` both cite: [SI 2012/2885 Sch 1 para 33](https://www.legislation.gov.uk/uksi/2012/2885/schedule/1/paragraph/33) (pensioners in England), [SI 2012/2886 Sch para 66](https://www.legislation.gov.uk/uksi/2012/2886/schedule/paragraph/66) (England's default scheme), [WSI 2013/3029 Sch 1 para 27](https://www.legislation.gov.uk/wsi/2013/3029/schedule/1/paragraph/27) and [Sch 6 para 29](https://www.legislation.gov.uk/wsi/2013/3029/schedule/6/paragraph/29) (Wales), [SSI 2012/319 reg 43](https://www.legislation.gov.uk/ssi/2012/319/regulation/43) (Scotland, pension age), [SSI 2012/303 reg 47](https://www.legislation.gov.uk/ssi/2012/303/regulation/47/2022-03-31) (Scotland, working age to 31 March 2022) and [SSI 2021/249 reg 70](https://www.legislation.gov.uk/ssi/2021/249/regulation/70) (Scotland, working age from 1 April 2022). In the first six, the closing words that take off 10% and any encumbrance follow both limbs; SSI 2021/249 applies reg 70(1)'s deductions to the value reg 70(2) gives.

## Invariants

- `property_wealth` equals the sum of its saved components for every nonnegative input.
- Each pound of each component, and of corporate, pension and savings wealth, counts once in `total_wealth`.
- When every property input is nonnegative, `household_land_value` never falls below `owned_land`, and an overseas-only household has zero UK land. A negative input breaks the bound: `other_property_value` of -1 with `owned_land` of £20,000.50 in London gives £19,999.65.
- A nonnegative financial-only report never reduces counted property for UC or Pension Credit.
- On council tax reduction's household route, in England, Wales and Scotland and at working age or pension age, each pound of overseas or other real estate adds 90p to `council_tax_reduction_assessable_capital`, within float32 rounding of the total, and never reduces it.
- Every name in Pension Credit's `property_sources` list appears in its `capital.sources` on each date either list changes, and every name in UC's list names a model variable.
- On the pinned company-capital cases, each benefit unit's UC capital equals the float32 conversion of the penny-rounded oracle.
- Leaving both new inputs at zero, or omitting them, reproduces `main`'s wealth, land and capital outputs on a hand-built grid pinned from `main` at `2da212ab`. 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 #1969's formulas take sale expenses off other residential and non-residential property and off `share_like_capital`, which holds row 9's `corporate_wealth`, and so change their other capital outputs independently of the new inputs. Row 8's UC capital comes to £207,000 on `main` at `2da212ab` and `6c6e229a`, at `f40cb894c` and at `1aacd327`.

## Tests

`8bfd46a9f` merges #1969's head `3dbdb0027` into `80e0d344f` (`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 at `8bfd46a9f7fcfa1aa5fc23aab8ca78d74fd4fa13`, on a clean checkout after `uv sync --frozen --extra dev`, and loaded no survey data:
- `uv run --frozen policyengine-core test <directory> -c policyengine_uk`: `gov/dwp` **1,019 passed**, `finance/benefit/family` **1,810 passed**, council tax reduction **359 passed**.
- `uv run --frozen pytest <path> -n 4`: `test_capital_valuation_properties.py` **18 passed**, `test_stock_capital_periods.py` **2 passed**, `test_uc_property_capital.py` **65 passed**, `test_uc_property_capital_mock.py` **26 passed**, `test_person_isa_capital.py` **32 passed**, `test_savings_credit_only_receipt_properties.py` **6 passed**, `test_stored_household_totals.py` **42 passed**, `test_property_wealth_components.py` **63 passed** and `test_isa_balances.py` **2 passed**, and `policyengine_uk/tests/code_health` **3,186 passed**.
- `uvx ruff@0.17.2 format --check .`: 2,236 files already formatted. `uvx ruff@0.17.2 check .` and `uvx ruff@0.17.2 check --select F811,F821 policyengine_uk`: all checks passed. `git diff --check 80e0d344f..8bfd46a9f`: clean.
- `.github/check_fix_pr.py` on this title and body, with the diff from the merge base `95dfc62f8`: **PASS**, 34 changed executable tests.
- `git merge-tree --write-tree --name-only` merges `8bfd46a9f` with `main` at `95dfc62f8` and with #1969's head `3dbdb0027` without conflict.

Mutation checks on the council tax reduction lists, each in a scratch copy of `8bfd46a9f`:
- Without `overseas_property_value` in the sale-expense list, the three overseas cases in `council_tax_reduction_capital_valuation.yaml` fail 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.
- Without `other_property_value` in the sale-expense list, the three other real estate cases fail at £50,000 against £45,000; without it in the capital list, they fail at £0.
- With both lists as at `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 hasn't run at this head.

`80e0d344f` changes only text on top of `0d760fccd`: the `pension_credit_reported_capital` documentation 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 at `80e0d344f`, on a clean checkout after `uv sync --frozen --extra dev`, and loaded no survey data:
- `uv run --frozen pytest <path> -n 4`: `test_parameter_descriptions.py` **24 passed**, `test_isa_balances.py` **2 passed**, `test_property_wealth_components.py` **61 passed**, `policyengine_uk/tests/code_health` **3,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 .` and `uvx ruff@0.17.2 check --select F811,F821 policyengine_uk`: all checks passed. `git diff --check 0d760fccd..80e0d344f`: clean.
- `.github/check_fix_pr.py` on this title and body, with the diff from the merge base `52b30809f`: **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 gives `pension_credit_assessable_capital` of 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 at `8bfd46a9f`.

`0d760fccd` changes only text: the changelog fragment, test comments, the `pension_credit_reported_capital` documentation string, one parameter description and the engineering page. We ran each command below on its own, on a clean checkout of `1aacd327b615b20ecfe43562cf890c032e1d4dd5` after `uv 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 passed**
- `test_isa_balances.py`: **2 passed**
- `test_uc_company_owner_properties.py`: **7 passed**
- `test_pension_credit_reported_capital_properties.py`: **4 passed**
- `policy/baseline/finance/benefit/family/universal_credit/test_uc_capital_allocation.py`: **1 passed**
- `test_benunit_reported_capital_properties.py`: **7 passed**
- `test_capital_valuation_properties.py`: **18 passed**
- `test_projection_horizon.py`: **14 passed**
- `test_savings_credit_only_receipt_properties.py`: **6 passed**
- `test_stock_capital_periods.py`: **2 passed**
- `test_uc_company_override_properties.py`: **11 passed**
- `test_uc_property_capital.py`: **65 passed**
- `test_uc_property_capital_mock.py`: **26 passed**
- `test_uprating_inputs_reach_data.py`: **18 passed**
- `test_savings_income_uprating.py`: **134 passed**
- `test_capital_share_components.py`: **14 passed** (2,238 s)

Of the test files `main` changed between `2da212ab` and `52b30809f`, only `test_stored_household_totals.py` reads `property_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 passed**
- `housing_benefit_capital.yaml`: **21 passed**
- `income_support_capital.yaml`: **18 passed**
- `jsa_income_capital.yaml`: **17 passed**
- `uc_assessable_capital.yaml`: **23 passed**
- `pension_credit_reported_capital.yaml`: **31 passed**
- `benunit_reported_capital.yaml`: **6 passed**
- `household_land_value.yaml`: **15 passed**
- `property_wealth_components.yaml`: **13 passed**
- `capital_valuation.yaml`: **26 passed**
- `pension_credit_income.yaml`: **10 passed**
- `council_tax_reduction_pension_credit.yaml`: **23 passed**

YAML directories, with the same command:
- `household/wealth`: **45 passed**
- `gov/dwp`: **856 passed**
- Universal Credit: **580 passed**
- ESA income: **74 passed**
- Housing Benefit: **593 passed**
- Income Support: **140 passed**
- JSA income: **40 passed**
- council tax reduction: **331 passed**

With `main`'s version of `test_stored_household_totals.py` at `1aacd327b`, `test_stored_household_totals_grow_past_the_projection_horizon` fails with £5,800.00 against £6,000.00 ± 0.06, as it failed in CI on `d0648474a`, 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 .` and `uvx ruff@0.17.2 check --select F811,F821 policyengine_uk`: all checks passed.
- `git diff --check f40cb894c..1aacd327b`: clean.
- `.github/check_fix_pr.py` on the title and body, with the diff from the merge base with `main`: **PASS**, 31 changed executable tests.
- `git merge-tree --write-tree --name-only 1aacd327b origin/main` at `52b30809f`: no conflict.
- `git diff --stat b4e872273..1aacd327b`: 45 files changed, 2,631 insertions, 85 deletions.

Runs at `f40cb894c` on clean checkouts.

We ran each Python file on its own with `uv run --frozen pytest <file> -n 4`:
- `test_property_wealth_components.py`: **61 passed**
- `test_isa_balances.py`: **2 passed**
- `test_uc_company_owner_properties.py`: **7 passed**
- `test_pension_credit_reported_capital_properties.py`: **4 passed**
- `policy/baseline/finance/benefit/family/universal_credit/test_uc_capital_allocation.py`: **1 passed**
- `test_benunit_reported_capital_properties.py`: **7 passed**
- `test_capital_valuation_properties.py`: **8 passed**
- `test_projection_horizon.py`: **14 passed**
- `test_savings_credit_only_receipt_properties.py`: **6 passed**
- `test_stock_capital_periods.py`: **2 passed**
- `test_uc_company_override_properties.py`: **11 passed**
- `test_uc_property_capital.py`: **65 passed**
- `test_uc_property_capital_mock.py`: **26 passed**
- `test_uprating_inputs_reach_data.py`: **18 passed**
- `test_savings_income_uprating.py`: **134 passed**
- `test_capital_share_components.py`: **14 passed**

YAML directories at `f40cb894c`, with `uv run --frozen policyengine-core test <directory> -c policyengine_uk`:
- `household/wealth`: **45 passed**
- `gov/dwp`: **851 passed**
- Universal Credit: **580 passed**
- ESA income: **74 passed**
- Housing Benefit: **593 passed**
- Income Support: **140 passed**
- JSA income: **40 passed**
- council tax reduction: **331 passed**

Mutation checks at `f40cb894c`:
- `test_new_inputs_at_zero_reproduce_main` fails when `overseas_property_value` defaults to £1,000 instead of zero: **9 failed, 9 passed**, one failure for each omitted-input row.
- It also fails when a mutant removes `uc_non_residential_property_capital` from UC's `property_sources.yaml`: **2 failed, 16 passed**. Row 8's UC capital falls to £135,000.
- `test_household_capital_is_allocated_and_conserved_across_benefit_units` fails when `uc_assessable_capital` skips its final penny rounding, and when one benefit unit loses a penny.
- `test_property_source_lists_name_configured_sources` fails 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**.
- Ruff format and lint pass.
- The F811/F821 check, `git diff --check` and the fix-evidence check pass.
- CI on the PR merged with `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 `1aacd327b`: 11,796 of 11,859 collected Python tests passed (61 skipped, 2 xfailed) with the dataset present, 5,588 YAML tests passed, and the documentation build passed. CI's Aggregate impact job ([run 41604662583](https://github.com/PolicyEngine/policyengine-uk/actions/runs/41604662583)) compared `1aacd327b` merged into `main` with `main` at `52b30809ff` on the certified `enhanced_frs_2024_25.h5` (policyengine-uk-data 1.58.1). Its run at `0d760fccd` against `main` at `1d4d020aa` ([run 41627481670](https://github.com/PolicyEngine/policyengine-uk/actions/runs/41627481670)) gave the same figures. 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 41588793376](https://github.com/PolicyEngine/policyengine-uk/actions/runs/41588793376)), 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](https://claude.com/claude-code)

Generated by Claude Code

@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

Ready for hub review

Head: 8bfd46a9f7fcfa1aa5fc23aab8ca78d74fd4fa13 on claude/merge-main-council-tax-m72rh7-2688, five commits on #2688's 80e0d344f. #1969's new head 3dbdb0027 (on claude/merge-main-council-tax-m72rh7-1969, hand-off on #1969) is an ancestor. I didn't push to property-wealth-overseas-other. The new PR_BODY_2688.md is in the comment above; the title stays "Add overseas and other real estate to property wealth and capital", so there's no PR_TITLE_2688.txt.

Commit What it does
d7caedad2772e1ca02a6e5656f9136a7185cf67e Merges #1969 at 3dbdb0027 (Task 2)
b6549961a39f0a158a66fe6b3c37968acb061275 Council tax reduction lists, citations and seven YAML cases (Task 3, 1030's P2-1)
2567d1cace28af49e704ad166f4ecb74922b3975 Outside-UK citations in the five legacy and Pension Credit sale_expenses/rate.yaml files; rewraps the engineering page (1030's P3-3)
f6dcf990cd529b69507fd92307268d572f2f8b89 Adds overseas_property_value and other_property_value to the growth factors page's per capita GDP list (docs only)
8bfd46a9f7fcfa1aa5fc23aab8ca78d74fd4fa13 Hypothesis property test for council tax reduction's household route

Findings and how I resolved them

  • Task 2, the merge. Git stopped on two files, pension_credit_assessable_capital.py and uc_assessable_capital.py, and only in their documentation strings; git merged both formulas' code, valued_capital's sources= argument (capital_valuation.py:36-54) and the six capital lists. I kept Value means-tested capital less 10% sale expenses and secured debt #1969's text and put back this branch's own sentences: in Pension Credit, "An explicit nonnegative pension_credit_reported_capital replaces the whole assessment. 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." (pension_credit_assessable_capital.py:30-34); in UC, "it does not replace separately valued domestic, overseas or unclassified other property" (uc_assessable_capital.py:26-27). Every dated list entry from both sides survives in all six programmes' sources, person_sources, property_sources and sale_expenses/sources (script comparison). git diff 3dbdb0027..8bfd46a9f touches the 48 files git diff b4e872273..80e0d344f touched plus the nine my commits touch, and nothing else; uprating_indices.yaml still differs from Value means-tested capital less 10% sale expenses and secured debt #1969's only by the two inputs and the pension_credit_reported_capital comment.
  • 1030's P2-1 (Task 3). council_tax_reduction/capital/sources.yaml and sale_expenses/sources.yaml now list both inputs (2013-04-01 entry). Both cite, from ctr-capital-valuation.txt, 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 (the source file's 31 March 2022 URL) and SSI 2021/249 reg 70(2). The capital list puts each after its scheme's existing entry; the sale-expense list pairs each with its UK rule where that list cites one. The sale-expense description says councils take 10%, where there would be expenses attributable to sale, and any encumbrance off the value under either limb, because each rule's closing words follow both limbs and SSI 2021/249 applies reg 70(1) to the reg 70(2) value. I left SECURED_DEBT alone. CountryTaxBenefitSystem() loads both lists, which give both inputs on 2013-04-01, 2022-04-01 and 2026-01-01.
  • The new cases (council_tax_reduction_capital_valuation.yaml). Six two-household cases put a working-age applicant without UC (would_claim_uc: false) and a pensioner without Pension Credit (would_claim_pc: false) in England (Maidstone), Wales (Cardiff) and Scotland (Edinburgh), and pin council_tax_reduction_pensioner at [false, true] and council_tax_reduction_assessable_capital at [90,000, 90,000] for £100,000 of overseas property and [45,000, 45,000] for £50,000 of other real estate. The seventh gives a working-age applicant in England with would_claim_uc: true £10,000 of overseas property, £5,000 of other real estate and £1,000 of land: uc_assessable_capital 13,500, council_tax_reduction_universal_credit_award true and council_tax_reduction_assessable_capital 13,500, where the household route would give 14,400 with the land.
  • The property test (test_property_wealth_components.py::test_council_tax_reduction_household_route_counts_each_input_less_sale_expenses, both inputs). For any float32 value and savings, England, Wales or Scotland and age 40 or 70, the household route adds 0.9 × the input, within float32 rounding of the total, and never less than without it. The body's Invariants list states it.
  • P2-1's draft sentence, checked against the merged code, needed two qualifications: council_tax_reduction_assessable_capital takes UC's capital only for a working-age applicant (working_age & council_tax_reduction_universal_credit_award & uses_universal_credit_assessment), and Scotland's uses_universal_credit_capital_assessment turns false on 2022-04-01. The four schemes no longer read uc_assessable_capital: Merton, Kingston upon Thames, Newham and Westminster reach council_tax_reduction_assessable_capital through legacy_council_tax_reduction (_legacy.py:98-102), Oxford reads it directly (oxford_council_tax_reduction.py:49), and so does the national formula (simulated_council_tax_reduction_benunit.py:87). The body's Stored totals paragraph carries the corrected sentence and says which formulas test the variable, the Means tests table has a council tax reduction row, and a new Council tax reduction paragraph describes the route and the cases.
  • P3-2. The body carries the review's sentence. I reran the witness at the head (pc_years.py): 1,000.00, 1,040.00, 1,268.69, 45,627.87 (−1 in the column) and 46,890.17 from the stored dataset; 37,792.48 without the column; 1,000.00 then 36,878.33 from the situation with the property for 2026 only; 1,000.00 then 36,000.00 with the property for 2026 and 2027. All match, and the body says so.
  • P3-3. IS reg 50, JSA reg 112, ESA reg 114, HB reg 48, HB (SPC) reg 46 and SPC reg 20 now follow each file's UK rule in its sale_expenses/rate.yaml (HB: reg 48 after reg 47(a), reg 46 after reg 45(a)), with the title and href each programme's sale_expenses/sources.yaml uses (a script asserts the match). property-wealth-components.md:241-242 now wraps at 71 and 63 columns. I skipped the aggregate sentence, as the brief says.
  • P3-1: nothing to do.
  • Main. The body's Main paragraph now describes 95dfc62f8 through Value means-tested capital less 10% sale expenses and secured debt #1969's 3dbdb0027, and the merge line under the tests names this head, main at 95dfc62f8 and Value means-tested capital less 10% sale expenses and secured debt #1969's 3dbdb0027.

Tests at 8bfd46a9f

Each command on its own, clean checkout, uv sync --frozen --extra dev, no survey data:

  • policyengine-core test: gov/dwp 1,019 passed, finance/benefit/family 1,810 passed, council tax reduction 359 passed.
  • pytest -n 4: test_capital_valuation_properties.py 18 passed, test_stock_capital_periods.py 2 passed, test_uc_property_capital.py 65 passed, test_uc_property_capital_mock.py 26 passed, test_person_isa_capital.py 32 passed, test_savings_credit_only_receipt_properties.py 6 passed, test_stored_household_totals.py 42 passed, test_property_wealth_components.py 63 passed and test_isa_balances.py 2 passed; code_health 3,186 passed.
  • .github/check_fix_pr.py as pr_fix_evidence.yaml runs it (title and body without a trailing newline, empty labels, git diff --name-status --no-renames -z 95dfc62f8 8bfd46a9f): PASS, 34 changed executable tests.
  • uvx ruff@0.17.2 format --check .: 2,236 files already formatted; ruff check . and --select F811,F821 policyengine_uk: all checks passed; git diff --check 80e0d344f..8bfd46a9f: clean.
  • git merge-tree --write-tree --name-only: 8bfd46a9f with origin/main (95dfc62f8) and with 3dbdb0027 merge without conflict, and 3dbdb0027 is an ancestor of the head.
  • main moved to 47a2817e2 while I worked: it merged Count Maternity Allowance in the benefit cap #2811 (benefit cap and Maternity Allowance), which changes no file either branch changes. git merge-tree --write-tree --name-only merges both heads with 47a2817e2 without conflict. I didn't merge it, since the job names 95dfc62f8.

Mutation and revert checks

In a scratch copy of the head, one list entry removed each time, then restored:

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.
  • f6dcf990c is outside the brief's list: the growth factors page lists every per capita GDP variable in uprating_indices.yaml except the two new inputs (script comparison), so I added them. Drop it if you'd rather not.
  • Council tax reduction's sale_expenses/rate.yaml still 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

@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

Ready for hub review

#1969 — Verdict: APPROVE WITH NITS at 3dbdb00276578089c68949b31658239386af9420 (uc-capital-sale-expenses)

#2688 — Verdict: APPROVE WITH NITS at 8bfd46a9f7fcfa1aa5fc23aab8ca78d74fd4fa13 (property-wealth-overseas-other)

Scope: b4e872273..3dbdb0027 (with git show --remerge-diff of 154f92286 and 3c395c3e4) and 80e0d344f..8bfd46a9f (with git show --remerge-diff d7caedad2). I made no edits to either branch and pushed nothing. I checked each head out detached in a scratch worktree, ran uv sync --frozen --extra dev, and unset HUGGING_FACE_TOKEN and POLICYENGINE_UK_DEFAULT_DATASET. Every mutation ran on a separate scratch worktree, and git status --short was empty after each restore.

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 sale_expenses/rate.yaml, a qualification on the English working-age schemes, one imprecise clause in #1969's body, and writing-rule fixes where a regulation, rule or PR acts.

Findings

P1

None.

P2

None.

P3-1. Council tax reduction's sale_expenses/rate.yaml cites only the UK rules (#2688)

policyengine_uk/parameters/gov/local_authorities/council_tax_reduction/capital/sale_expenses/rate.yaml:15-30 at 8bfd46a9f. The hand-off asks whether leaving it is right. It isn't quite: after b6549961a this rate also reaches overseas_property_value, and the 10% for that capital comes from each scheme's rule for capital outside the United Kingdom. 2567d1cac gave the five legacy and Pension Credit rate files the same treatment on the same reasoning (review 1030's P3-3). The file already cites SSI 2021/249 reg 70 as a whole, which holds reg 70(2), so six entries are missing, not seven. Add, each after its scheme's UK rule, the titles and hrefs capital/sale_expenses/sources.yaml already uses:

    - title: The Council Tax Reduction Schemes (Prescribed Requirements) (England) Regulations 2012 Schedule 1 para. 33 (capital outside the United Kingdom)
      href: https://www.legislation.gov.uk/uksi/2012/2885/schedule/1/paragraph/33
    # after Sch para. 65:
    - title: The Council Tax Reduction Schemes (Default Scheme) (England) Regulations 2012, Schedule para. 66 (capital outside the United Kingdom)
      href: https://www.legislation.gov.uk/uksi/2012/2886/schedule/paragraph/66
    # after Sch 1 para. 26:
    - title: The Council Tax Reduction Schemes and Prescribed Requirements (Wales) Regulations 2013 Schedule 1 para. 27 (capital outside the United Kingdom)
      href: https://www.legislation.gov.uk/wsi/2013/3029/schedule/1/paragraph/27
    # after Sch 6 para. 28:
    - title: The Council Tax Reduction Schemes and Prescribed Requirements (Wales) Regulations 2013 Schedule 6 para. 29 (capital outside the United Kingdom)
      href: https://www.legislation.gov.uk/wsi/2013/3029/schedule/6/paragraph/29
    # after reg. 42:
    - title: The Council Tax Reduction (State Pension Credit) (Scotland) Regulations 2012 reg. 43 (capital outside the United Kingdom)
      href: https://www.legislation.gov.uk/ssi/2012/319/regulation/43
    # after reg. 46:
    - title: The Council Tax Reduction (Scotland) Regulations 2012 reg. 47 (capital outside the United Kingdom; persons under pension age, to 31 March 2022)
      href: https://www.legislation.gov.uk/ssi/2012/303/regulation/47/2022-03-31

The numbers don't change.

P3-2. #2688's body: "Under each scheme" overstates what binds an English council's working-age scheme

Body, Council tax reduction, fourth sentence: "Under each scheme, the council values capital outside the United Kingdom …". The brief asks whether a council in every scheme year must count those assets and take the 10%. From ctr-capital-valuation.txt:

  • England, pensioners. SI 2012/2885 Sch 1 is headed "Pensioners: matters that must be included in an authority's scheme", so every English council must apply para 33 to a pensioner.
  • England, working age. Para 66 sits in "Council Tax Reduction Scheme (Default Scheme) 2013" (SI 2012/2886). It binds a council only where the default scheme applies. A council that made its own working-age scheme sets its own capital rules, and the file quotes nothing that requires it to follow para 66.
  • Scotland. SSI 2012/319 reg 43 (pension age), SSI 2012/303 reg 47 (working age to 31 March 2022) and SSI 2021/249 reg 70 (working age from 1 April 2022) are the schemes themselves, so they cover every year from 2013-04-01.
  • Wales. WSI 2013/3029 is a prescribed-requirements instrument, and both Sch 1 para 27 and Sch 6 para 29 carry the rule. The file doesn't show which Welsh instrument governed 2013-14 or whether Sch 6 binds every authority's scheme (see "What I couldn't check").

So every scheme the formula follows takes the 10%. That holds for the default scheme that the national formula models, but not for every English council's own working-age scheme. The model's household route already makes that assumption for main's UK land and property, so the code needn't change. Replace the sentence with:

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 closing-words reading holds. In all six of SI 2012/2885 para 33, SI 2012/2886 para 66, WSI 2013/3029 Sch 1 para 27 and Sch 6 para 29, SSI 2012/319 reg 43 and SSI 2012/303 reg 47, the file marks "less, where there would be expenses attributable to sale, 10 per cent and the amount of any encumbrance(s) secured on it" as closing words at the paragraph's level, after limb (b), so they govern both limbs. "Where there would be expenses" governs the 10%, and the encumbrance comes off unconditionally. The body and the YAML keep that order. SSI 2021/249 reg 70(2) defines "the market value" of an asset outside the United Kingdom, and reg 70(1) takes 10% and encumbrances off "current market value", so both deductions reach either reg 70(2) value.

P3-3. #1969's body: valued_capital does floor a negative encumbered source

Body, What changes, the council tax reduction bullet: "…where valued_capital uses a negative source or debt as given…". valued_capital (policyengine_uk/utils/capital_valuation.py:46-55 at 3dbdb0027) applies max_(0, value - debt) to every source in SECURED_DEBT, even where the debt is nil. A negative owned_land, other_residential_property_value or non_residential_property_value therefore counts as nil, as on the council tax reduction route. Only a negative savings, cash ISA or share source, or a negative debt, passes through as given. The hand-off's table tests only negative savings and a negative debt, so it doesn't show this. Replace the clause with:

…where valued_capital uses a negative savings, cash ISA or share-like source, or a negative debt, as given…

P3-4. #2688's body: the mutation list can now name the other half of the property test

Body, Tests, mutation bullets, second bullet. The hand-off left the property test unrun for the two other_property_value mutations. I ran them (table under "Checked and sound"), and both kill the other real estate half. Replace the bullet with:

  • Without other_property_value in 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.

P3-5. Writing rules: regulations, rules, PRs and a programme act

These sentences are new in the delta. In each, a regulation, rule, PR or programme acts; replace each one as shown.

#2688

#1969

Checked and sound

1. #1969's merge with main (154f92286)

The six formulas. git show --remerge-diff 154f92286 has conflict hunks in the six assessable-capital formulas, pension_credit_income.yaml and test_savings_credit_only_receipt_properties.py. It also has hand edits in capital_valuation.py, council tax reduction's assessable capital and rate.yaml, the changelog, person_isa_capital.yaml, test_person_isa_capital.py and the two YAML files that get new cases. I diffed each formula's formula body at the head against main af9361114 and against b4e872273:

  • Against b4e872273, the only code change in each is the value_of lambda. It now returns household(source) - pooled_person_capital(benunit, source, period, <capital>), and UC passes ~covered, which comes with its covered mask from main. IS drops main's capital = alias and spells out IS.means_test.capital, with the same meaning.
  • Against af9361114, every other change is Value means-tested capital less 10% sale expenses and secured debt #1969's own: valued_capital in place of sum(...), valued_person_capital in place of the per-source benunit.sum, exact_amount on the reported and override inputs, and the final round_to_penny.

Each pound counts once. pooled_person_capital subtracts every member's counted ISA balance from cash_isa or share_like_capital before valuation. valued_person_capital (capital_valuation.py:58-112) then adds the claimant's and partner's balances, each valued through HOUSEHOLD_SOURCE_OF as its household source. On share_like_capital that gives 0.9 × (S − P) + 0.9 × P = 0.9 × S, before penny rounding. A dependant's balance leaves the pool and counts nowhere, which is main's #2839 rule. test_person_isa_capital.py's pound-counts-once tests pass at the head (32 passed).

The hand-off's "stray "The "" is in HB (:24) and PC (:23). It joins with the next fragment to read "The formula allocates…", so the text is right, if oddly split.

Lists. I ran a script over yaml.safe_load of every capital/sources.yaml, person_sources.yaml, property_sources.yaml and sale_expenses/sources.yaml under parameters/gov. It checked that every dated entry of either parent appears in the merge with nothing added. All 21 files pass at 154f92286 (parents b4e872273 and af9361114), and again at 3dbdb0027. That covers the six programmes and council tax reduction's two lists.

Hand reworking.

  • pension_credit_income.yaml Case 4. 455 + 0.9 × 55,370 = 50,288. (50,288 − 10,000) / 500 = 80.58, so 81 a week and 4,212 a year. Guarantee credit (238 − 81) × 52 = 8,164, and savings credit nil. Main's figures were 55,825, 92 and (238 − 92) × 52 = 7,592, as the body says.
  • test_person_balances_without_household_totals_count_with_their_holders. Household 1: corporate wealth 5,000 less the 3,000 of stocks and shares ISAs leaves 2,000, 1,800 after the 10%. The pool is 7,000 + 1,800 = 8,800, split over three claimants. The couple get 5,866.67 + 3,000 + 1,800 = 10,666.67, and the single adult 2,933.33 + 5,000 + 900 = 8,833.33. Household 2: 9,000 − 6,000 leaves 3,000, 2,700 after the 10%, so the pool is 12,000 + 2,700 = 14,700. The parents get 9,800 + 4,000 = 13,800 and the son 4,900 + 5,400 = 10,300. Pension Credit, with two pension-age adults, gives 14,700 + 4,000 = 18,700. All of these match expected_uc and the Pension Credit assertion.
  • capital_valuation.yaml, "Every means test counts a cash ISA and savings at face value and a mortgaged second home…". 3,000 + 4,000 + (100,000 − 10,000 − 60,000) = 37,000 in all six.
  • person_isa_capital.yaml. Seven cases change an expected value (the hunks at old lines 70, 101, 180, 209, 268, 378 and 566), plus Cases 14 and 15, as the body says. In Case 3, 9,000 − 6,000 leaves 3,000, 2,700 after the 10%, split 1,800 and 900, and the child adds 5,400. 0.9 × 17,777.78 = 16,000.002 and 0.9 × 6,666.67 = 6,000.003, which round to the limits.
  • 76242a05b. With 154f92286's version of test_person_isa_capital.py on a scratch copy of the head, test_both_forms_in_one_dataset_add_up_to_the_stored_total fails in 2032: "Obtained 1268.699951171875, Expected 1268.690185546875 ± 0.00126869". So the penny tolerance is needed, and the body's "£634.345 each … a penny over" holds. The commit message describes oracle changes that 154f92286 made, as the hand-off says. That is history, not the body.

The main merge 3c395c3e4. git show --remerge-diff 3c395c3e4 is empty, so the merge had no conflict.

2. The two choices

(a) A stocks and shares ISA balance less 10%. UC reg 49(1)(a) takes 10% off any capital "where there would be expenses attributable to sale". The quoted law doesn't say whether cashing in a stocks and shares ISA is such a sale. The body cites ADM H1674 and H1656 for a different treatment, which I can't read. Before the merge, b4e872273 already took 10% off that ISA inside share_like_capital. The merge keeps the same valuation once the pound moves to its holder, so it adds no new legal reading, and it keeps a pound's value the same wherever it sits. The body discloses this twice: in "What changes" ("A pound of a stocks and shares ISA therefore counts at 90% whether it stays in the pool or moves to its holder") and in the new table row, which carries over share_like_capital's limitation and names #2131.

(b) owned_land_secured_debt in council tax reduction's SECURED_DEBT. The land case gives 50,000 − 5,000 − 20,000 = 25,000. On a scratch copy without the "owned_land" entry, the case fails: [45000.] differs from 25000.0. The YAML's header and the body cite para 32, whose limb (b) reads "the amount of any encumbrance secured on it", and its equivalents (paras 65, 26 and 28, regs 42 and 46, and reg 70(1)(b) in ctr-capital-valuation.txt). The changelog and the rate.yaml description name land among the secured-debt inputs, which matches the code.

3. test_savings_credit_only_receipt_properties.py

The sums hold:

  • Pension Credit: 12,000 + 0.9 × 4,400 + 0.9 × 500 = 16,410. Council tax reduction's route: 12,000 + 3,960 = 15,960.
  • Deemed income: 6,410 / 500 = 12.82, so 13 a week.
  • Savings credit: 0.6 × (238.00 − 208.07) − 0.4 × (253.00 − 238.00) = 11.958 a week, and × 52 = 621.82.

The test can still fail. I ran test_known_families_detect_main and test_stopping_pension_credit_payments_ends_the_pension_credit_capital_test on a scratch copy of 3dbdb0027:

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), where property_share is valued_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_report replaces financial_proxy + person_capital, and uc_property_capital is added outside it. The UC property_sources.yaml lists the four uc_*_property_capital variables.

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 reads council_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 at simulated_council_tax_reduction_benunit.py:87.
  • council_tax_reduction_tariff_income.py:71 reads 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, with other_property_value and overseas_property_value added.
  • main 47a2817e2: 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 95dfc62f8 and today's 47a2817e2. The merge base is 95dfc62f8 for 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:

#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, tree 97377406c.
  • … 8bfd46a9f 47a2817e2: exit 0, tree 7d2ec3eda.
  • … 8bfd46a9f 3dbdb0027: exit 0, tree 73ce18261, the head's own tree, because 3dbdb0027 is an ancestor (merge-base --is-ancestor succeeds).
  • main gained only Count Maternity Allowance in the benefit cap #2811 (4d6c027f) and a version commit after 95dfc62f8.

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 finished success at 06:08:53Z. One Fix evidence run (04:46:08Z to 04:46:09Z) is cancelled; a second started 6 s later and passed, so the second superseded it.
  • 8bfd46a9f. The same: every job passed, including Test / 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.txt doesn'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 entries 2013-04-01 and cite only WSI 2013/3029, as main'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) and pc_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

MaxGhenis and others added 3 commits November 5, 2026 02:32
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The merge brings in #1969's changelog fragment and changes nothing else.

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>
@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

Your follow-up at 798491cb7 marked C1 of your review at 442f0330c resolved. C2 named the Python and result checks, and CI at 7849e1128 passes all of them, along with every other job.

@MaxGhenis
MaxGhenis marked this pull request as ready for review November 5, 2026 11:22

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add overseas and other real estate components and correct household land accounting

4 participants