Repository navigation
Fix ISA income and capital treatment; model bonuses and charges - #2589
Conversation
Aggregate impact on certified data: passBase Checks: 0 broken by this change, 1 failing on both sides, 0 fixed. Moves flagged for review: 0 (thresholds in New warnings
Notes
Moves flagged for reviewNone. Receipts by tax head
Spending by benefit and service
Caseloads
Government balance, each component counted once
Model totals
Household income totals
Inequality
Mean household net income in each side's own deciles
Standard reform costings (change in gov_balance)
Standard reforms: change in each reform's target
Household net income by decileMean change in household net income per household, and as a share of the decile's base income; households stay in the base run's deciles.
No change in any year: population; poverty rates; people in poverty. Checks failing on both base and head (1)
Counted-once checkEach of 91 components was raised by £1 per unit in 2026-27, and every head of its totals was read household by household: its own head must rise by exactly £1, no other tax head may move, no other benefit may rise, and the model's own total must move by what its heads moved. A head that breaks a rule is measured again twice: with every other head held at its baseline value, and with every other head held at its value after the first £1 while the component rises again. It fails where either still shows the response. 0 failed; 0 were skipped.
Provenance
Dataset: Receipts by tax head: head levels
Spending by benefit and service: head levels
Caseloads: head levels
Government balance, each component counted once: head levels
Model totals: head levels
Household income totals: head levels
Population: head levels
Poverty rates: head levels
People in poverty: head levels
Inequality: head levels
Mean household net income in each side's own deciles: head levels
Standard reform costings (change in gov_balance): head levels
Standard reforms: change in each reform's target: head levels
|
|
First review round from me, at head The policy side matches the law on every point I checked. What stops it leaving draft is CI, the conflicts with main and one index question. What I checked against the law
Findings
What I ran: a fresh venv at the head. The 5 new or changed YAML files pass (34 cases), and the 3 new Python files pass (49 tests). The code-health failure reproduces. On base What I did not read in detail: most of the 708-line Verdict: not ready to leave draft. Items 1 and 2 must be fixed, the Winter Fuel Payment growth question above needs an answer, item 3 should be fixed before merge, and item 6 needs a maintainer decision. The law and parameter values look right. 🤖 Generated with Claude Code |
c51a77a to
bead2c6
Compare
|
Fixed the household-income list check by documenting the Lifetime ISA bonus as a capital transfer. The existing synthetic fiscal-routing test continues to require unchanged current household income and supplied capital stocks. Rebased onto current main and preserved both ISA income contracts and energy-price declarations across the three dataset conflict resolutions. Added two public synthetic cases through reset, export, filtering, copy, H5 and reprojection. Local post-integration validation: 330 distinct passes (296 Python and 34 policy YAML), no skips or expected failures. One fixture-only correction supplied the existing explicit energy declaration; only its two failed cases were rerun. Formatting, lint and diff checks pass. Main's certified receipts precision fix is inherited without a new tolerance change. Certified dataset and aggregate reconciliation remain for current-head CI. |
|
@juaristi22 on finding 3 (interest growth index): no need to change it here for #2638. Whichever of #2589 and #2638 merges second, I'll do the follow-up in #2638 so ISA interest and savings interest use the same index. 🤖 Generated with Claude Code |
bead2c6 to
8d18af5
Compare
|
The technical review fixes are implemented, with the population-evidence limitation still open:
On the WFP/projection question: current main separates gross payments from the personal tax charge; ISA returns are excluded from taxable income. The nominal £35,000 threshold stays fixed while income can grow, and short growth series repeat their final 2030 rate in the 2031–32 extension. These are verified mechanisms, not a verified attribution of the earlier population trend. The description now states that limitation and the need for a matched current-code comparison. |
|
Second review round from me, at head The branch is rebased onto main Round 1 status
Winter Fuel Payment question: answered as far as it can be without a matched run. The body now names the two mechanisms (the fixed nominal £35,000 threshold, and 2031-32 repeating the final 2030 growth rate) and claims no attribution. That is enough for me. The changed WFP policy case keeps its original outputs (£34,500 total income, £200 payment, £0 charge) and adds the split. New findings
What I ran: a fresh venv at What I did not read in detail: the 919-line Verdict: close, but not ready to leave draft. New findings 1 and 2 must be fixed so the full suite goes green. Both are small. After that, what is left is two decisions that are not María's: item 6 (keep the adapter, Max) and item 5 (whether to merge before microcosm#1360 with the effect disclosed but not checked against outturn, Vahid and Max). The law, the parameters and the round 1 fixes look right. 🤖 Generated with Claude Code |
8d18af5 to
80281a9
Compare
|
The two new CI findings and the WFP comment nit are addressed on the rebased branch:
The branch is cleanly integrated with actual main A separate isolated run of the complete 53-case ISA income-contract module also passes on released Core 3.33.3 with the same final source and no production dependency change. Those version checks are not added to the 740-case distinct total and do not constitute Core-boundary-owner acceptance. The compatibility adapter remains, following the direction to keep it for compatibility and document maintenance. The explicit Core-boundary-owner acceptance you requested remains an external review matter; no individual approval is claimed. The matched population/outturn comparison and the decision whether to merge before microcosm#1360 also remain open. Public household tests do not resolve those population-evidence decisions, and no new private-data or population run was performed for this repair. |
|
Third review round from me, at head Thanks for the fast fixes. I compared the new branch with the one I reviewed in round 2 ( Round 2 status
Energy metadata and BSP commits. I read the two test-only commits I had skipped in round 2 ( New findings
What I ran: nothing locally this round, to keep load off the laptop. I read the diff and the range-diff, and I rely on CI at What I did not read in detail: the rest of the 1,001-line Verdict: no must or should findings, and the full suite is green at this head. It is ready to leave draft from my side. When you mark it ready, I will approve at that exact head, provided it is still
🤖 Generated with Claude Code |
vahid-ahmadi
left a comment
There was a problem hiding this comment.
Approving at 80281a9; see the round 3 comment above. The two maintainer notes there (runtime adapter; merging before microcosm#1360 with the HB/ESA capital effect disclosed) are not blocking from our side.
🤖 Generated with Claude Code
Brings in #2589 (ISA inputs), #2639 (rail per person) and #2650. uprating_indices.yaml keeps both per-person blocks (interest and rail journeys). non_cash_isa_cash_interest moves with savings_interest_income to the per-person interest index, so KNOWN_INDEX_OVERRIDES stays empty. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
All savings interest now uses ons.per_capita.household_interest_income: non_cash_isa_cash_interest (declaration and dataset YAML), the additive ISA-interest total (dataset projection and Core fallback), and gross interest under the legacy contract. #2589 had left these on GDP per capita or the aggregate ONS index pending this PR. Tests that controlled interest through GDP growth now set the interest index; the column test uses different GDP and interest rates so a wrong index fails. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fixes #2584
What changes
Make ordinary savings interest and ISA returns additive. With £2,000 of bank interest and £500 of ISA interest, the model includes £2,500 in cash income and £2,000 in taxable savings interest. ISA dividends remain outside ordinary dividend tax. Tax Credits exclude ISA returns; the affected Council Tax Reduction gross-income calculation retains them.
Unmarked datasets and exported input frames retain their legacy gross-interest interpretation. The loader converts gross interest minus the effective tax-free aggregate once, preserving explicit zero and differing overrides and the projection origin. Versioned metadata preserves those conventions through copies, H5/DataFrame round trips, projection resets and fallback. ISA dividend and noncash interest household inputs now carry forward with their ordinary counterparts' GDP-per-capita and ONS household-interest indices, respectively; explicit later observations take priority. Stored ISA dividends and noncash-interest detail also grow throughout dataset projections, including before 2031, with the existing GDP-per-capita dataset rules for ordinary dividends and savings interest. The narrow code-health exception documents the existing ONS-household/GDP-dataset split for interest; it does not change either ordinary index. The separately discussed dataset-index alignment remains with #2638.
Include
cash_isain the UC, Housing Benefit, Pension Credit, Income Support, income-based JSA and income-related ESA capital pools. Reported-total and complete-capital overrides replace the relevant pool, including observed zero; cash ISAs receive no generic sale-cost deduction.Implement the Lifetime ISA bonus and withdrawal charge, and the final charge on noncash-ISA cash interest from 6 April 2027. The bonus and withdrawal charge enter government spending and revenue as capital transfers, outside household current income, without updating supplied account-balance stocks. The noncash-ISA interest charge enters household and government tax once, outside ordinary income tax and its allowances. The medical-exemption input is specifically named
lifetime_isa_terminally_ill; its medical evidence requirement and withdrawal treatment are unchanged. The old generic name existed only in this unmerged implementation and is removed.The assumptions page documents eligibility, exemptions, annual sampling and transaction/declaration limitations. The closing Help to Buy ISA scheme, subscription limits and proposed First Time Buyer ISA remain outside this change; app input grouping remains separate. Primary rules are linked in the parameter and variable references: ISA regulation 22, Lifetime ISA Schedule, UC capital valuation and corresponding legacy regimes, Tax Credits regulation 10, and SI 2026/1018.
Compatibility and population evidence
Keep the legacy compatibility adapter. PolicyEngine-UK maintainers own its private Core API integration and the legacy/additive override, projection and reset regressions when Core changes. Future retirement requires an explicit maintainer decision supported by a versioned producer and consumer migration; no removal date is assumed. Retaining the adapter is the current compatibility decision. Explicit acceptance by the Core-boundary owner remains an open review matter; no individual approval is claimed.
Microcosm issue 1360 remains open producer-migration and calibration work. This consumer PR has not verified a released producer dataset recalibrated for the expanded capital definition. Population estimates also depend on balances, reported-total overrides, benefit take-up and weights. The numerical gap against a consistently defined DWP outturn remains unverified. Public synthetic UC, Housing Benefit and ESA award tests establish household routing; they do not establish population magnitude or fit to outturn. That part of the review remains an open evidence item.
Winter Fuel Payment and projections
Current main separates gross Winter Fuel Payment entitlement from the individual income-tax recovery charge. The charge recovers the payment above £35,000 total taxable income, subject to the relevant-benefit exemption. ISA returns stay outside taxable income, as does the payment itself. The migrated policy fixture preserves £34,000 pension income and £2,000 total interest split into £500 ordinary and £1,500 ISA interest; taxable income stays £34,500, the gross payment £200, the charge £0 and income tax £4,286. HMRC charge guidance, ISA exemption.
The modeled £35,000 threshold is fixed in nominal terms, so income growth can move recipients across it. Dataset projections extend through 2032, repeating the final 2030 annual growth rate for series that end then. This extrapolation and the old household eligibility proxy do not establish the cause of an earlier population trend. A current comparison must separate gross payments from the charge and match dataset, policy version, year and income convention. No quantitative attribution is claimed here.
Integration and validation
The household-income list check documents the Lifetime ISA bonus's capital-transfer exception. The synthetic accounting regression requires the bonus to reach government spending while current household income and supplied stocks stay unchanged.
The branch is rebased onto actual main
e0998eee742a824e52d25a86bc393801eea47729, preserving ISA income contracts, energy-price declarations, BSP split/projection behavior, employer-NI and fixed-employer-cost changes, Housing Benefit family premiums and couple-carer rules. Both integration steps were clean, with no manual conflict resolution. Main's existing certified receipts precision correction is inherited unchanged.The total-income property now uses the additive ordinary-interest contract: £1 other investment income plus £1 separate ISA interest gives £1 taxable total income. Its deterministic minimized example retains all tax-component and relief assertions. The stored-flow regression uses positive ordinary-counterpart benchmarks under both legacy and additive dataset contracts through 2025, 2028, 2030, 2031 and 2032, while preserving exemptions, cash and assessable-income conservation and caller-owned source tables. The WFP fixture deliberately retains an explicit exemption equal to ISA interest as an override-consistency check; all economic inputs and expected policy outcomes are unchanged.
Final local evidence covers 740 distinct passing cases: 537 Python and 203 policy YAML, with no failures, errors, skips or expected failures. The initial frozen integration gate on main
5056d25epassed 525 cases (406 Python and 119 YAML). After main advanced, a bounded closure on final maine0998eeepassed 206 Python and 106 YAML cases, revalidating the affected registry, additive-income, projection/ISA contract and new NI/Housing Benefit/carer consumers. The distinct total uses the latest result per test ID and does not double-count the 75 Python and 22 YAML cases rerun for the changed base. Unaffected prior results are retained; this is focused integration evidence, not the full repository suite.A separate isolated compatibility gate passes all 53 ISA income-contract cases on released Core 3.33.3 with the same final UK source, Python 3.13.14 and UK 2.160.0. All 134 reviewed source/test fingerprints remain unchanged, and no active environment or production dependency pin was changed. These version checks are reported separately from the 740 distinct native cases; they establish this tested interface combination, not Core-boundary-owner acceptance or compatibility with future Core versions.
The original ISA coverage still includes household growth and explicit observations, the Lifetime ISA medical input, HB/ESA award comparisons, complete WFP-charge policy cases, legacy/additive metadata boundaries and energy/BSP composition. The ESA comparison uses an observed ongoing-claim input to establish capital-screen routing; it does not independently prove every entitlement condition or population magnitude.
Recorded final bounded selections in the frozen development environment (local cache/report flags omitted):
uv run --frozen --no-sync pytest \ policyengine_uk/tests/code_health/test_uprating_declarations.py \ policyengine_uk/tests/test_income_concepts_properties.py \ policyengine_uk/tests/test_isa_income_contract.py \ policyengine_uk/tests/test_isa_income_consumers.py \ policyengine_uk/tests/code_health/test_household_income_lists.py \ policyengine_uk/tests/test_housing_benefit_capital_threshold_history.py \ policyengine_uk/tests/test_esa_income_universal_credit_claim_properties.py \ policyengine_uk/tests/test_employer_ni_fixed_cost_properties.py \ policyengine_uk/tests/test_employer_ni_zero_rate_consumers.py \ policyengine_uk/tests/test_legacy_carer_premium_properties.py \ policyengine_uk/tests/test_ni_class_1_age_properties.py \ policyengine_uk/tests/test_salary_sacrifice_ni_measure_properties.py \ policyengine_uk/tests/test_threshold_indexation.py \ -n 4 --dist worksteal -q -o xfail_strict=true uv run --frozen --no-sync policyengine-core test \ policyengine_uk/tests/policy/baseline/finance/benefit/couple_carer_award.yaml \ policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/housing_benefit_family_premium.yaml \ policyengine_uk/tests/policy/baseline/gov/hmrc/national_insurance/class_1/ni_class_1_employer.yaml \ policyengine_uk/tests/policy/baseline/gov/hmrc/national_insurance/salary_sacrifice_pension_ni_employer.yaml \ policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/housing_benefit_capital.yaml \ policyengine_uk/tests/policy/baseline/finance/benefit/family/esa_income/esa_income_capital.yaml \ -c policyengine_uk uv run --frozen --no-sync make documentationThe documentation build passes for 51 pages, including the changed ISA assumptions page; existing documentation and Node runtime warnings remain. The scoped formatter ran once for this repair, repository Ruff checks and
git diff --checkpass, and all 57 reviewed Python ASTs remain unchanged after formatting under Python 3.13.14. All seven repair paths stayed byte-identical through the clean final rebase. The final closure records 134 byte and 81 AST fingerprints on the actual base. The frozen final environment uses Python 3.13.14, Core 3.33.2 and UK 2.160.0. The typed fragment ischangelog.d/isa-current-law.fixed.md. Trusted current-head full-suite, certified dataset and aggregate reconciliation remain separate CI gates; no private survey or population run was performed for this round.Reproducer: policyengine_uk/tests/test_isa_income_contract.py::test_new_isa_columns_follow_ordinary_dataset_projection_in_both_contracts
Reproducer: policyengine_uk/tests/test_income_concepts_properties.py::test_total_income_equals_tax_components_before_step_two_reliefs
Reproducer: policyengine_uk/tests/code_health/test_uprating_declarations.py::test_declared_uprating_variables_are_in_the_yaml
Reproducer: policyengine_uk/tests/test_isa_income_contract.py::test_isa_and_energy_contracts_compose_through_reset_export_and_projection
Reproducer: policyengine_uk/tests/test_isa_income_contract.py::test_split_bsp_isa_and_energy_share_reset_and_projection_boundaries
Reproducer: policyengine_uk/tests/policy/baseline/gov/hmrc/income_tax/charges/winter_fuel_payment_charge.yaml::2025 ISA interest is not total income
Reproducer: policyengine_uk/tests/policy/baseline/gov/hmrc/isa/isa_income.yaml
Reproducer: policyengine_uk/tests/policy/baseline/gov/dwp/cash_isa_capital.yaml
axiom: TheAxiomFoundation/rulespec-uk#487 queued
The existing ISA parity queue retains its module/corpus targets, primary provisions and companion cases. The capital valuation queue also covers ordinary cash ISAs. Source ingestion/release and a separately authorized signed encoder run remain pending; no encoder was run or manual RuleSpec written in this repair.