diff --git a/changelog.d/2724.fixed.md b/changelog.d/2724.fixed.md new file mode 100644 index 0000000000..b4673125c4 --- /dev/null +++ b/changelog.d/2724.fixed.md @@ -0,0 +1 @@ +Counted the 2022 Energy Bills Rebate (the Energy Bills Support Scheme credit and the £150 Council Tax Rebate) in HBAI household net income as miscellaneous income, as DWP's HBAI does, and paid the one-off Council Tax Rebate in 2022-23 only rather than again in 2023-24. diff --git a/changelog.d/2725.fixed.md b/changelog.d/2725.fixed.md new file mode 100644 index 0000000000..0d29eaab62 --- /dev/null +++ b/changelog.d/2725.fixed.md @@ -0,0 +1 @@ +Applied the trading allowance to income not otherwise charged (ITTOIA 2005 Part 5 Chapter 8), sharing one allowance with trade receipts, through a new optional `income_not_otherwise_charged` input that defaults to zero. Means tests and gross income bases now read `miscellaneous_income_before_trading_allowance`, so the income tax relief does not reach them. diff --git a/changelog.d/ni-alternative-fuel-payment.added.md b/changelog.d/ni-alternative-fuel-payment.added.md new file mode 100644 index 0000000000..a27d6018e5 --- /dev/null +++ b/changelog.d/ni-alternative-fuel-payment.added.md @@ -0,0 +1 @@ +Added Northern Ireland's £200 Alternative Fuel Payment (`ebr_alternative_fuel_payment`), paid with the £400 Energy Bills Support Scheme amount in January 2023, so a Northern Ireland household's Energy Bills Rebate totals the £600 the December 2022 direction set. diff --git a/changelog.d/ni-council-tax-rebate.fixed.md b/changelog.d/ni-council-tax-rebate.fixed.md new file mode 100644 index 0000000000..9bf2a9964c --- /dev/null +++ b/changelog.d/ni-council-tax-rebate.fixed.md @@ -0,0 +1 @@ +Stopped paying the 2022 £150 Council Tax Rebate in Northern Ireland, which levies domestic rates rather than council tax and had no rebate. The enhanced FRS gives every Northern Ireland household band A, so the model had paid it to all of them. diff --git a/docs/book/programs/gov/treasury/cost-of-living-payments.md b/docs/book/programs/gov/treasury/cost-of-living-payments.md index 798c0fefe7..b160d11e46 100644 --- a/docs/book/programs/gov/treasury/cost-of-living-payments.md +++ b/docs/book/programs/gov/treasury/cost-of-living-payments.md @@ -2,9 +2,10 @@ In response to the 2021-23 cost-of-living crisis the UK government announced two distinct one-off support packages. Both are modelled in -PolicyEngine UK under `gov.treasury.*` and feed into household net -income via [`gov_spending`](https://github.com/PolicyEngine/policyengine-uk/blob/main/policyengine_uk/variables/gov/gov_spending.py) -and the HBAI benefit aggregates. +PolicyEngine UK under `gov.treasury.*` and count in household net income +and in [`gov_spending`](https://github.com/PolicyEngine/policyengine-uk/blob/main/policyengine_uk/variables/gov/gov_spending.py). +Both also count in HBAI household net income, the income behind the poverty +statistics, though under different HBAI headings (see below). ## Energy Bills Rebate (winter 2022) @@ -12,21 +13,70 @@ Announced by the Chancellor in February 2022 to help households with the spike in domestic energy prices. Two strands: - **£150 Council Tax Rebate** for households in Council Tax bands A–D in - England, paid in April 2022. Equivalent payments were made by the - devolved administrations. -- **£200 Energy Bills Credit** delivered via energy suppliers to all - domestic electricity customers from October 2022. Originally announced - as a repayable loan, the repayment requirement was scrapped at the May - 2022 emergency support package. + England, a one-off in 2022-23. Households qualified on their band on 1 + April 2022. The guidance asked billing authorities to pay from April and + set 30 September 2022 as the deadline for the payments the government + would reconcile (paragraph 7). Scotland and Wales paid their own £150 + awards to bands A–D, and also to Council Tax Reduction recipients in any + band, which the model leaves out. Northern Ireland levies domestic rates, + not council tax, and had no Council Tax Rebate, so the model pays none + there whatever the household's council tax band (the enhanced FRS gives + every Northern Ireland household band A, the default policyengine-uk-data + assigns when the FRS records no band). +- **Energy Bills Support Scheme**, a £400 discount on domestic electricity + bills in Great Britain, paid in six monthly instalments from October 2022 + to March 2023 (£66 for two months, then £67). Announced in February 2022 + as a repayable £200, it became a £400 grant in the May 2022 support + package. Northern Ireland paid £600 in one go from January 2023: £400 of + support and a £200 Alternative Fuel Payment, to each domestic electricity + customer on 2 January 2023, whatever fuel the household heats with (EBSS + AFP NI Direction, 23 December 2022, paragraph 3 and footnote 1). Great + Britain's £200 Alternative Fuel Payment, for households that heat with + heating oil, LPG, biomass and other alternative fuels, is not modelled. PolicyEngine models these via: - `energy_bills_rebate` (Household, YEAR) = `ebr_council_tax_rebate` + - `ebr_energy_bills_credit` + `ebr_energy_bills_credit` + `ebr_alternative_fuel_payment` - Parameters at `parameters/gov/treasury/energy_bills_rebate/`: - - `energy_bills_credit.yaml` — the £200 flat household amount + - `energy_bills_credit_monthly.yaml` — the Great Britain monthly + instalments + - `energy_bills_credit_northern_ireland.yaml` — the Northern Ireland + £400 in January 2023 + - `alternative_fuel_payment_northern_ireland.yaml` — the Northern Ireland + £200 Alternative Fuel Payment, paid with the £400 + - `energy_bills_credit.yaml` — a yearly top-up, zero by default, kept for + reforms - `council_tax_rebate/bands.yaml` — qualifying CT bands (A–D) - - `council_tax_rebate/amount.yaml` — the £150 rebate amount + - `council_tax_rebate/amount.yaml` — £150 in 2022-23 and nothing in any + other year + +`ebr_energy_bills_credit` sums the monthly instalments within each calendar +year, so a Great Britain household on the scheme throughout gets £199 in +2022 and £201 in 2023, while HBAI counts the whole £400 in 2022-23. Each +month takes the scheme of the household's `country` in that calendar year. +A situation that sets Northern Ireland for 2022 only therefore gets the +Great Britain credit from January to March 2023, and no Alternative Fuel +Payment, because `country` is not carried forward and defaults to England. + +HBAI classifies both strands as miscellaneous income, not state support, +and DWP imputes receipt because the FRS did not ask about them. So +`energy_bills_rebate` is in `hbai_household_net_income` but not in +`hbai_benefits`. No FRS income field behind `miscellaneous_income` holds +the rebates, so the model counts them once. + +HBAI income takes the same calendar-year sum as household net income and +government spending. A Great Britain household in bands A to D on the +scheme throughout gets £349 in 2022 and £201 in 2023, where HBAI counts +£550 in 2022-23. A Northern Ireland household gets nothing in 2022 and £600 +in 2023, though its main rollout began on 16 January 2023, in 2022-23. +The direction set 31 March 2023 as the normal deadline for providing it, and +let suppliers reissue vouchers and cheques that customers could redeem until +30 June 2023 (paragraph 12(b)). Summing the +credit by financial year would move all three measures together. `epg_subsidy` sums the Energy Price Guarantee by +calendar year in the same way; it counts in household net income and +government spending but not in HBAI income. The model keeps one timing for +both schemes for now. ## Cost-of-Living Payments (2022-23 and 2023-24) @@ -62,4 +112,5 @@ will collect all three payments. This matches the actual policy. - [HMT/DWP, May 2022 cost-of-living support package announcement](https://www.gov.uk/government/news/millions-of-most-vulnerable-households-will-receive-1200-of-help-with-cost-of-living) - DWP, [Cost-of-Living Payments 2023-24 guidance](https://www.gov.uk/guidance/cost-of-living-payments-2023-to-2024) (means-tested, pensioner and disability strands). - HMT, [Energy Bills Support Scheme](https://www.gov.uk/government/publications/the-energy-bills-support-scheme-explainer) and [Council Tax Energy Rebate](https://www.gov.uk/government/publications/the-council-tax-rebate-2022-23-billing-authority-guidance). +- DWP, [HBAI quality and methodology information report, FYE 2023](https://www.gov.uk/government/statistics/households-below-average-income-for-financial-years-ending-1995-to-2023/households-below-average-income-series-quality-and-methodology-information-report-fye-2023) (treatment of the cost-of-living support schemes in HBAI income). - House of Commons Library briefing [CBP-9501 — Cost of Living Payments and the energy crisis](https://commonslibrary.parliament.uk/research-briefings/cbp-9501/). diff --git a/policyengine_uk/data/uprating_indices.yaml b/policyengine_uk/data/uprating_indices.yaml index 8c2e4a57ae..d978c0ffb3 100644 --- a/policyengine_uk/data/uprating_indices.yaml +++ b/policyengine_uk/data/uprating_indices.yaml @@ -116,6 +116,8 @@ gov.economic_assumptions.yoy_growth.obr.per_capita.gdp: - lump_sum_income - maintenance_income - miscellaneous_income +# Part of miscellaneous_income, so it grows with it. +- income_not_otherwise_charged - mortgage_capital_repayment - net_financial_wealth - non_residential_property_value diff --git a/policyengine_uk/parameters/gov/treasury/energy_bills_rebate/alternative_fuel_payment_northern_ireland.yaml b/policyengine_uk/parameters/gov/treasury/energy_bills_rebate/alternative_fuel_payment_northern_ireland.yaml new file mode 100644 index 0000000000..58a741410f --- /dev/null +++ b/policyengine_uk/parameters/gov/treasury/energy_bills_rebate/alternative_fuel_payment_northern_ireland.yaml @@ -0,0 +1,17 @@ +description: >- + Northern Ireland provides this one-off Alternative Fuel Payment to eligible + domestic electricity customers, with its Energy Bills Support Scheme amount. +values: + 2022-01-01: 0 + 2023-01-01: 200 + 2023-02-01: 0 +metadata: + unit: currency-GBP + period: month + label: Northern Ireland one-off Alternative Fuel Payment + reference: + - title: Energy Bills Support Scheme and Alternative Fuel Payment Northern Ireland direction, paragraph 3 ("EBSS") and footnote 1 + href: https://assets.publishing.service.gov.uk/media/63a59ed48fa8f5654fe0a812/ebss-ni-direction.pdf#page=2 + - title: Households across Northern Ireland to start receiving £600 UK government energy support + href: https://www.gov.uk/government/news/households-across-northern-ireland-to-start-receiving-600-uk-government-energy-support + preserve_calendar_dates: true diff --git a/policyengine_uk/parameters/gov/treasury/energy_bills_rebate/council_tax_rebate/amount.yaml b/policyengine_uk/parameters/gov/treasury/energy_bills_rebate/council_tax_rebate/amount.yaml index 311502e4ea..cd116eabe3 100644 --- a/policyengine_uk/parameters/gov/treasury/energy_bills_rebate/council_tax_rebate/amount.yaml +++ b/policyengine_uk/parameters/gov/treasury/energy_bills_rebate/council_tax_rebate/amount.yaml @@ -1,8 +1,20 @@ description: Council Tax rebate paid to households in qualifying Council Tax bands under the Energy Bills Rebate. +# A one-off £150 per household in 2022-23. Eligibility was fixed at the end of +# 1 April 2022, and billing authorities had to make every payment by +# 30 September 2022 (billing authority guidance, paragraphs 7, 14 and 19). +# 2023-24 had no rebate. values: 0001-01-01: 0 - 2022-01-01: 150 - 2023-10-01: 0 + 2022-04-01: + value: 150 + reference: + - title: The Council Tax Rebate 2022-23 billing authority guidance, paragraphs 7, 14 and 19 + href: https://www.gov.uk/government/publications/the-council-tax-rebate-2022-23-billing-authority-guidance/support-for-energy-bills-the-council-tax-rebate-2022-23-billing-authority-guidance + 2022-10-01: + value: 0 + reference: + - title: The Council Tax Rebate 2022-23 billing authority guidance, paragraph 7 (payments made by 30 September 2022) + href: https://www.gov.uk/government/publications/the-council-tax-rebate-2022-23-billing-authority-guidance/support-for-energy-bills-the-council-tax-rebate-2022-23-billing-authority-guidance metadata: unit: currency-GBP period: year diff --git a/policyengine_uk/parameters/gov/welsh_government/childcare_offer/income/gross_income_sources.yaml b/policyengine_uk/parameters/gov/welsh_government/childcare_offer/income/gross_income_sources.yaml index 1e14c7c83a..c062d8e88c 100644 --- a/policyengine_uk/parameters/gov/welsh_government/childcare_offer/income/gross_income_sources.yaml +++ b/policyengine_uk/parameters/gov/welsh_government/childcare_offer/income/gross_income_sources.yaml @@ -61,7 +61,7 @@ values: # education_grants residual can still hold Childcare Grant and Disabled # Students' Allowance, because the dataset moves those out only through # England-only formulas; and the FRS allpay2 amount reaches both - # taxable_miscellaneous_income and private_transfer_income + # miscellaneous_income_before_trading_allowance and private_transfer_income # (policyengine-uk-data#627). 2019-08-01: - employment_income @@ -92,6 +92,6 @@ values: - savings_interest_income - other_investment_income - dividend_income - - taxable_miscellaneous_income + - miscellaneous_income_before_trading_allowance - maintenance_income - spousal_maintenance_income diff --git a/policyengine_uk/programs.yaml b/policyengine_uk/programs.yaml index 3803f6da39..4218101add 100644 --- a/policyengine_uk/programs.yaml +++ b/policyengine_uk/programs.yaml @@ -590,6 +590,26 @@ programs: verified_end_year: 2023 notes: Temporary program, ended 2023 + - id: energy_bills_rebate + name: Energy Bills Rebate + full_name: Energy Bills Rebate, Energy Bills Support Scheme and Alternative Fuel Payment + category: Benefits + agency: Treasury + status: partial + coverage: UK + variable: energy_bills_rebate + parameter_prefix: gov.treasury.energy_bills_rebate + verified_start_year: 2022 + verified_end_year: 2023 + notes: >- + Temporary program, ended 2023. The £150 Council Tax Rebate in bands A to + D in Great Britain (none in Northern Ireland, which has domestic rates), + the £400 Energy Bills Support Scheme, and Northern Ireland's £200 + Alternative Fuel Payment. Leaves out the Scottish and Welsh £150 for + Council Tax Reduction recipients in bands E and above, England's + discretionary fund, Great Britain's Alternative Fuel Payment and the + alternative funding schemes. + # --- Ofgem programs --- - id: energy_price_guarantee name: Energy Price Guarantee diff --git a/policyengine_uk/tests/policy/baseline/gov/hmrc/income_tax/allowances/miscellaneous_income_trading_allowance_deduction.yaml b/policyengine_uk/tests/policy/baseline/gov/hmrc/income_tax/allowances/miscellaneous_income_trading_allowance_deduction.yaml new file mode 100644 index 0000000000..c89f3aaaca --- /dev/null +++ b/policyengine_uk/tests/policy/baseline/gov/hmrc/income_tax/allowances/miscellaneous_income_trading_allowance_deduction.yaml @@ -0,0 +1,235 @@ +# The trading allowance on miscellaneous income, ITTOIA 2005 Part 6A +# Chapter 1. Relief covers the income of a relevant trade and "miscellaneous +# income" (s. 783A(1)): income chargeable under Part 5 Chapter 8, income not +# otherwise charged, before expenses (s. 783AB). One allowance covers both +# (s. 783AC, "relevant income"). +# - Full relief: relevant income within the allowance is not charged +# (ss. 783AE, 783AF and 783AG). +# - Partial relief, by election above the allowance: each source less its +# share of the allowance, the shares adding up to the allowance and chosen +# by the person, with no expenses (ss. 783AI-783AK). +# income_not_otherwise_charged is the part of miscellaneous_income that is +# Chapter 8 income. 2026-27: trading allowance £1,000 (s. 783AD), personal +# allowance £12,570, basic rate 20%. + +- name: Case 1, casual income within the allowance is fully relieved. + period: 2026 + absolute_error_margin: 0 + input: + miscellaneous_income: 800 + income_not_otherwise_charged: 800 + output: + miscellaneous_income_trading_allowance_deduction: 800 + taxable_miscellaneous_income: 0 + uses_trading_allowance: true + +- name: Case 2, above the allowance, £1,000 comes off. + period: 2026 + absolute_error_margin: 0.01 + input: + employment_income: 30_000 + miscellaneous_income: 2_000 + income_not_otherwise_charged: 2_000 + output: + miscellaneous_income_trading_allowance_deduction: 1_000 + taxable_miscellaneous_income: 1_000 + # (30,000 + 1,000 - 12,570) x 20% + income_tax: 3_686 + +- name: Case 3, the same tax as £2,000 of trade receipts with no expenses. + period: 2026 + absolute_error_margin: 0.01 + input: + employment_income: 30_000 + self_employment_gross_receipts: 2_000 + self_employment_income: 2_000 + output: + trading_allowance_deduction: 1_000 + income_tax: 3_686 + +- name: Case 4, miscellaneous income outside Chapter 8 gets no allowance. + period: 2026 + absolute_error_margin: 0.01 + input: + employment_income: 30_000 + miscellaneous_income: 2_000 + output: + miscellaneous_income_trading_allowance_deduction: 0 + taxable_miscellaneous_income: 2_000 + uses_trading_allowance: false + # (30,000 + 2,000 - 12,570) x 20% + income_tax: 3_886 + +- name: Case 5, trade and casual income share one allowance. + period: 2026 + absolute_error_margin: 0 + input: + # Receipts unknown: a profit within the allowance is taken as receipts + # of £600, leaving £400 of the allowance. + self_employment_income: 600 + miscellaneous_income: 900 + income_not_otherwise_charged: 900 + output: + trading_allowance_deduction: 600 + taxable_self_employment_income: 0 + miscellaneous_income_trading_allowance_deduction: 400 + taxable_miscellaneous_income: 500 + +- name: Case 6, receipts of £800 and casual income of £150 are within the allowance together. + period: 2026 + absolute_error_margin: 0 + input: + self_employment_gross_receipts: 800 + self_employment_income: 500 + miscellaneous_income: 150 + income_not_otherwise_charged: 150 + output: + trading_allowance_deduction: 500 + miscellaneous_income_trading_allowance_deduction: 150 + taxable_self_employment_income: 0 + taxable_miscellaneous_income: 0 + +- name: Case 7, trade receipts above the allowance take all of it. + period: 2026 + absolute_error_margin: 0 + input: + # £300 of expenses: receipts less the allowance (£4,000) beats profit + # (£4,700), and the trade uses the whole allowance. + self_employment_gross_receipts: 5_000 + self_employment_income: 4_700 + miscellaneous_income: 900 + income_not_otherwise_charged: 900 + output: + trading_allowance_deduction: 700 + taxable_self_employment_income: 4_000 + miscellaneous_income_trading_allowance_deduction: 0 + taxable_miscellaneous_income: 900 + +- name: Case 8, partial relief would cost the trade more in expenses than it saves. + period: 2026 + absolute_error_margin: 0 + input: + # £1,500 of expenses: partial relief would charge receipts plus casual + # income less the allowance, £4,900, against £3,500 + £900 = £4,400. + self_employment_gross_receipts: 5_000 + self_employment_income: 3_500 + miscellaneous_income: 900 + income_not_otherwise_charged: 900 + output: + trading_allowance_deduction: 0 + miscellaneous_income_trading_allowance_deduction: 0 + taxable_miscellaneous_income: 900 + uses_trading_allowance: false + +- name: Case 9, Chapter 8 income counts only up to miscellaneous income. + period: 2026 + absolute_error_margin: 0 + input: + miscellaneous_income: 500 + income_not_otherwise_charged: 2_000 + output: + miscellaneous_income_trading_allowance_deduction: 500 + taxable_miscellaneous_income: 0 + +- name: Case 10, a larger current trading loss is claimed instead, and the casual income is taxed. + period: 2026 + absolute_error_margin: 0.01 + input: + employment_income: 30_000 + trading_loss: 5_000 + miscellaneous_income: 800 + income_not_otherwise_charged: 800 + output: + uses_trading_allowance: false + miscellaneous_income_trading_allowance_deduction: 0 + taxable_miscellaneous_income: 800 + trade_loss_relief_against_general_income: 5_000 + # (30,000 + 800 - 5,000 - 12,570) x 20% + income_tax: 2_646 + +- name: Case 11, full relief on casual income makes a smaller current trading loss nil. + period: 2026 + absolute_error_margin: 0.01 + input: + employment_income: 30_000 + trading_loss: 500 + miscellaneous_income: 800 + income_not_otherwise_charged: 800 + output: + # Full relief saves £800 of income against a £500 loss (s. 783AF makes + # the trade's loss nil). Without receipts, the loss-making trade is + # taken to have none, so the casual income alone is relevant income. + uses_trading_allowance: true + miscellaneous_income_trading_allowance_deduction: 800 + trade_loss_relief_against_general_income: 0 + # (30,000 - 12,570) x 20% + income_tax: 3_486 + +- name: Case 12, means tests read the income before the allowance. + period: 2026 + absolute_error_margin: 0 + input: + miscellaneous_income: 2_000 + income_not_otherwise_charged: 2_000 + output: + miscellaneous_income_before_trading_allowance: 2_000 + taxable_miscellaneous_income: 1_000 + gross_taxable_inputs: 2_000 + +- name: Case 13, no trading allowance before 2017-18. + period: 2016 + absolute_error_margin: 0 + input: + miscellaneous_income: 800 + income_not_otherwise_charged: 800 + output: + miscellaneous_income_trading_allowance_deduction: 0 + taxable_miscellaneous_income: 800 + +- name: Case 14, losses brought forward do not move the allowance from the trade. + period: 2026 + absolute_error_margin: 0.01 + input: + employment_income: 30_000 + self_employment_gross_receipts: 800 + self_employment_income: 800 + loss_relief: 800 + miscellaneous_income: 800 + income_not_otherwise_charged: 800 + output: + # Relevant income of £1,600 is above the allowance. The trade takes its + # receipts' worth of it (£800) and the casual income the other £200 + # (s. 783AK). The trade's profit is then nil, so none of the £800 loss + # is used this year; it carries forward (ITA 2007 ss. 83 and 84). The Act + # would also allow £200 for the trade and £800 for the casual income: + # £3,486 of tax now, using £600 of the loss. + trading_allowance_deduction: 800 + miscellaneous_income_trading_allowance_deduction: 200 + taxable_self_employment_income: 0 + taxable_miscellaneous_income: 600 + # (30,000 + 600 - 12,570) x 20% + income_tax: 3_606 + +- name: Case 15, the trade takes the allowance even where pension relief would rise without it. + period: 2026 + absolute_error_margin: 0.01 + input: + self_employment_gross_receipts: 20_000 + self_employment_income: 20_000 + miscellaneous_income: 30_000 + income_not_otherwise_charged: 30_000 + personal_pension_contributions: 20_000 + output: + # The trade's receipts of £20,000 exceed the allowance, so the trade + # takes all £1,000 of it and the casual income none (s. 783AK). Trade + # profit of £19,000 is the relevant UK earnings that cap pension relief + # (FA 2004 s. 189), so £19,000 of the £20,000 contributions get relief. + # The Act would also allow the casual income the whole allowance: relief + # on all £20,000 and £3,286 of income tax, but £445.80 of Class 4. + trading_allowance_deduction: 1_000 + miscellaneous_income_trading_allowance_deduction: 0 + pension_contributions_relief: 19_000 + # (19,000 + 30,000 - 19,000 - 12,570) x 20% + income_tax: 3_486 + # (19,000 - 12,570) x 6% + ni_class_4: 385.80 diff --git a/policyengine_uk/tests/policy/baseline/gov/treasury/energy_bills_rebate/alternative_fuel_payment.yaml b/policyengine_uk/tests/policy/baseline/gov/treasury/energy_bills_rebate/alternative_fuel_payment.yaml new file mode 100644 index 0000000000..9e06d74bed --- /dev/null +++ b/policyengine_uk/tests/policy/baseline/gov/treasury/energy_bills_rebate/alternative_fuel_payment.yaml @@ -0,0 +1,108 @@ +# Northern Ireland's £600 Energy Bills Support Scheme payment combined £400 +# of support with a £200 Alternative Fuel Payment (EBSS AFP NI Direction, +# 23 December 2022, paragraph 3 and footnote 1). Each domestic electricity +# customer at 08:00 on 2 January 2023, the qualifying date, qualified, and +# the main rollout began on 16 January 2023 (GOV.UK announcement that day). +# The model pays it in January 2023, on the household's nation that year. +# Great Britain's Alternative Fuel Payment is not modelled. + +- name: Case 1, Northern Ireland receives the payment in January 2023. + period: 2023 + input: + country: NORTHERN_IRELAND + output: + monthly_ebr_alternative_fuel_payment: + 2023-01: 200 + 2023-02: 0 + 2023-12: 0 + ebr_alternative_fuel_payment: 200 + +- name: Case 2, Northern Ireland receives nothing in 2022. + period: 2022 + input: + country: NORTHERN_IRELAND + output: + monthly_ebr_alternative_fuel_payment: + 2022-10: 0 + 2022-12: 0 + ebr_alternative_fuel_payment: 0 + +- name: Case 3, Great Britain households receive no modelled payment. + period: 2023 + input: + country: + - ENGLAND + - SCOTLAND + - WALES + output: + monthly_ebr_alternative_fuel_payment: + 2023-01: + - 0 + - 0 + - 0 + ebr_alternative_fuel_payment: + - 0 + - 0 + - 0 + +- name: Case 4, a household without a qualifying electricity supply is ineligible. + period: 2023 + input: + country: NORTHERN_IRELAND + ebr_energy_bills_credit_eligible: + 2023-01: false + output: + monthly_ebr_alternative_fuel_payment: + 2023-01: 0 + ebr_alternative_fuel_payment: 0 + ebr_energy_bills_credit: 0 + +- name: Case 5, the payment has ended in 2024. + period: 2024 + input: + country: NORTHERN_IRELAND + output: + ebr_alternative_fuel_payment: 0 + +- name: Case 6, Northern Ireland's full 2023 rebate is the direction's £600. + period: 2023 + input: + country: NORTHERN_IRELAND + output: + ebr_energy_bills_credit: 400 + ebr_alternative_fuel_payment: 200 + energy_bills_rebate: 600 + +- name: Case 7, a household that moves to Northern Ireland in 2023 receives the payment. + period: 2023 + input: + country: + 2022: ENGLAND + 2023: NORTHERN_IRELAND + output: + monthly_ebr_alternative_fuel_payment: + 2023-01: 200 + ebr_alternative_fuel_payment: 200 + energy_bills_rebate: 600 + +- name: Case 8, a household that leaves Northern Ireland before 2023 receives no payment. + period: 2023 + input: + country: + 2022: NORTHERN_IRELAND + 2023: ENGLAND + output: + monthly_ebr_alternative_fuel_payment: + 2023-01: 0 + ebr_alternative_fuel_payment: 0 + energy_bills_rebate: 201 + +- name: Case 9, a Northern Ireland input for 2022 alone leaves 2023 in England, the default. + period: 2023 + input: + country: + 2022: NORTHERN_IRELAND + output: + country: ENGLAND + ebr_alternative_fuel_payment: 0 + energy_bills_rebate: 201 diff --git a/policyengine_uk/tests/policy/baseline/gov/treasury/energy_bills_rebate/council_tax_rebate.yaml b/policyengine_uk/tests/policy/baseline/gov/treasury/energy_bills_rebate/council_tax_rebate.yaml index ba7fceee11..a4cd8dc68a 100644 --- a/policyengine_uk/tests/policy/baseline/gov/treasury/energy_bills_rebate/council_tax_rebate.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/treasury/energy_bills_rebate/council_tax_rebate.yaml @@ -1,5 +1,5 @@ - name: CT bands A-D get full rebate, others don't. - period: 2023 + period: 2022 input: council_tax: - 150 @@ -32,3 +32,113 @@ - 0 - 0 - 0 + +# The rebate was a one-off in 2022-23: the guidance set 30 September 2022 as +# the deadline for the payments the government would reconcile (billing +# authority guidance, paragraph 7). The model pays nothing in 2023-24 or +# before 2022-23. +- name: No rebate in 2023-24, in any band. + period: 2023 + input: + council_tax_band: + - A + - D + - E + output: + ebr_council_tax_rebate: + - 0 + - 0 + - 0 + +- name: No rebate in 2021-22. + period: 2021 + input: + council_tax_band: A + output: + ebr_council_tax_rebate: 0 + +# England's Council Tax Rebate applied to England only (billing authority +# guidance, paragraph 2); Scotland and Wales paid their own £150 awards to +# bands A to D. Northern Ireland levies domestic rates, not council tax, and +# had no Council Tax Rebate, so a Northern Ireland household gets none in any +# band, including the default band D. +- name: Each Great Britain nation pays the rebate in bands A to D. + period: 2022 + input: + council_tax_band: + - A + - D + - A + - D + - A + - D + country: + - ENGLAND + - ENGLAND + - SCOTLAND + - SCOTLAND + - WALES + - WALES + output: + ebr_council_tax_rebate: + - 150 + - 150 + - 150 + - 150 + - 150 + - 150 + +- name: Northern Ireland households get no rebate, in any band. + period: 2022 + input: + council_tax_band: + - A + - B + - C + - D + - E + - I + country: + - NORTHERN_IRELAND + - NORTHERN_IRELAND + - NORTHERN_IRELAND + - NORTHERN_IRELAND + - NORTHERN_IRELAND + - NORTHERN_IRELAND + output: + ebr_council_tax_rebate: + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + +- name: A Northern Ireland household on the default band gets no rebate. + period: 2022 + input: + country: NORTHERN_IRELAND + output: + council_tax_band: D + ebr_council_tax_rebate: 0 + +# The rebate follows the household's nation in 2022, whatever it is in 2023. +- name: A household in England in 2022 gets the rebate, though it moves to Northern Ireland in 2023. + period: 2022 + input: + council_tax_band: A + country: + 2022: ENGLAND + 2023: NORTHERN_IRELAND + output: + ebr_council_tax_rebate: 150 + +- name: A household in Northern Ireland in 2022 gets no rebate, though it moves to England in 2023. + period: 2022 + input: + council_tax_band: A + country: + 2022: NORTHERN_IRELAND + 2023: ENGLAND + output: + ebr_council_tax_rebate: 0 diff --git a/policyengine_uk/tests/policy/baseline/gov/treasury/energy_bills_rebate/energy_bills_rebate.yaml b/policyengine_uk/tests/policy/baseline/gov/treasury/energy_bills_rebate/energy_bills_rebate.yaml index 257afcb44c..e4f40c9cfa 100644 --- a/policyengine_uk/tests/policy/baseline/gov/treasury/energy_bills_rebate/energy_bills_rebate.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/treasury/energy_bills_rebate/energy_bills_rebate.yaml @@ -12,3 +12,118 @@ council_tax_band: G output: energy_bills_rebate: 199 + +# HBAI income. DWP's HBAI classifies the Energy Bills Support Scheme (£600 in +# Northern Ireland) and the £150 Council Tax Rebate as miscellaneous income +# (HBAI quality and methodology report FYE 2023, "Treatment of cost of living +# and wider support schemes"), so both reach HBAI household net income. +# Each household below has no other income, does not claim Universal Credit +# and has no council tax to pay, so its HBAI income is the rebate alone. The +# Great Britain credit is £66 in October and November 2022 and £67 a month +# from December 2022 to March 2023, summed by calendar year (£199 and £201). +# DWP's HBAI counts all £400 in 2022-23; the model keeps the calendar-year +# sum, as in household net income (docs: cost-of-living-payments.md). + +- name: Case 3, a band C household's 2022 rebates are HBAI income. + period: 2022 + input: + people: + adult: + age: 40 + benunits: + unit: + members: [adult] + would_claim_uc: false + households: + household: + members: [adult] + council_tax_band: C + council_tax: 0 + output: + ebr_council_tax_rebate: 150 + ebr_energy_bills_credit: 199 + hbai_household_net_income: 150 + 199 + +- name: Case 4, in 2023 only the credit's remaining instalments are paid, and counted. + period: 2023 + input: + people: + adult: + age: 40 + benunits: + unit: + members: [adult] + would_claim_uc: false + households: + household: + members: [adult] + council_tax_band: C + council_tax: 0 + output: + ebr_council_tax_rebate: 0 + energy_bills_rebate: 201 + hbai_household_net_income: 201 + +- name: Case 5, Northern Ireland's January 2023 £600 payment is HBAI income. + period: 2023 + input: + people: + adult: + age: 40 + benunits: + unit: + members: [adult] + would_claim_uc: false + households: + household: + members: [adult] + country: NORTHERN_IRELAND + council_tax: 0 + output: + ebr_energy_bills_credit: 400 + ebr_alternative_fuel_payment: 200 + energy_bills_rebate: 600 + hbai_household_net_income: 600 + +- name: Case 6, the rebates are miscellaneous income, not HBAI state support. + period: 2022 + input: + people: + adult: + age: 40 + benunits: + unit: + members: [adult] + would_claim_uc: false + households: + household: + members: [adult] + council_tax_band: A + council_tax: 0 + output: + energy_bills_rebate: 349 + hbai_benefits: 0 + +# Northern Ireland had no Council Tax Rebate, and its £600 payment came in +# January 2023, so a Northern Ireland household in band A (the band the +# enhanced FRS gives every Northern Ireland household) has no rebate in 2022. +- name: Case 7, a Northern Ireland household has no 2022 rebate in HBAI income. + period: 2022 + input: + people: + adult: + age: 40 + benunits: + unit: + members: [adult] + would_claim_uc: false + households: + household: + members: [adult] + country: NORTHERN_IRELAND + council_tax_band: A + council_tax: 0 + output: + ebr_council_tax_rebate: 0 + energy_bills_rebate: 0 + hbai_household_net_income: 0 diff --git a/policyengine_uk/tests/test_energy_bills_rebate_hbai_properties.py b/policyengine_uk/tests/test_energy_bills_rebate_hbai_properties.py new file mode 100644 index 0000000000..7d43f612c3 --- /dev/null +++ b/policyengine_uk/tests/test_energy_bills_rebate_hbai_properties.py @@ -0,0 +1,155 @@ +"""Property tests for the 2022 Energy Bills Rebate in HBAI income. + +DWP's HBAI classifies the Energy Bills Support Scheme (£600 in Northern +Ireland) and the £150 Council Tax Rebate as miscellaneous income, not state +support (HBAI quality and methodology report FYE 2023, "Treatment of cost of +living and wider support schemes in the HBAI income estimates for FYE +2023"). The Council Tax Rebate was a one-off: billing authorities made every +payment by 30 September 2022 (Council Tax Rebate 2022-23 billing authority +guidance, paragraph 7). + +Invariants, each checked over many generated households in one reusable +simulation: + +1. HBAI household net income before the reset to zero rises by exactly the + household's energy_bills_rebate, in every year, nation and band. +2. The rebate is miscellaneous income: hbai_benefits does not change with it. +3. The Council Tax Rebate is paid once: in 2022 only, at £150, and only in + bands A to D outside Northern Ireland, which has domestic rates and had no + Council Tax Rebate. +""" + +import numpy as np +import pytest +from hypothesis import HealthCheck, given, settings +from hypothesis import strategies as st + +from policyengine_uk import Simulation +from policyengine_uk.variables.household.income.hbai_household_net_income import ( + HBAI_HOUSEHOLD_NET_INCOME_ADDS, +) + +pytestmark = pytest.mark.usefixtures("cloned_uk_tax_benefit_system") + +YEARS = [2021, 2022, 2023, 2024] +BANDS = ["A", "B", "C", "D", "E", "F", "G", "H", "I"] +COUNTRIES = ["ENGLAND", "SCOTLAND", "WALES", "NORTHERN_IRELAND"] +HOUSEHOLDS = 16 + +household = st.tuples( + st.sampled_from(BANDS), + st.sampled_from(COUNTRIES), + st.sampled_from([0.0, 6_000.0, 18_000.0, 45_000.0]), # employment income + st.booleans(), # claims Universal Credit + st.lists(st.booleans(), min_size=12, max_size=12), # EBSS months eligible +) +households = st.lists(household, min_size=HOUSEHOLDS, max_size=HOUSEHOLDS) + + +def simulation(drawn, years, without_rebate=False): + """One household per drawn case, with the same circumstances in each of + ``years``.""" + bands, countries, pay, claims_uc, months = zip(*drawn) + + def each_year(value): + return {year: value for year in years} + + people = { + f"adult_{i}": {"age": each_year(40), "employment_income": each_year(pay[i])} + for i in range(HOUSEHOLDS) + } + benunits = { + f"unit_{i}": { + "members": [f"adult_{i}"], + "would_claim_uc": each_year(claims_uc[i]), + } + for i in range(HOUSEHOLDS) + } + households = { + f"household_{i}": { + "members": [f"adult_{i}"], + "council_tax_band": each_year(bands[i]), + "country": each_year(countries[i]), + "ebr_energy_bills_credit_eligible": { + f"{year}-{month:02d}": months[i][month - 1] + for year in years + for month in range(1, 13) + }, + } + for i in range(HOUSEHOLDS) + } + if without_rebate: + for values in households.values(): + values["energy_bills_rebate"] = each_year(0) + return Simulation( + situation={"people": people, "benunits": benunits, "households": households} + ) + + +SETTINGS = settings( + max_examples=10, + deadline=None, + suppress_health_check=[HealthCheck.too_slow], +) + + +@SETTINGS +@given(households, st.sampled_from(YEARS)) +def test_hbai_income_rises_by_exactly_the_rebate(drawn, year): + with_rebate = simulation(drawn, [year]) + without = simulation(drawn, [year], without_rebate=True) + rebate = with_rebate.calculate("energy_bills_rebate", year) + gain = with_rebate.calculate( + "hbai_household_net_income_before_reset_to_zero", year + ) - without.calculate("hbai_household_net_income_before_reset_to_zero", year) + np.testing.assert_allclose(gain, rebate, atol=0.01) + np.testing.assert_allclose( + with_rebate.calculate("hbai_benefits", year), + without.calculate("hbai_benefits", year), + atol=0.01, + ) + + +@SETTINGS +@given(households) +def test_the_council_tax_rebate_is_paid_once(drawn): + bands = np.array([band for band, *_ in drawn]) + countries = np.array([country for _, country, *_ in drawn]) + years = range(2015, 2031) + sim = simulation(drawn, years) + total = np.zeros(HOUSEHOLDS) + for year in years: + rebate = sim.calculate("ebr_council_tax_rebate", year) + if year == 2022: + np.testing.assert_array_equal( + rebate, + np.where( + np.isin(bands, ["A", "B", "C", "D"]) + & (countries != "NORTHERN_IRELAND"), + 150, + 0, + ), + ) + else: + np.testing.assert_array_equal(rebate, 0) + total += rebate + assert (total <= 150).all() + + +def test_the_rebate_is_counted_once_and_not_as_state_support(): + """HBAI adds energy_bills_rebate once, and not its parts as well; it and + the Warm Home Discount are miscellaneous income, outside hbai_benefits.""" + from policyengine_uk import CountryTaxBenefitSystem + + system = CountryTaxBenefitSystem() + parts = system.variables["energy_bills_rebate"].adds + assert set(parts) == { + "ebr_council_tax_rebate", + "ebr_energy_bills_credit", + "ebr_alternative_fuel_payment", + } + assert HBAI_HOUSEHOLD_NET_INCOME_ADDS.count("energy_bills_rebate") == 1 + assert not set(parts) & set(HBAI_HOUSEHOLD_NET_INCOME_ADDS) + state_support = system.variables["hbai_benefits"].adds + for miscellaneous_income in ["energy_bills_rebate", "warm_home_discount"]: + assert miscellaneous_income not in state_support diff --git a/policyengine_uk/tests/test_energy_bills_rebate_northern_ireland_properties.py b/policyengine_uk/tests/test_energy_bills_rebate_northern_ireland_properties.py new file mode 100644 index 0000000000..732f7af06e --- /dev/null +++ b/policyengine_uk/tests/test_energy_bills_rebate_northern_ireland_properties.py @@ -0,0 +1,169 @@ +"""Property tests for the Energy Bills Rebate in Northern Ireland. + +England's £150 Council Tax Rebate applied to England only (billing authority +guidance, paragraph 2), and Scotland and Wales paid their own £150 awards. +Northern Ireland levies domestic rates, not council tax, and had no Council +Tax Rebate. Its households received one £600 payment instead: £400 of Energy +Bills Support Scheme support and a £200 Alternative Fuel Payment, paid to each +domestic electricity customer on 2 January 2023 (EBSS AFP NI Direction, +23 December 2022, paragraph 3 and footnote 1). + +Invariants, each checked over many generated households in one simulation +covering 2015 to 2030. Some households stay in one nation; others draw a +nation for each year, so they move between 2022 and 2023: + +1. No household receives the Council Tax Rebate in a year it lives in + Northern Ireland, in any band. +2. The Alternative Fuel Payment is £200, paid once, in January 2023, to each + household in Northern Ireland in 2023 with a qualifying electricity supply + that month, and never to a household in Great Britain in 2023. +3. A household in Northern Ireland in 2022 receives no rebate that year. One + in Northern Ireland in 2023 receives exactly the direction's £600 that year + if it had a qualifying electricity supply in January 2023, and nothing + otherwise. So a household in Northern Ireland in both years receives £600 + over the whole scheme. +4. In every year, energy_bills_rebate is the sum of its three parts, and + none is negative. +""" + +import numpy as np +import pytest +from hypothesis import HealthCheck, given, settings +from hypothesis import strategies as st + +from policyengine_uk import Simulation + +pytestmark = pytest.mark.usefixtures("cloned_uk_tax_benefit_system") + +YEARS = list(range(2015, 2031)) +BANDS = ["A", "B", "C", "D", "E", "F", "G", "H", "I"] +COUNTRIES = ["ENGLAND", "SCOTLAND", "WALES", "NORTHERN_IRELAND"] +HOUSEHOLDS = 16 +PARTS = [ + "ebr_council_tax_rebate", + "ebr_energy_bills_credit", + "ebr_alternative_fuel_payment", +] + +# A household's nation in each year: either the same nation throughout or an +# independent draw for each year. +yearly_countries = st.one_of( + st.sampled_from(COUNTRIES).map(lambda country: [country] * len(YEARS)), + st.lists(st.sampled_from(COUNTRIES), min_size=len(YEARS), max_size=len(YEARS)), +) +household = st.tuples( + st.sampled_from(BANDS), + yearly_countries, + # Whether the household has a qualifying electricity supply in each month + # of 2022 and 2023. + st.lists(st.booleans(), min_size=24, max_size=24), +) +households = st.lists(household, min_size=HOUSEHOLDS, max_size=HOUSEHOLDS) + + +def in_northern_ireland(drawn, year): + return ( + np.array([countries[YEARS.index(year)] for _, countries, _ in drawn]) + == "NORTHERN_IRELAND" + ) + + +def eligible_in_january_2023(drawn): + return np.array([months[12] for _, _, months in drawn]) + + +def simulation(drawn): + """One household per drawn case, in the same band each year.""" + bands, countries, months = zip(*drawn) + + def each_year(value): + return {year: value for year in YEARS} + + people = {f"adult_{i}": {"age": each_year(40)} for i in range(HOUSEHOLDS)} + benunits = { + f"unit_{i}": {"members": [f"adult_{i}"], "would_claim_uc": each_year(False)} + for i in range(HOUSEHOLDS) + } + households = { + f"household_{i}": { + "members": [f"adult_{i}"], + "council_tax_band": each_year(bands[i]), + "country": dict(zip(YEARS, countries[i])), + "ebr_energy_bills_credit_eligible": { + f"{year}-{month:02d}": months[i][12 * (year - 2022) + month - 1] + for year in [2022, 2023] + for month in range(1, 13) + }, + } + for i in range(HOUSEHOLDS) + } + return Simulation( + situation={"people": people, "benunits": benunits, "households": households} + ) + + +SETTINGS = settings( + max_examples=10, + deadline=None, + suppress_health_check=[HealthCheck.too_slow], +) + + +@SETTINGS +@given(households) +def test_northern_ireland_never_gets_the_council_tax_rebate(drawn): + sim = simulation(drawn) + for year in YEARS: + rebate = sim.calculate("ebr_council_tax_rebate", year) + np.testing.assert_array_equal(rebate[in_northern_ireland(drawn, year)], 0) + + +@SETTINGS +@given(households) +def test_the_alternative_fuel_payment_is_paid_once_in_northern_ireland(drawn): + northern_ireland = in_northern_ireland(drawn, 2023) + eligible = eligible_in_january_2023(drawn) + sim = simulation(drawn) + for year in YEARS: + for month in range(1, 13): + payment = sim.calculate( + "monthly_ebr_alternative_fuel_payment", f"{year}-{month:02d}" + ) + if (year, month) == (2023, 1): + expected = np.where(northern_ireland & eligible, 200, 0) + np.testing.assert_array_equal(payment, expected) + else: + np.testing.assert_array_equal(payment, 0) + + +@SETTINGS +@given(households) +def test_northern_ireland_receives_the_directions_600(drawn): + in_2022 = in_northern_ireland(drawn, 2022) + in_2023 = in_northern_ireland(drawn, 2023) + expected = np.where(eligible_in_january_2023(drawn), 600, 0) + sim = simulation(drawn) + np.testing.assert_array_equal( + sim.calculate("energy_bills_rebate", 2022)[in_2022], 0 + ) + np.testing.assert_allclose( + sim.calculate("energy_bills_rebate", 2023)[in_2023], + expected[in_2023], + atol=0.01, + ) + total = sum(sim.calculate("energy_bills_rebate", year) for year in YEARS) + stayed = in_2022 & in_2023 + np.testing.assert_allclose(total[stayed], expected[stayed], atol=0.01) + + +@SETTINGS +@given(households) +def test_the_rebate_is_the_sum_of_its_parts(drawn): + sim = simulation(drawn) + for year in YEARS: + parts = [sim.calculate(part, year) for part in PARTS] + for part in parts: + assert (part >= 0).all() + np.testing.assert_allclose( + sim.calculate("energy_bills_rebate", year), sum(parts), atol=0.01 + ) diff --git a/policyengine_uk/tests/test_trading_allowance_miscellaneous_income_properties.py b/policyengine_uk/tests/test_trading_allowance_miscellaneous_income_properties.py new file mode 100644 index 0000000000..eef1f1c8b9 --- /dev/null +++ b/policyengine_uk/tests/test_trading_allowance_miscellaneous_income_properties.py @@ -0,0 +1,384 @@ +"""Property tests for the trading allowance on miscellaneous income +(ITTOIA 2005 Part 6A Chapter 1). + +The allowance relieves the receipts of a person's relevant trades and their +"miscellaneous income", income chargeable under Part 5 Chapter 8 (income not +otherwise charged), before expenses (ss. 783A, 783AB and 783AC). One +allowance covers both. Within it, both are relieved in full (ss. 783AF and +783AG); above it, the person can elect to charge each source less a share of +the allowance, the shares adding up to the allowance, in place of actual +expenses (ss. 783AI-783AK). income_not_otherwise_charged is the part of +miscellaneous_income that is Chapter 8 income. + +Invariants, each checked over many generated people in one reusable +simulation: + +1. With trade receipts known (or a trade with a loss or no profit, taken + to have none), taxable trade profit plus taxable miscellaneous income + equals the figure the Act gives under the model's split of the allowance, + trades first: the lower of ordinary treatment and the allowance. It is + never below the lowest figure any split the Act allows gives, and equals + it when no trade losses are brought forward (differential against a + reference written from the Act). +2. One allowance: the deductions from trade profits and from miscellaneous + income never add up to more than the allowance. +3. Bounds: the miscellaneous deduction is between nil and the Chapter 8 + income, which counts up to positive miscellaneous income; taxable + miscellaneous income is between nil and miscellaneous income. +4. The allowance's relief on trade profits does not depend on Chapter 8 + income when no current trading loss is supplied: the miscellaneous income + gets only what the trade receipts leave. +5. With no Chapter 8 income nothing changes: no deduction, and taxable + miscellaneous income is miscellaneous income with a loss counted as nil. +6. Means tests read the income before the allowance, whatever part of it is + Chapter 8 income. +7. Income tax never rises as more of a person's miscellaneous income is + Chapter 8 income. +8. Parity (differential): without pension contributions, Chapter 8 income + of X, with no trade, is charged the same income tax as trade receipts of + X with no expenses. +9. A current trading loss is never both claimed and given up: when the + allowance gives either relief, no current loss is relieved against + general income or Class 4, and a loss above anything the allowance could + save is claimed. +""" + +import numpy as np +import pytest +from hypothesis import HealthCheck, given, settings +from hypothesis import strategies as st + +from policyengine_uk import Simulation + +pytestmark = pytest.mark.usefixtures("cloned_uk_tax_benefit_system") + +YEAR = 2026 +OTHER_EMPLOYMENT_INCOME = [0, 8_000, 30_000, 60_000, 110_000, 200_000] +CASES = 24 +# Each case is simulated three times: as drawn, with no Chapter 8 income, and +# with all its positive miscellaneous income Chapter 8 income. +PEOPLE = 3 * CASES + +amount = st.one_of( + st.floats(min_value=0, max_value=3_500), + st.floats(min_value=0, max_value=60_000), + st.sampled_from([0.0, 400.0, 999.99, 1_000.0, 1_000.01, 3_000.0, 12_570.0]), +) +case = st.tuples( + st.one_of(amount, st.floats(min_value=-3_000, max_value=0)), # trade profit + amount, # actual expenses + st.sampled_from([0.0, 0.0, 150.0, 900.0]), # capital allowances + st.sampled_from(["unknown", "consistent", "none"]), # receipts + st.one_of(amount, st.floats(min_value=-2_000, max_value=0)), # misc income + st.floats(min_value=0, max_value=1.5), # Chapter 8 share of misc income + st.sampled_from([0.0, 0.0, 0.0, 300.0, 5_000.0]), # current trading loss + st.sampled_from([0.0, 0.0, 0.0, 300.0, 1_500.0, 20_000.0]), # losses b/f +) +cases = st.lists(case, min_size=CASES, max_size=CASES) + + +@pytest.fixture(scope="module") +def sim(): + people = { + f"person_{i}": { + "age": {YEAR: 40}, + "employment_income": { + YEAR: OTHER_EMPLOYMENT_INCOME[i % CASES % len(OTHER_EMPLOYMENT_INCOME)] + }, + "self_employment_income": {YEAR: 0}, + "self_employment_gross_receipts": {YEAR: 0}, + "capital_allowances": {YEAR: 0}, + "miscellaneous_income": {YEAR: 0}, + "income_not_otherwise_charged": {YEAR: 0}, + "trading_loss": {YEAR: 0}, + "loss_relief": {YEAR: 0}, + } + for i in range(PEOPLE) + } + return Simulation( + situation={ + "people": people, + "benunits": { + f"benunit_{i}": {"members": [f"person_{i}"]} for i in range(PEOPLE) + }, + "households": { + f"household_{i}": {"members": [f"person_{i}"]} for i in range(PEOPLE) + }, + } + ) + + +OUTPUTS = [ + "trading_allowance_deduction", + "miscellaneous_income_trading_allowance_deduction", + "taxable_self_employment_income", + "taxable_miscellaneous_income", + "miscellaneous_income_before_trading_allowance", + "uses_trading_allowance", + "trade_loss_relief_against_general_income", + "ni_class_4_trading_loss", + "income_tax", +] + + +def run(sim, drawn, trade=True): + """Simulate the drawn cases three times over (see PEOPLE). With + ``trade`` false, the trade is dropped.""" + profit, expenses, capital_allowances, mode, misc, share, loss, brought_forward = ( + map(np.array, zip(*drawn)) + ) + if not trade: + profit = np.zeros_like(profit) + expenses = np.zeros_like(expenses) + capital_allowances = np.zeros_like(capital_allowances) + brought_forward = np.zeros_like(brought_forward) + mode = np.full_like(mode, "none") + chapter_8 = np.concatenate( + [share * np.maximum(misc, 0), np.zeros_like(misc), np.maximum(misc, 0)] + ) + profit, expenses, capital_allowances, mode, misc, loss, brought_forward = ( + np.tile(x, 3) + for x in ( + profit, + expenses, + capital_allowances, + mode, + misc, + loss, + brought_forward, + ) + ) + profit = np.where(mode == "none", 0, profit) + receipts = np.where(mode == "consistent", np.maximum(profit + expenses, 0), 0) + sim.set_input("self_employment_income", YEAR, profit) + sim.set_input("self_employment_gross_receipts", YEAR, receipts) + sim.set_input("capital_allowances", YEAR, capital_allowances) + sim.set_input("miscellaneous_income", YEAR, misc) + sim.set_input("income_not_otherwise_charged", YEAR, chapter_8) + sim.set_input("trading_loss", YEAR, loss) + sim.set_input("loss_relief", YEAR, brought_forward) + sim.reset_calculations() + + def stored(name): + return sim.calculate(name, YEAR).astype(float) + + # Compare against the stored (float32) inputs, not the float64 draws. + r = dict( + profit=stored("self_employment_income"), + receipts=stored("self_employment_gross_receipts"), + capital_allowances=stored("capital_allowances"), + misc=stored("miscellaneous_income"), + chapter_8=stored("income_not_otherwise_charged"), + loss=stored("trading_loss"), + brought_forward=stored("loss_relief"), + allowance=float( + sim.tax_benefit_system.parameters( + YEAR + ).gov.hmrc.income_tax.allowances.trading_allowance + ), + ) + r["known"] = (r["receipts"] > 0) & (r["receipts"] >= r["profit"]) + r["expenses"] = np.maximum(r["receipts"] - r["profit"], 0) + for name in OUTPUTS: + r[name] = sim.calculate(name, YEAR) + return r + + +def part(r, name, i): + """The ith of the three copies of each case.""" + return r[name][i * CASES : (i + 1) * CASES] + + +def tolerance(*values): + """A penny plus float32 rounding on the amounts involved.""" + return 0.01 + 1e-6 * sum(np.abs(v) for v in values) + + +def statutory_taxable_income(r): + """Taxable trade profit plus taxable miscellaneous income under Part 6A, + with trade receipts known (or a trade with a loss or no profit, taken to + have none) and no current trading loss, as ``(trades_first, lowest)``. + + Ordinary treatment charges the profit after capital allowances and + losses brought forward (nil if they exceed it) and all positive + miscellaneous income. Relevant income (receipts plus Chapter 8 income) + within the allowance is not charged (ss. 783AF and 783AG). Above it, + partial relief charges each trade's receipts less its share of the + allowance, less losses brought forward (ITA 2007 s. 83), and the Chapter + 8 income less the rest of the allowance (ss. 783AI-783AK). The trade's + share is at most its receipts and the allowance, and at least what the + Chapter 8 income cannot take (s. 783AK(4) and (5)). Miscellaneous income + outside Chapter 8 is charged either way. The person takes the lower of + ordinary treatment and the allowance, with the trade's share either as + large as it can be (the model's split) or whatever charges least. + """ + allowance = r["allowance"] + misc = np.maximum(r["misc"], 0) + chapter_8 = np.minimum(np.maximum(r["chapter_8"], 0), misc) + other = misc - chapter_8 + receipts = np.where(r["known"], r["receipts"], 0) + losses = r["brought_forward"] + ordinary = np.maximum(r["profit"] - r["capital_allowances"] - losses, 0) + misc + + def partial_relief(trade_share): + trade = np.maximum(receipts - trade_share - losses, 0) + return trade + chapter_8 - (allowance - trade_share) + other + + # The charge is piecewise linear in the trade's share, so its least value + # is at an end or where losses brought forward stop absorbing the trade. + smallest = np.maximum(allowance - chapter_8, 0) + largest = np.minimum(receipts, allowance) + kink = np.clip(receipts - losses, smallest, largest) + within = receipts + chapter_8 <= allowance + trades_first = np.where(within, other, partial_relief(largest)) + lowest = np.where( + within, + other, + np.minimum.reduce( + [partial_relief(share) for share in (smallest, largest, kink)] + ), + ) + return np.minimum(ordinary, trades_first), np.minimum(ordinary, lowest) + + +SETTINGS = settings( + max_examples=30, + deadline=None, + suppress_health_check=[HealthCheck.too_slow], +) + + +@SETTINGS +@given(cases) +def test_taxable_income_matches_statutory_reference(sim, drawn): + r = run(sim, drawn) + # Known receipts, or none (no trade, or a loss), and no current loss. + applies = (r["known"] | (r["profit"] <= 0)) & (r["loss"] == 0) + taxable = r["taxable_self_employment_income"] + r["taxable_miscellaneous_income"] + trades_first, lowest = statutory_taxable_income(r) + margin = tolerance( + r["receipts"], r["profit"], r["misc"], r["chapter_8"], r["brought_forward"] + ) + assert (np.abs(taxable - trades_first)[applies] <= margin[applies]).all() + # Never more relief than the Act allows, and all of it without losses + # brought forward, which only a smaller trade share can put to use. + assert (taxable[applies] >= lowest[applies] - margin[applies]).all() + no_losses = applies & (r["brought_forward"] == 0) + assert (np.abs(taxable - lowest)[no_losses] <= margin[no_losses]).all() + + +@SETTINGS +@given(cases) +def test_one_allowance_covers_both(sim, drawn): + r = run(sim, drawn) + total = ( + r["trading_allowance_deduction"] + + r["miscellaneous_income_trading_allowance_deduction"] + ) + assert (total <= r["allowance"] + tolerance(r["allowance"])).all() + + +@SETTINGS +@given(cases) +def test_deduction_and_taxable_income_are_bounded(sim, drawn): + r = run(sim, drawn) + deduction = r["miscellaneous_income_trading_allowance_deduction"] + eligible = np.minimum(np.maximum(r["chapter_8"], 0), np.maximum(r["misc"], 0)) + assert (deduction >= 0).all() + assert (deduction <= eligible + tolerance(eligible)).all() + taxable = r["taxable_miscellaneous_income"] + assert (taxable >= -tolerance(r["misc"])).all() + assert (taxable <= np.maximum(r["misc"], 0) + tolerance(r["misc"])).all() + + +@SETTINGS +@given(cases) +def test_trade_relief_does_not_depend_on_chapter_8_income(sim, drawn): + r = run(sim, drawn) + no_loss = part(r, "loss", 0) == 0 + drawn_share = part(r, "trading_allowance_deduction", 0) + none = part(r, "trading_allowance_deduction", 1) + all_of_it = part(r, "trading_allowance_deduction", 2) + assert np.allclose(drawn_share[no_loss], none[no_loss], atol=0.01) + assert np.allclose(all_of_it[no_loss], none[no_loss], atol=0.01) + + +@SETTINGS +@given(cases) +def test_no_chapter_8_income_changes_nothing(sim, drawn): + r = run(sim, drawn) + misc = part(r, "misc", 1) + np.testing.assert_array_equal( + part(r, "miscellaneous_income_trading_allowance_deduction", 1), 0 + ) + np.testing.assert_allclose( + part(r, "taxable_miscellaneous_income", 1), + np.maximum(misc, 0), + atol=0.01, + ) + + +@SETTINGS +@given(cases) +def test_means_tests_read_income_before_the_allowance(sim, drawn): + r = run(sim, drawn) + np.testing.assert_allclose( + r["miscellaneous_income_before_trading_allowance"], + np.maximum(r["misc"], 0), + atol=0.01, + ) + + +@SETTINGS +@given(cases) +def test_income_tax_never_rises_with_more_chapter_8_income(sim, drawn): + r = run(sim, drawn) + # Without a current loss, whose opt-out weighs raw amounts, not tax. + no_loss = part(r, "loss", 0) == 0 + none, drawn_share, all_of_it = (part(r, "income_tax", i) for i in (1, 0, 2)) + # The drawn share can exceed one, so order the two by the Chapter 8 + # income each copy actually has. + more_than_all = part(r, "chapter_8", 0) > part(r, "chapter_8", 2) + assert (drawn_share[no_loss] <= none[no_loss] + tolerance(none[no_loss])).all() + lower = np.where(more_than_all, all_of_it, drawn_share) + higher = np.where(more_than_all, drawn_share, all_of_it) + assert (lower[no_loss] >= higher[no_loss] - tolerance(lower[no_loss])).all() + + +@SETTINGS +@given(st.lists(amount, min_size=CASES, max_size=CASES)) +def test_chapter_8_income_is_taxed_like_trade_receipts(sim, amounts): + amounts = np.array(amounts) + zeros = np.zeros(CASES) + # Copy 0: Chapter 8 income of X. Copy 1: receipts and profit of X. + misc = np.concatenate([amounts, zeros, zeros]) + profit = np.concatenate([zeros, amounts, zeros]) + sim.set_input("self_employment_income", YEAR, profit) + sim.set_input("self_employment_gross_receipts", YEAR, profit) + sim.set_input("capital_allowances", YEAR, np.zeros(PEOPLE)) + sim.set_input("miscellaneous_income", YEAR, misc) + sim.set_input("income_not_otherwise_charged", YEAR, misc) + sim.set_input("trading_loss", YEAR, np.zeros(PEOPLE)) + sim.set_input("loss_relief", YEAR, np.zeros(PEOPLE)) + sim.reset_calculations() + tax = sim.calculate("income_tax", YEAR) + np.testing.assert_allclose( + tax[:CASES], tax[CASES : 2 * CASES], atol=float(tolerance(amounts).max()) + ) + + +@SETTINGS +@given(cases) +def test_a_current_loss_is_never_both_claimed_and_given_up(sim, drawn): + for trade in (True, False): + r = run(sim, drawn, trade=trade) + # Either relief, not only the trade's, means the allowance was used. + used = (r["trading_allowance_deduction"] > 0) | ( + r["miscellaneous_income_trading_allowance_deduction"] > 0 + ) + assert (r["trade_loss_relief_against_general_income"][used] == 0).all() + assert (r["ni_class_4_trading_loss"][used] == 0).all() + # A loss larger than everything the allowance could save is claimed. + saving_bound = r["allowance"] + tolerance(r["allowance"]) + assert not used[r["loss"] > saving_bound].any() + np.testing.assert_array_equal(r["uses_trading_allowance"], used) diff --git a/policyengine_uk/tests/test_wales_childcare_offer_income_sources.py b/policyengine_uk/tests/test_wales_childcare_offer_income_sources.py index 3508c8ffed..f083cb4b4f 100644 --- a/policyengine_uk/tests/test_wales_childcare_offer_income_sources.py +++ b/policyengine_uk/tests/test_wales_childcare_offer_income_sources.py @@ -83,7 +83,7 @@ def sources(name): "savings_interest_income", "other_investment_income", "dividend_income", - "taxable_miscellaneous_income", + "miscellaneous_income_before_trading_allowance", "maintenance_income", "spousal_maintenance_income", }, diff --git a/policyengine_uk/utils/pensions.py b/policyengine_uk/utils/pensions.py index be14ef3e7c..0c6a4eb202 100644 --- a/policyengine_uk/utils/pensions.py +++ b/policyengine_uk/utils/pensions.py @@ -123,7 +123,7 @@ def annual_allowance_net_income(person, period, parameters): cap = hmrc.income_tax.reliefs.cap loss_cap = np.maximum(cap.amount, cap.rate * adjusted_total) loss = np.where( - person("trading_allowance_deduction", period) > 0, + person("uses_trading_allowance", period), 0, person("trading_loss", period), ) diff --git a/policyengine_uk/utils/trading_allowance.py b/policyengine_uk/utils/trading_allowance.py new file mode 100644 index 0000000000..d16a3b54f0 --- /dev/null +++ b/policyengine_uk/utils/trading_allowance.py @@ -0,0 +1,125 @@ +"""The trading allowance (ITTOIA 2005 Part 6A Chapter 1), shared by +trading_allowance_deduction and miscellaneous_income_trading_allowance_deduction. + +One allowance covers an individual's "relevant income": the receipts of +their relevant trades and their miscellaneous income, which s. 783AB defines +as income chargeable under Part 5 Chapter 8 (income not otherwise charged), +before expenses (ss. 783A and 783AC). Full relief, partial relief and the +opt-out each apply to the relevant income as a whole, so the two deductions +come from one decision. +""" + +from policyengine_core.model_api import max_, min_, where + +from policyengine_uk.utils.supplied_inputs import supplied_input + + +def trading_allowance_reliefs(person, period, parameters): + """The allowance's relief on trades and on miscellaneous income. + + Returns ``(trade_relief, miscellaneous_relief)``: the deduction from + trading profits beyond the expenses and capital allowances already + deducted from them, and the deduction from income not otherwise charged. + + - Full relief (s. 783AE(1)): when trade receipts and miscellaneous income + together are within the allowance, trade profits or losses and the + miscellaneous income are treated as nil (ss. 783AF and 783AG). + - Partial relief (ss. 783AH-783AK, on election under s. 783AM): above the + allowance, each trade's profit is its receipts less its share of the + allowance, with no expenses, and the miscellaneous income is that + income less its share. The shares add up to the allowance and the + individual chooses them (s. 783AK). The model gives the trades their + receipts' worth first, which keeps Class 4 as low as it can be, so the + miscellaneous income gets the part of the allowance the trade receipts + leave. Where trade receipts exceed the allowance it gets none: partial + relief then beats actual deductions exactly when the trades' expenses + and capital allowances are under the allowance, and the trades take + all of it. Every split leaves the same trade and miscellaneous income + in total, relevant income less the allowance, but two other reliefs + read the trade's share alone, so a smaller trade share can charge less + income tax this year: + - losses brought forward (loss_relief, ITA 2007 s. 83) absorb the + trade profit it leaves, but that uses up loss which otherwise + carries forward against later profits of the trade (s. 84); + - pension contributions above relevant UK earnings (FA 2004 s. 189) + get more relief, since trade profit counts towards those earnings + and miscellaneous income does not. + The model keeps the trades-first split in both cases: it leaves the + loss for later years and keeps Class 4 profits lower, but it does not + always minimise the year's income tax and Class 4 together. + - Ordinary treatment: an individual whose relevant income is within the + allowance can elect out of full relief (s. 783AL), and one above it + need not elect for partial relief (s. 783AM). A raw current trading + loss larger than the saving from both deductions is claimed instead, + and then neither applies. + + Without gross receipts, a trade's receipts are taken as the lowest its + profit allows: a profit within the allowance is taken as receipts equal + to the profit, which takes up that much of the allowance, and a loss or + no profit as no receipts, which leaves all of it for miscellaneous + income. A larger profit is taken to reflect the better of actual + expenses and the allowance already, which leaves none of it. + + The miscellaneous income is income_not_otherwise_charged, up to the + person's miscellaneous_income when that is positive (it is part of it). + The model holds no expenses against it. Not modelled: the exclusions in + ss. 783AN-783AQ (rent-a-room receipts, and income including a payment + from the person's employer, firm or close company), and a loss created + by capital allowances, which the model never sets against other income. + """ + allowance = parameters(period).gov.hmrc.income_tax.allowances.trading_allowance + profit = person("self_employment_income", period) + receipts = person("self_employment_gross_receipts", period) + capital_allowances = person("capital_allowances", period) + # Receipts below profit (including the default zero) mean unknown. + receipts_known = (receipts > 0) & (receipts >= profit) + expenses = max_(receipts - profit, 0) + # Full relief (receipts within the allowance, s. 783AE-783AF) makes + # the profit nil; partial relief (s. 783AI) replaces expenses and + # capital allowances with the allowance. Both come to the allowance's + # excess over actual deductions, capped at profit. + relief_with_receipts = min_(profit, allowance - expenses) - capital_allowances + relief_without_receipts = where(profit <= allowance, profit - capital_allowances, 0) + trade_relief = where(receipts_known, relief_with_receipts, relief_without_receipts) + trade_relief = max_(trade_relief, 0) + + # The trade receipts that use the allowance first. Unknown receipts are + # the profit within the allowance, and otherwise use all of it. + trade_receipts = where( + receipts_known, + receipts, + where(profit <= allowance, max_(profit, 0), allowance), + ) + miscellaneous_income = min_( + max_(person("income_not_otherwise_charged", period), 0), + max_(person("miscellaneous_income", period), 0), + ) + miscellaneous_relief = min_( + miscellaneous_income, max_(allowance - trade_receipts, 0) + ) + + # Full relief makes every relevant trade's current profit AND loss + # nil (s.783AF); the election out of it (s.783AL), like not electing for + # partial relief (s.783AM), is for the individual/year and takes the + # miscellaneous income's relief with it. Partial relief also + # replaces every trade's actual expenses. Choose ordinary treatment if + # the raw supplied current loss exceeds the allowance saving. Before + # applying net-income and relief-cap limits, this also preserves an + # unused loss if both choices give nil income. Prior s.83 losses remain + # separate and may follow partial relief. + profit_after_other_deductions = max_( + 0, profit - capital_allowances - person("loss_relief", period) + ) + allowance_saving = ( + min_(trade_relief, profit_after_other_deductions) + miscellaneous_relief + ) + # An earlier input carried/uprated into this year is not a new loss + # for the election, just as it is not a new Class 4 loss. + current_loss = supplied_input(person, "trading_loss", period) + if current_loss is None: + current_loss = person.empty_array() + claim_current_loss = current_loss > allowance_saving + return ( + where(claim_current_loss, 0, trade_relief), + where(claim_current_loss, 0, miscellaneous_relief), + ) diff --git a/policyengine_uk/variables/gov/dwp/housing_benefit/applicable_income/_net_earnings.py b/policyengine_uk/variables/gov/dwp/housing_benefit/applicable_income/_net_earnings.py index 993bfd0a45..e724fe8d1c 100644 --- a/policyengine_uk/variables/gov/dwp/housing_benefit/applicable_income/_net_earnings.py +++ b/policyengine_uk/variables/gov/dwp/housing_benefit/applicable_income/_net_earnings.py @@ -70,7 +70,7 @@ def person_net_earnings( "savings_interest_income", "other_investment_income", "dividend_income", - "taxable_miscellaneous_income", + "miscellaneous_income_before_trading_allowance", ] ) earnings_share = np.divide( diff --git a/policyengine_uk/variables/gov/dwp/housing_benefit/non_dep_deduction/housing_benefit_non_dep_gross_income.py b/policyengine_uk/variables/gov/dwp/housing_benefit/non_dep_deduction/housing_benefit_non_dep_gross_income.py index a493f0d7e5..527588841d 100644 --- a/policyengine_uk/variables/gov/dwp/housing_benefit/non_dep_deduction/housing_benefit_non_dep_gross_income.py +++ b/policyengine_uk/variables/gov/dwp/housing_benefit/non_dep_deduction/housing_benefit_non_dep_gross_income.py @@ -52,7 +52,7 @@ def formula(person, period, parameters): "property_income", "savings_interest_income", "dividend_income", - "taxable_miscellaneous_income", + "miscellaneous_income_before_trading_allowance", "maternity_allowance", "iidb", "sda", diff --git a/policyengine_uk/variables/gov/dwp/universal_credit/income/uc_individual_earned_income_before_mif.py b/policyengine_uk/variables/gov/dwp/universal_credit/income/uc_individual_earned_income_before_mif.py index 95a0d853dc..ed286a1a0b 100644 --- a/policyengine_uk/variables/gov/dwp/universal_credit/income/uc_individual_earned_income_before_mif.py +++ b/policyengine_uk/variables/gov/dwp/universal_credit/income/uc_individual_earned_income_before_mif.py @@ -49,9 +49,11 @@ def formula(person, period, parameters): # The deductions are the person's own and come off only their own # earnings, so one partner's tax, NI or pension contributions never # reduce the other partner's earned income. - # Miscellaneous income as income tax counts it, so a loss does not - # reduce pay (and matches uc_income_tax_on_earnings). Whether it is - # earnings at all is issue #1961. + # Miscellaneous income with a net loss counted as nil, so a loss does + # not reduce pay, and before the trading allowance, an income tax + # relief the UC earnings rules do not give (as a trade counts its + # profit before it). uc_income_tax_on_earnings deducts the tax on it. + # Whether it is earnings at all is issue #1961. # # Reg. 55(4) treats statutory sick, maternity and paternity pay as # employed earnings, and reg. 55(5) takes the deductions off "the @@ -64,7 +66,7 @@ def formula(person, period, parameters): # payments rose. earnings_components = [ "employment_income", - "taxable_miscellaneous_income", + "miscellaneous_income_before_trading_allowance", "statutory_sick_pay", "statutory_maternity_pay", "statutory_paternity_pay", diff --git a/policyengine_uk/variables/gov/hmrc/income_tax/allowances/miscellaneous_income_trading_allowance_deduction.py b/policyengine_uk/variables/gov/hmrc/income_tax/allowances/miscellaneous_income_trading_allowance_deduction.py new file mode 100644 index 0000000000..8b48624fdc --- /dev/null +++ b/policyengine_uk/variables/gov/hmrc/income_tax/allowances/miscellaneous_income_trading_allowance_deduction.py @@ -0,0 +1,54 @@ +from policyengine_uk.model_api import * +from policyengine_uk.utils.trading_allowance import trading_allowance_reliefs + + +class miscellaneous_income_trading_allowance_deduction(Variable): + value_type = float + entity = Person + label = "Deduction from miscellaneous income applied by the trading allowance" + documentation = ( + "The trading allowance's relief on income not otherwise charged " + "(ITTOIA 2005 Part 5 Chapter 8), the miscellaneous income Part 6A " + "covers alongside trade receipts. One allowance covers both. When " + "trade receipts and this income together are within it, both are " + "relieved in full (ss. 783AE, 783AF and 783AG). Above it, the person " + "can elect to deduct the allowance, split between them as they " + "choose, in place of actual expenses (ss. 783AI-783AK); the model " + "gives the trades their receipts' worth first, so this income gets " + "the part of the allowance the trade receipts leave, even where trade " + "losses brought forward would absorb the trade's share or pension " + "contributions exceed relevant UK earnings (see " + "utils/trading_allowance.py). Ordinary treatment to claim a current " + "trading loss (no full relief, s. 783AL, or no partial relief, s. " + "783AM) leaves it unrelieved. Only " + "income tax reads this deduction, through taxable_miscellaneous_income; " + "means tests and gross income bases read " + "miscellaneous_income_before_trading_allowance. Datasets that hold " + "income already net of the allowance should set it to zero, as " + "policyengine-uk-data does for trading_allowance_deduction on SPI " + "records." + ) + definition_period = YEAR + reference = [ + dict( + title="Income Tax (Trading and Other Income) Act 2005 s. 783AB", + href="https://www.legislation.gov.uk/ukpga/2005/5/section/783AB", + ), + dict( + title="Income Tax (Trading and Other Income) Act 2005 s. 783AG (full relief)", + href="https://www.legislation.gov.uk/ukpga/2005/5/section/783AG", + ), + dict( + title="Income Tax (Trading and Other Income) Act 2005 s. 783AJ (partial relief)", + href="https://www.legislation.gov.uk/ukpga/2005/5/section/783AJ", + ), + dict( + title="Income Tax (Trading and Other Income) Act 2005 s. 783AK (splitting the allowance)", + href="https://www.legislation.gov.uk/ukpga/2005/5/section/783AK", + ), + ] + unit = GBP + + def formula(person, period, parameters): + _, miscellaneous_relief = trading_allowance_reliefs(person, period, parameters) + return miscellaneous_relief diff --git a/policyengine_uk/variables/gov/hmrc/income_tax/allowances/trading_allowance_deduction.py b/policyengine_uk/variables/gov/hmrc/income_tax/allowances/trading_allowance_deduction.py index 25554afc7f..565a4957d9 100644 --- a/policyengine_uk/variables/gov/hmrc/income_tax/allowances/trading_allowance_deduction.py +++ b/policyengine_uk/variables/gov/hmrc/income_tax/allowances/trading_allowance_deduction.py @@ -1,5 +1,5 @@ from policyengine_uk.model_api import * -from policyengine_uk.utils.supplied_inputs import supplied_input +from policyengine_uk.utils.trading_allowance import trading_allowance_reliefs class trading_allowance_deduction(Variable): @@ -20,7 +20,8 @@ class trading_allowance_deduction(Variable): "full profit just above the allowance. Receipts below profit are " "inconsistent and treated as unknown. The model compares the raw loss " "supplied for this tax year, before net-income and relief-cap limits, " - "with the allowance's effective saving after other deductions. " + "with the allowance's effective saving after other deductions, " + "including its relief on miscellaneous income. " "Ordinary expenses with a loss claim win when that raw loss is " "larger; allowance treatment otherwise, including ties. Comparing " "the raw loss can preserve an unused carry-forward even when both " @@ -29,7 +30,9 @@ class trading_allowance_deduction(Variable): "the individual's relevant trades " "for the tax year. Allowance treatment makes their current losses " "nil for Income Tax and Class 4. It does not optimise future loss use " - "or benefits." + "or benefits. The same allowance relieves income not otherwise " + "charged (miscellaneous_income_trading_allowance_deduction), which " + "gets only what the trade receipts leave of it." ) definition_period = YEAR reference = [ @@ -57,38 +60,5 @@ class trading_allowance_deduction(Variable): unit = GBP def formula(person, period, parameters): - allowance = parameters(period).gov.hmrc.income_tax.allowances.trading_allowance - profit = person("self_employment_income", period) - receipts = person("self_employment_gross_receipts", period) - capital_allowances = person("capital_allowances", period) - # Receipts below profit (including the default zero) mean unknown. - receipts_known = (receipts > 0) & (receipts >= profit) - expenses = max_(receipts - profit, 0) - # Full relief (receipts within the allowance, s. 783AE-783AF) makes - # the profit nil; partial relief (s. 783AI) replaces expenses and - # capital allowances with the allowance. Both come to the allowance's - # excess over actual deductions, capped at profit. - relief_with_receipts = min_(profit, allowance - expenses) - capital_allowances - relief_without_receipts = where( - profit <= allowance, profit - capital_allowances, 0 - ) - relief = where(receipts_known, relief_with_receipts, relief_without_receipts) - relief = max_(relief, 0) - # Full relief makes every relevant trade's current profit AND loss - # nil (s.783AF); the opt-out is for the individual/year (s.783AL). - # Partial relief also replaces every trade's actual expenses. Choose - # ordinary treatment if the raw supplied current loss exceeds the - # allowance saving. Before applying net-income and relief-cap limits, - # this also preserves an unused loss if both choices give nil income. - # Prior s.83 losses remain separate and may follow partial relief. - profit_after_other_deductions = max_( - 0, profit - capital_allowances - person("loss_relief", period) - ) - allowance_saving = min_(relief, profit_after_other_deductions) - # An earlier input carried/uprated into this year is not a new loss - # for the election, just as it is not a new Class 4 loss. - current_loss = supplied_input(person, "trading_loss", period) - if current_loss is None: - current_loss = person.empty_array() - claim_current_loss = current_loss > allowance_saving - return where(claim_current_loss, 0, relief) + trade_relief, _ = trading_allowance_reliefs(person, period, parameters) + return trade_relief diff --git a/policyengine_uk/variables/gov/hmrc/income_tax/allowances/uses_trading_allowance.py b/policyengine_uk/variables/gov/hmrc/income_tax/allowances/uses_trading_allowance.py new file mode 100644 index 0000000000..588605e2e8 --- /dev/null +++ b/policyengine_uk/variables/gov/hmrc/income_tax/allowances/uses_trading_allowance.py @@ -0,0 +1,39 @@ +from policyengine_uk.model_api import * + + +class uses_trading_allowance(Variable): + value_type = bool + entity = Person + label = "Uses the trading allowance" + documentation = ( + "Whether the person takes the trading allowance for the year, by full " + "or partial relief, rather than ordinary treatment: the allowance " + "relieves their trade profits or their income not otherwise charged. " + "Either relief covers all their relevant income (ITTOIA 2005 s. 783A), " + "so it makes every relevant trade's current profit or loss its " + "allowance figure (ss. 783AF and 783AI), and a current trading loss " + "cannot then be claimed. A person with a larger current loss takes " + "ordinary treatment and claims it: within the allowance by electing " + "out of full relief (s. 783AL), above it by not electing for partial " + "relief (s. 783AM)." + ) + definition_period = YEAR + reference = [ + dict( + title="Income Tax (Trading and Other Income) Act 2005 s. 783A", + href="https://www.legislation.gov.uk/ukpga/2005/5/section/783A", + ), + dict( + title="Income Tax (Trading and Other Income) Act 2005 s. 783AL", + href="https://www.legislation.gov.uk/ukpga/2005/5/section/783AL", + ), + dict( + title="Income Tax (Trading and Other Income) Act 2005 s. 783AM", + href="https://www.legislation.gov.uk/ukpga/2005/5/section/783AM", + ), + ] + + def formula(person, period, parameters): + return (person("trading_allowance_deduction", period) > 0) | ( + person("miscellaneous_income_trading_allowance_deduction", period) > 0 + ) diff --git a/policyengine_uk/variables/gov/hmrc/income_tax/bases/miscellaneous_income_before_trading_allowance.py b/policyengine_uk/variables/gov/hmrc/income_tax/bases/miscellaneous_income_before_trading_allowance.py new file mode 100644 index 0000000000..cc45a0c247 --- /dev/null +++ b/policyengine_uk/variables/gov/hmrc/income_tax/bases/miscellaneous_income_before_trading_allowance.py @@ -0,0 +1,40 @@ +from policyengine_uk.model_api import * + + +class miscellaneous_income_before_trading_allowance(Variable): + value_type = float + entity = Person + label = "Miscellaneous income before the trading allowance" + documentation = ( + "Miscellaneous income with a net loss counted as nil, before the " + "trading allowance relieves income not otherwise charged. Means tests " + "and gross income bases read this figure, as they read a trade's " + "profit before the allowance: the allowance is an income tax relief. " + "Income tax reads taxable_miscellaneous_income, which is this less " + "miscellaneous_income_trading_allowance_deduction. A loss on a " + "miscellaneous transaction is relieved only against income under the " + "same section 1016 provision (ITA 2007 s. 152(4)-(5)) and never set " + "against other income, which is why a net loss counts as nil. The " + "model holds one net figure per person, so it assumes the items netted " + "inside miscellaneous_income can be relieved against each other; that " + "can undertax a person with a gain under one provision and a loss " + "under another. The input also covers income from sources outside " + "section 1016, some of which carry no loss relief at all. Carrying a " + "loss forward against later years' miscellaneous income is not " + "modelled." + ) + definition_period = YEAR + reference = [ + dict( + title="Income Tax (Trading and Other Income) Act 2005 s. 574", + href="https://www.legislation.gov.uk/ukpga/2005/5/section/574", + ), + dict( + title="Income Tax Act 2007 s. 152", + href="https://www.legislation.gov.uk/ukpga/2007/3/section/152", + ), + ] + unit = GBP + + def formula(person, period, parameters): + return max_(0, person("miscellaneous_income", period)) diff --git a/policyengine_uk/variables/gov/hmrc/income_tax/bases/taxable_miscellaneous_income.py b/policyengine_uk/variables/gov/hmrc/income_tax/bases/taxable_miscellaneous_income.py index ce8be05b12..0c5248ceab 100644 --- a/policyengine_uk/variables/gov/hmrc/income_tax/bases/taxable_miscellaneous_income.py +++ b/policyengine_uk/variables/gov/hmrc/income_tax/bases/taxable_miscellaneous_income.py @@ -6,17 +6,12 @@ class taxable_miscellaneous_income(Variable): entity = Person label = "Amount of miscellaneous income that is taxable" documentation = ( - "Miscellaneous income charged to income tax. On a claim, a loss on a " - "miscellaneous transaction is relieved only against relevant " - "miscellaneous income: income under the same section 1016 provision as " - "the loss (ITA 2007 s. 152(4)-(5)). It is never set against other " - "income, so a net loss counts as nil here. The model holds one net " - "figure per person, so it assumes the items netted inside " - "miscellaneous_income can be relieved against each other; that can " - "undertax a person with a gain under one provision and a loss under " - "another. The input also covers income from sources outside section " - "1016, some of which carry no loss relief at all. Carrying a loss " - "forward against later years' miscellaneous income is not modelled." + "Miscellaneous income charged to income tax: " + "miscellaneous_income_before_trading_allowance, in which a net loss " + "counts as nil, less the trading allowance's relief on income not " + "otherwise charged (miscellaneous_income_trading_allowance_deduction). " + "Income tax reads this figure; means tests and gross income bases read " + "the figure before the allowance." ) definition_period = YEAR reference = [ @@ -24,6 +19,10 @@ class taxable_miscellaneous_income(Variable): title="Income Tax (Trading and Other Income) Act 2005 s. 574", href="https://www.legislation.gov.uk/ukpga/2005/5/section/574", ), + dict( + title="Income Tax (Trading and Other Income) Act 2005 s. 688(2)(za)", + href="https://www.legislation.gov.uk/ukpga/2005/5/section/688", + ), dict( title="Income Tax Act 2007 s. 152", href="https://www.legislation.gov.uk/ukpga/2007/3/section/152", @@ -32,4 +31,6 @@ class taxable_miscellaneous_income(Variable): unit = GBP def formula(person, period, parameters): - return max_(0, person("miscellaneous_income", period)) + return person("miscellaneous_income_before_trading_allowance", period) - person( + "miscellaneous_income_trading_allowance_deduction", period + ) diff --git a/policyengine_uk/variables/gov/hmrc/income_tax/reliefs/trade_loss_relief_against_general_income.py b/policyengine_uk/variables/gov/hmrc/income_tax/reliefs/trade_loss_relief_against_general_income.py index 5f45fcbdef..a63ba805ae 100644 --- a/policyengine_uk/variables/gov/hmrc/income_tax/reliefs/trade_loss_relief_against_general_income.py +++ b/policyengine_uk/variables/gov/hmrc/income_tax/reliefs/trade_loss_relief_against_general_income.py @@ -42,7 +42,7 @@ def formula(person, period, parameters): # Allowance treatment cannot retain a current expense-generated loss # on another trade in the same individual's tax year. loss = where( - person("trading_allowance_deduction", period) > 0, + person("uses_trading_allowance", period), 0, person("trading_loss", period), ) diff --git a/policyengine_uk/variables/gov/hmrc/national_insurance/class_4/ni_class_4_trading_loss.py b/policyengine_uk/variables/gov/hmrc/national_insurance/class_4/ni_class_4_trading_loss.py index 5bda122912..4d0f62e334 100644 --- a/policyengine_uk/variables/gov/hmrc/national_insurance/class_4/ni_class_4_trading_loss.py +++ b/policyengine_uk/variables/gov/hmrc/national_insurance/class_4/ni_class_4_trading_loss.py @@ -56,8 +56,9 @@ def formula(person, period, parameters): if loss is None: return person.empty_array() # Class 4 uses the same year's recomputed trade profit or loss as - # Income Tax. A positive allowance deduction means the taxpayer - # chose allowance treatment and gave up ordinary current losses. + # Income Tax. A positive allowance deduction, on trade profits or on + # income not otherwise charged, means the taxpayer chose allowance + # treatment and gave up ordinary current losses. # This leaves independently carried earlier Class 4 losses intact. - allowance_chosen = person("trading_allowance_deduction", period) > 0 + allowance_chosen = person("uses_trading_allowance", period) return where(allowance_chosen, 0, max_(loss, 0)) diff --git a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_non_dep_gross_income.py b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_non_dep_gross_income.py index 74bb4a87d3..24b75757db 100644 --- a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_non_dep_gross_income.py +++ b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_non_dep_gross_income.py @@ -56,7 +56,7 @@ def formula(person, period, parameters): "property_income", "savings_interest_income", "dividend_income", - "taxable_miscellaneous_income", + "miscellaneous_income_before_trading_allowance", "maternity_allowance", "iidb", "sda", diff --git a/policyengine_uk/variables/gov/treasury/energy_bills_rebate/alternative_fuel_payment.py b/policyengine_uk/variables/gov/treasury/energy_bills_rebate/alternative_fuel_payment.py new file mode 100644 index 0000000000..964219a8b1 --- /dev/null +++ b/policyengine_uk/variables/gov/treasury/energy_bills_rebate/alternative_fuel_payment.py @@ -0,0 +1,26 @@ +from policyengine_uk.model_api import * + + +class ebr_alternative_fuel_payment(Variable): + label = "Alternative Fuel Payment (EBR)" + documentation = ( + "Calendar-year sum of the monthly Alternative Fuel Payment, as " + "ebr_energy_bills_credit sums the Energy Bills Support Scheme." + ) + entity = Household + definition_period = YEAR + value_type = float + unit = GBP + reference = ( + "https://assets.publishing.service.gov.uk/media/63a59ed48fa8f5654fe0a812/ebss-ni-direction.pdf#page=2", + "https://www.gov.uk/government/news/households-across-northern-ireland-to-start-receiving-600-uk-government-energy-support", + ) + + def formula(household, period, parameters): + annual_payment = 0 + for month in range(1, MONTHS_IN_YEAR + 1): + annual_payment = annual_payment + household( + "monthly_ebr_alternative_fuel_payment", + f"{period.this_year}-{month:02d}", + ) + return annual_payment diff --git a/policyengine_uk/variables/gov/treasury/energy_bills_rebate/council_tax_rebate.py b/policyengine_uk/variables/gov/treasury/energy_bills_rebate/council_tax_rebate.py index d9b186149c..8dc7ac15b0 100644 --- a/policyengine_uk/variables/gov/treasury/energy_bills_rebate/council_tax_rebate.py +++ b/policyengine_uk/variables/gov/treasury/energy_bills_rebate/council_tax_rebate.py @@ -3,11 +3,26 @@ class ebr_council_tax_rebate(Variable): label = "Council Tax Rebate (EBR)" - documentation = "Council Tax discount under the Energy Bills Rebate." + documentation = ( + "Council Tax discount under the Energy Bills Rebate. England's £150 " + "Council Tax Rebate went to households in bands A to D (billing " + "authority guidance, paragraphs 2 and 11), and Scotland and Wales " + "paid their own £150 awards to the same bands. Northern Ireland " + "levies domestic rates, not council tax, and had no Council Tax " + "Rebate; its households received the £600 Energy Bills Support " + "Scheme payment. The model doesn't pay the Scottish and Welsh awards " + "to Council Tax Reduction recipients in bands E and above." + ) entity = Household definition_period = YEAR value_type = float unit = GBP + reference = ( + "https://www.gov.uk/government/publications/the-council-tax-rebate-2022-23-billing-authority-guidance/support-for-energy-bills-the-council-tax-rebate-2022-23-billing-authority-guidance", + "https://www.gov.scot/publications/local-government-finance-circular-2-2022-guidance-to-local-authorities-april-2022-cost-of-living-award/", + "https://www.gov.wales/cost-living-support-scheme-guide-local-authorities-html", + "https://www.legislation.gov.uk/nisi/1977/2157/contents", + ) def formula(household, period, parameters): ctr = parameters(period).gov.treasury.energy_bills_rebate.council_tax_rebate @@ -17,4 +32,8 @@ def formula(household, period, parameters): np.array([ct_band == getattr(ct_band_values, band) for band in ctr.bands]), axis=0, ) - return eligible * ctr.amount + # Northern Ireland has no council tax bands, so its input band means + # nothing here (the enhanced FRS gives every NI household band A). + country = household("country", period) + has_council_tax = country != country.possible_values.NORTHERN_IRELAND + return has_council_tax * eligible * ctr.amount diff --git a/policyengine_uk/variables/gov/treasury/energy_bills_rebate/energy_bills_rebate.py b/policyengine_uk/variables/gov/treasury/energy_bills_rebate/energy_bills_rebate.py index 7f94b77cc6..9b51a0984e 100644 --- a/policyengine_uk/variables/gov/treasury/energy_bills_rebate/energy_bills_rebate.py +++ b/policyengine_uk/variables/gov/treasury/energy_bills_rebate/energy_bills_rebate.py @@ -9,4 +9,8 @@ class energy_bills_rebate(Variable): value_type = float unit = GBP - adds = ["ebr_council_tax_rebate", "ebr_energy_bills_credit"] + adds = [ + "ebr_council_tax_rebate", + "ebr_energy_bills_credit", + "ebr_alternative_fuel_payment", + ] diff --git a/policyengine_uk/variables/gov/treasury/energy_bills_rebate/monthly_ebr_alternative_fuel_payment.py b/policyengine_uk/variables/gov/treasury/energy_bills_rebate/monthly_ebr_alternative_fuel_payment.py new file mode 100644 index 0000000000..8ec5791536 --- /dev/null +++ b/policyengine_uk/variables/gov/treasury/energy_bills_rebate/monthly_ebr_alternative_fuel_payment.py @@ -0,0 +1,29 @@ +from policyengine_uk.model_api import * + + +class monthly_ebr_alternative_fuel_payment(Variable): + label = "Monthly Alternative Fuel Payment" + documentation = ( + "Northern Ireland's £200 Alternative Fuel Payment for the month. " + "Electricity suppliers paid it with the £400 Energy Bills Support " + "Scheme amount, as one £600 payment to each eligible domestic " + "customer, whatever fuel the household heats with. Great Britain's " + "Alternative Fuel Payment, for households that heat with heating oil, " + "LPG, biomass and other alternative fuels, is not modelled." + ) + entity = Household + definition_period = MONTH + value_type = float + unit = GBP + defined_for = "ebr_energy_bills_credit_eligible" + reference = ( + "https://assets.publishing.service.gov.uk/media/63a59ed48fa8f5654fe0a812/ebss-ni-direction.pdf#page=2", + "https://www.gov.uk/government/news/households-across-northern-ireland-to-start-receiving-600-uk-government-energy-support", + "https://www.gov.uk/government/news/increased-flexibility-of-alternative-fuel-payments-so-more-households-will-be-supported-with-their-energy-bills", + ) + + def formula(household, period, parameters): + ebr = parameters(period).gov.treasury.energy_bills_rebate + country = household("country", period.this_year) + is_northern_ireland = country == country.possible_values.NORTHERN_IRELAND + return is_northern_ireland * ebr.alternative_fuel_payment_northern_ireland diff --git a/policyengine_uk/variables/gov/treasury/energy_bills_rebate/monthly_ebr_energy_bills_credit.py b/policyengine_uk/variables/gov/treasury/energy_bills_rebate/monthly_ebr_energy_bills_credit.py index 2509be974e..5c7668c18b 100644 --- a/policyengine_uk/variables/gov/treasury/energy_bills_rebate/monthly_ebr_energy_bills_credit.py +++ b/policyengine_uk/variables/gov/treasury/energy_bills_rebate/monthly_ebr_energy_bills_credit.py @@ -4,8 +4,9 @@ class monthly_ebr_energy_bills_credit(Variable): label = "Monthly Energy Bills Support Scheme credit" documentation = ( - "Main-scheme entitlement for the month. Alternative-funding payments and " - "Northern Ireland's separate Alternative Fuel Payment are not included." + "Main-scheme entitlement for the month. Alternative-funding payments are " + "not included, and Northern Ireland's Alternative Fuel Payment is " + "monthly_ebr_alternative_fuel_payment." ) entity = Household definition_period = MONTH diff --git a/policyengine_uk/variables/household/income/gross_taxable_inputs.py b/policyengine_uk/variables/household/income/gross_taxable_inputs.py index c759969f7a..97c3ee9f31 100644 --- a/policyengine_uk/variables/household/income/gross_taxable_inputs.py +++ b/policyengine_uk/variables/household/income/gross_taxable_inputs.py @@ -7,7 +7,9 @@ class gross_taxable_inputs(Variable): This is a modelling choice, not a statutory definition of gross income. Employment income excludes statutory pay and employment expenses are not deducted. Property and trading income retain signed input values; a net - miscellaneous loss is floored inside taxable_miscellaneous_income. + miscellaneous loss is floored inside + miscellaneous_income_before_trading_allowance, which is before the + trading allowance as trading income is. """ value_type = float @@ -34,5 +36,5 @@ class gross_taxable_inputs(Variable): "savings_interest_income", "other_investment_income", "dividend_income", - "taxable_miscellaneous_income", + "miscellaneous_income_before_trading_allowance", ] diff --git a/policyengine_uk/variables/household/income/hbai_household_net_income.py b/policyengine_uk/variables/household/income/hbai_household_net_income.py index 604ec70078..75af27e693 100644 --- a/policyengine_uk/variables/household/income/hbai_household_net_income.py +++ b/policyengine_uk/variables/household/income/hbai_household_net_income.py @@ -63,6 +63,15 @@ # HBAI classifies the Warm Home Discount as miscellaneous income: # https://www.gov.uk/government/statistics/households-below-average-income-for-financial-years-ending-1995-to-2025/households-below-average-income-background-information-and-methodology-report-fye-2025 "warm_home_discount", + # HBAI classifies the £400 Energy Bills Support Scheme (£600 in Northern + # Ireland) and the £150 Council Tax Rebate as miscellaneous income, not + # state support. DWP imputes receipt, since the FRS did not ask about + # them, so no FRS income field behind miscellaneous_income holds them. + # HBAI counts the whole credit in FYE 2023; the model sums it by + # calendar year here as in household net income (see + # docs/book/programs/gov/treasury/cost-of-living-payments.md): + # https://www.gov.uk/government/statistics/households-below-average-income-for-financial-years-ending-1995-to-2023/households-below-average-income-series-quality-and-methodology-information-report-fye-2023 + "energy_bills_rebate", # Reference for tax-free-childcare: https://assets.publishing.service.gov.uk/media/5e7b191886650c744175d08b/households-below-average-income-1994-1995-2018-2019.pdf ] diff --git a/policyengine_uk/variables/input/income_not_otherwise_charged.py b/policyengine_uk/variables/input/income_not_otherwise_charged.py new file mode 100644 index 0000000000..615a6ec09a --- /dev/null +++ b/policyengine_uk/variables/input/income_not_otherwise_charged.py @@ -0,0 +1,36 @@ +from policyengine_uk.model_api import * + + +class income_not_otherwise_charged(Variable): + value_type = float + entity = Person + label = "income not otherwise charged" + documentation = ( + "The part of miscellaneous_income chargeable to income tax under ITTOIA " + "2005 Part 5 Chapter 8 (s. 687): income from a source no other " + "provision charges, such as casual income that does not come from a " + "trade. It is part of miscellaneous_income, not added to it, and counts " + "up to miscellaneous_income where that is positive. This is the " + '"miscellaneous income" the trading allowance relieves (s. 783AB), ' + "measured before expenses; the model holds no expenses against it. " + "Optional: used only to apply the trading allowance. Leave it at zero " + "when unknown. The survey data do not separate it from the rest of " + "miscellaneous_income, which also holds royalties, charges under other " + "Part 5 Chapters and, on SPI records, benefits and termination " + "payments, none of which the allowance relieves. Without " + "self_employment_gross_receipts, a trade with a loss or no profit is " + "taken to have no receipts, so its relevant income is this alone." + ) + definition_period = YEAR + unit = GBP + reference = [ + dict( + title="Income Tax (Trading and Other Income) Act 2005 s. 687", + href="https://www.legislation.gov.uk/ukpga/2005/5/section/687", + ), + dict( + title="Income Tax (Trading and Other Income) Act 2005 s. 783AB", + href="https://www.legislation.gov.uk/ukpga/2005/5/section/783AB", + ), + ] + uprating = "gov.economic_assumptions.indices.obr.per_capita.gdp"