Repository navigation
Conversation
…on Chapter 8 income - HBAI household net income adds energy_bills_rebate as miscellaneous income (DWP HBAI QMI FYE 2023); the Council Tax Rebate is paid in 2022-23 only (payments by 30 September 2022). - The trading allowance relieves income not otherwise charged (ITTOIA 2005 s. 783AB), sharing one allowance with trade receipts. New optional input income_not_otherwise_charged (default 0), deduction miscellaneous_income_trading_allowance_deduction, flag uses_trading_allowance for the loss opt-out, and miscellaneous_income_before_trading_allowance for means tests. Fixes #2724, fixes #2725. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… income YAML cases for miscellaneous_income_trading_allowance_deduction worked from ITTOIA 2005 Part 6A, Hypothesis properties for both fixes (a statutory differential, one allowance, bounds, monotonicity, trade parity, the loss election), and the Energy Bills Rebate docs: the monthly EBSS, the one-off Council Tax Rebate and its HBAI treatment as miscellaneous income. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Aggregate impact on certified data: passBase Checks: 0 broken by this change, 1 failing on both sides, 0 fixed. Moves flagged for review: 0 (thresholds in Notes
Moves flagged for reviewNone. Spending by benefit and service
Caseloads
Standard reform costings (change in gov_balance)
Standard reforms: change in each reform's target
Household net income by decileNo household's net income changes in any year. No change in any year: receipts by tax head; government balance, each component counted once; model totals; household income totals; population; poverty rates; people in poverty; inequality; mean household net income in each side's own deciles. 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
|
It declares the GDP-per-capita index, so dataset projection needs it in uprating_indices.yaml (test_uprating_declarations), in the block that grows miscellaneous_income, which it is part of. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The trades-first split of the allowance charges the least income tax only without trade losses brought forward. With them, a smaller trade share can charge less now but uses up a loss that otherwise carries forward (ITA 2007 ss. 83-84) and raises Class 4 profits. The helper now says so instead of claiming every split is tax-neutral, and YAML Case 14 pins the split. The property tests draw losses brought forward and negative profits. The statutory reference returns the trades-first figure, which the model must match, and the lowest figure any split under s. 783AK allows, which it must never undercut and must equal without losses brought forward. The docs state the zero-receipts reading for a loss-making trade, and that ordinary treatment is an election out of full relief (s. 783AL) or no election for partial relief (s. 783AM). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n relief - HBAI counts the Energy Bills Rebate by financial year, as DWP does: hbai_energy_bills_rebate sums the monthly credit from April to the next March, so the whole £400 (Northern Ireland's January 2023 payment too) counts in 2022-23 and nothing in 2023-24. energy_bills_rebate keeps the calendar-year sum for household net income and government spending. - The Welsh childcare offer's reviewed source set now names miscellaneous_income_before_trading_allowance (CI's only failure). - The trading allowance docstring no longer claims every split charges the same income tax without losses brought forward: pension relief reads the trade's share too. YAML Case 15 pins the trades-first result. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fixes #2724
Fixes #2725
What and why
The review of #2719 (Autumn Budget 2026 baseline) found two pre-existing bugs. This PR fixes them outside #2719 and together, so one merge-queue pass covers both. It is not for merging during the Budget-week hold.
A third item from that review, Carer's Allowance penny rounding at the earnings limit, is already fixed more thoroughly by open draft #2543 (issue #2541). #2543 stores the limit in pence and applies reg. 5 per step, while the review's patch used a float tolerance, so it is not repeated here.
1. The 2022 Energy Bills Rebate in HBAI income (#2724)
Bug.
energy_bills_rebate(the Energy Bills Support Scheme credit plus the £150 Council Tax Rebate) counted inhousehold_net_incomeandgov_spending, but not inhbai_household_net_income, the income behind the poverty statistics. HBAI counts both. The HBAI quality and methodology report for FYE 2023 puts the EBSS (£400, or £600 in Northern Ireland) and the £150 Council Tax Rebate under miscellaneous income, not state support, and DWP imputes receipt because the FRS did not ask about them.No double counting. The FRS fields behind
miscellaneous_incomein policyengine-uk-data (frs.py: current odd-job incomeoddjob.ojamt, plusallpay2,royyr2–royyr4,chamternandchamttst) hold no rebate, so adding the rebate counts it once. (allpay2reaching bothmiscellaneous_incomeandprivate_transfer_incomeis a separate, known overlap, policyengine-uk-data#627.) It stays out ofhbai_benefits, which HBAI keeps for state support.Second bug found on the way: the Council Tax Rebate was paid again in 2023-24.
council_tax_rebate/amount.yamlset £150 from 1 January 2022 to 1 October 2023. Fiscal-year parameters are read at 30 April, so the model paid £150 in 2022-23 and again in 2023-24. The existing test case expected £150 in 2023. Counting the rebate in HBAI income would have counted that second payment too. The billing authority guidance makes it a one-off: eligibility was fixed on 1 April 2022 and billing authorities made every payment by 30 September 2022 (paragraphs 7, 14 and 19). The parameter is now £150 from 2022-04-01 to 2022-09-30 and zero otherwise.Timing: HBAI counts the rebate by financial year. HBAI is a financial-year statistic and counts the whole EBSS in FYE 2023. The model sums the monthly credit by calendar year (#2103's convention): £199 in 2022 and £201 in 2023 for a household on the scheme throughout. Fiscal-year parameters are read at 30 April, so model year 2022 is 2022-23, the HBAI year that should hold all £400. A new variable,
hbai_energy_bills_rebate, sums the monthly credit from April of the year to the next March, plus the Council Tax Rebate and the annual top-up parameter. That puts £400 in 2022-23 (Northern Ireland's January 2023 payment included) and nothing in 2023-24.energy_bills_rebatekeeps the calendar-year sum forhousehold_net_incomeandgov_spending.The financial-year sum reads January to March of the next calendar year, including the household's
countryin that year. Datasets carry inputs forward. A situation that setscountryfor one year only gets the default (England) the next year, whileregioncarries forward, so the new tests setregion.Changes.
hbai_energy_bills_rebate, added toHBAI_HOUSEHOLD_NET_INCOME_ADDSnext to the Warm Home Discount.council_tax_rebate/amount.yamlnow covers 2022-23 only.cost-of-living-payments.mdnow describes the monthly EBSS, the 2022-23-only CTR and HBAI's financial-year treatment. It previously described a flat £200 credit.2. The trading allowance on Chapter 8 miscellaneous income (#2725)
Bug. The trading allowance covers "relevant income": trade receipts plus "miscellaneous income" (ITTOIA 2005 s. 783AC). The model applied it to trades only.
Law. These are from ITTOIA 2005 Part 6A Chapter 1.
Royalties, other Part 5 chapters, and the SPI's benefits and termination payments are in
miscellaneous_incomebut are not Chapter 8 income, so they get no allowance.Design.
income_not_otherwise_charged(person, year): the Chapter 8 part ofmiscellaneous_income. It is optional and defaults to zero, because no dataset separates it yet (see Follow-ups). It is capped atmiscellaneous_incomeand never adds to it.utils/trading_allowance.py::trading_allowance_reliefsmakes one decision for the whole relevant income and returns(trade_relief, miscellaneous_relief).trading_allowance_deductionlogic, unchanged when there is no Chapter 8 income.loss_relief, ITA 2007 s. 83) absorb the trade profit a smaller share leaves, but that uses up loss which would otherwise carry forward against later profits of the trade (s. 84). YAML Case 14 pins this: £3,606 against £3,486 under the £200/£800 split, which would use £600 of the loss.miscellaneous_income_trading_allowance_deduction;miscellaneous_income_before_trading_allowance(the oldtaxable_miscellaneous_incomeformula,max_(0, miscellaneous_income));uses_trading_allowance, true when either relief applies.taxable_miscellaneous_incomeis now the before-allowance figure less the deduction.Means tests read income before the allowance. Benefits read raw
self_employment_income, not profit after the trading allowance. To match, every means-test and gross-income reader of miscellaneous income now usesmiscellaneous_income_before_trading_allowance:uc_individual_earned_income_before_mif);gross_taxable_inputs;Tax-side readers stay on
taxable_miscellaneous_income: adjusted net income, the UC income tax deduction, the CPS marriage tax reform and the property allowance check."Allowance chosen" now means either relief. Three places treated
trading_allowance_deduction > 0as "the allowance was used": the trading loss inannual_allowance_net_income(utils/pensions.py),trade_loss_relief_against_general_income, andni_class_4_trading_loss. With relief on Chapter 8 income alone, that would let the s. 783AF nil trade loss be claimed as well. All three now readuses_trading_allowance. YAML Cases 10 and 11 cover both directions: a £5,000 loss is claimed and the casual income is taxed, while a £500 loss gives way to full relief on £800 of casual income.Not modelled (stated in the helper's docstring):
Assumption with unknown receipts. Without
self_employment_gross_receipts, a trade's receipts are taken as the lowest its profit allows: the profit when that is within the allowance, and nil when the trade made a loss or no profit. This is the reading the trade-side logic already uses, and it is the generous one. The helper and theincome_not_otherwise_chargeddocs both state it. So a loss-making trade with unknown receipts leaves the whole allowance to Chapter 8 income. Where actual receipts would push relevant income above the allowance, the model can over-relieve. With receipts supplied, the statutory differential test covers it exactly.Invariants
Each one holds for every input. Each is tested by Hypothesis over many generated cases in one reusable simulation.
Energy Bills Rebate (
tests/test_energy_bills_rebate_hbai_properties.py, 16 households across bands, nations, pay, UC claims and EBSS months, 2021–2024):hbai_energy_bills_rebate, in every year, nation and band. (test_hbai_income_rises_by_exactly_the_rebate)hbai_benefitsdoes not change with the rebate, because it is miscellaneous income. (same test)test_the_council_tax_rebate_is_paid_once)test_hbai_counts_the_credit_by_financial_year)hbai_energy_bills_rebateandenergy_bills_rebateadd up to the same total. (test_financial_and_calendar_years_count_the_same_payments)hbai_energy_bills_rebateonce, and neitherenergy_bills_rebatenor its parts, and neither it nor the Warm Home Discount is inhbai_benefits. (test_the_rebate_is_counted_once_and_not_as_state_support)Trading allowance (
tests/test_trading_allowance_miscellaneous_income_properties.py, 24 drawn cases × 3 copies: drawn Chapter 8 share, none, all; each with and without the trade; the allowance is read from parameters, so the tests carry over to #2719's £3,000):test_taxable_income_matches_statutory_reference)test_one_allowance_covers_both)test_deduction_and_taxable_income_are_bounded)test_trade_relief_does_not_depend_on_chapter_8_income)test_no_chapter_8_income_changes_nothing)test_means_tests_read_income_before_the_allowance)test_income_tax_never_rises_with_more_chapter_8_income)test_chapter_8_income_is_taxed_like_trade_receipts)uses_trading_allowancematches the deductions, which only restates its formula. (test_a_current_loss_is_never_both_claimed_and_given_up)Mutation check. Each mutant was applied to the helper or flag, then the property tests and YAML were run.
uses_trading_allowancereads the trade relief onlyhbai_energy_bills_rebatesums calendar months, not financial-year monthsenergy_bills_rebate.yamlM3 at first got past every property test, because the loss test read the flag it was meant to check. The test now works out "allowance used" from the two deductions.
Independent review
Round 1 was dispatched as
subfleet run --task review --tier hard, at bd9cca3. No Codex lane was open, so Subfleet served it on Opus 5.5. Verdict: CHANGES REQUESTED, with 1 major, 2 minor and 2 nit findings. The report is in~/reviews/pe-uk-2726/review-r1.md. Each finding is addressed in 729062e:income_not_otherwise_charged, and Case 11's comment states it.loss_reliefand negative profits. The reference minimises over the feasible splits, and M5 shows it pins the documented split.Round 2 ran on GPT-6.1 Sol (pinned to a Codex lane) at 729062e. Verdict: CHANGES REQUESTED, with 1 major and 2 minor findings; report in
~/reviews/pe-uk-2726/review-r2.md. It recommended keeping trade-first as the documented convention. It confirmed the statutory reference's piecewise argument and found it non-vacuous: 31 strict gaps between the lowest split and trade-first among 1,050 applicable cases. It also found no change against main with Chapter 8 income at zero, over 40,000 formula cases. Each finding is addressed in 1077aa2:hbai_energy_bills_rebate(see Fix 1, Timing). Cases 3–7 and rebate invariants 4 and 5 pin it.taxable_miscellaneous_income. It now namesmiscellaneous_income_before_trading_allowance. This was CI's only failure on 729062e.Round 3, pinned to GPT-6.1 Sol, is pending on 1077aa2.
Test plan
miscellaneous_income_trading_allowance_deduction.yaml: 15 cases, with tax worked from s. 783AD's £1,000, the £12,570 personal allowance, the 20% basic rate and the 6% Class 4 main rate.energy_bills_rebate.yaml: Cases 3–7.council_tax_rebate.yaml: 2021, 2022 and 2023.pytest -n 4.tests/code_health,test_uprating_inputs_reach_data.pyandtest_projection_horizon.pywith-n 2: 3,027 passed.-n 4:make formatchangelog.d/2724.fixed.mdandchangelog.d/2725.fixed.mdAggregate impact
Pending CI's Aggregate impact job. Expected zero over 2025-26 to 2031-32: the rebates are nil after 2023-24, and
income_not_otherwise_chargeddefaults to zero, sotaxable_miscellaneous_income, every means-test reader and the loss sites compute what they did before.Overlaps with open PRs
household_energy_paymentto the same HBAI list, right afterwarm_home_discount, and inserts a docs section after the CTR parameter line this PR rewrites. Both are textual conflicts that keep both sides.trading_allowance.yamlsays the s. 783AD amount "also relieves miscellaneous income (s. 783B); the model applies it to trading income". Once this PR lands, the model applies it to both. The section is s. 783AB; s. 783B starts Chapter 2, the property allowance. Whichever lands second should fix that comment.utils/pensions.pyandtrade_loss_relief_against_general_income.py, where this PR changes one line each (trading_allowance_deduction > 0becomesuses_trading_allowance). Any new "allowance chosen" test there should read the flag.Follow-ups (not in this PR)
income_not_otherwise_chargedfrom the FRS odd-jobs and casual-income fields, and keep it at zero for SPI income that is already net of the allowance. Until then the relief has no aggregate effect.household_net_incomeandgov_spendingkeep Correct Energy Bills Support Scheme amounts and timing #2103's calendar-year sum of the EBSS. Whether they should follow the financial year too is a separate methodology question.Reproducer: policyengine_uk/tests/policy/baseline/gov/treasury/energy_bills_rebate/energy_bills_rebate.yaml; policyengine_uk/tests/policy/baseline/gov/treasury/energy_bills_rebate/council_tax_rebate.yaml; policyengine_uk/tests/policy/baseline/gov/hmrc/income_tax/allowances/miscellaneous_income_trading_allowance_deduction.yaml; policyengine_uk/tests/test_trading_allowance_miscellaneous_income_properties.py::test_taxable_income_matches_statutory_reference; policyengine_uk/tests/test_energy_bills_rebate_hbai_properties.py::test_hbai_counts_the_credit_by_financial_year
axiom: TheAxiomFoundation/rulespec-uk#348 queued
The axiom line covers the trading allowance. The spec for its miscellaneous-income half is in this comment, and the encoder is blocked because the corpus lacks ss. 783AA–783AR. The Energy Bills Rebate half is n/a: HBAI income is DWP's statistical definition (microsimulation-only), and the 2022-23 Council Tax Rebate was a grant scheme set by billing-authority guidance, with no rulespec-uk module.
🤖 Generated with Claude Code