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/docs/book/programs/gov/treasury/cost-of-living-payments.md b/docs/book/programs/gov/treasury/cost-of-living-payments.md index 798c0fefe7..3aea349205 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,53 @@ 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, and billing authorities made every payment by 30 September + 2022. The devolved administrations made equivalent payments. +- **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 £400 in one go in January 2023, alongside + a £200 Alternative Fuel Payment that the model does not include. PolicyEngine models these via: - `energy_bills_rebate` (Household, YEAR) = `ebr_council_tax_rebate` + `ebr_energy_bills_credit` - 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 + payment in January 2023 + - `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, 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. 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 +95,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/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/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/council_tax_rebate.yaml b/policyengine_uk/tests/policy/baseline/gov/treasury/energy_bills_rebate/council_tax_rebate.yaml index ba7fceee11..60829709ff 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,26 @@ - 0 - 0 - 0 + +# The rebate was a one-off in 2022-23: billing authorities made every payment +# by 30 September 2022 (billing authority guidance, paragraph 7). It was not +# paid again 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 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..53b0915e0d 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,92 @@ 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 credit 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: + energy_bills_rebate: 400 + hbai_household_net_income: 400 + +- 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 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..64de39ae84 --- /dev/null +++ b/policyengine_uk/tests/test_energy_bills_rebate_hbai_properties.py @@ -0,0 +1,143 @@ +"""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. +""" + +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]) + 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"]), 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"} + 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_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/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"