diff --git a/changelog.d/council-tax-reduction-oxford.md b/changelog.d/council-tax-reduction-oxford.md new file mode 100644 index 000000000..b91c97da8 --- /dev/null +++ b/changelog.d/council-tax-reduction-oxford.md @@ -0,0 +1 @@ +Add Oxford working-age Council Tax Reduction. diff --git a/policyengine_uk/parameters/gov/local_authorities/oxford/council_tax_reduction/income_band/maximum_support_rate.yaml b/policyengine_uk/parameters/gov/local_authorities/oxford/council_tax_reduction/income_band/maximum_support_rate.yaml new file mode 100644 index 000000000..79ca552e6 --- /dev/null +++ b/policyengine_uk/parameters/gov/local_authorities/oxford/council_tax_reduction/income_band/maximum_support_rate.yaml @@ -0,0 +1,31 @@ +description: Maximum Council Tax Reduction rate by weekly income band under the Oxford working-age scheme. +brackets: + - threshold: + 2026-04-01: 0 + amount: + 2026-04-01: 1 + - threshold: + 2026-04-01: 480.01 + amount: + 2026-04-01: 0.75 + - threshold: + 2026-04-01: 530.01 + amount: + 2026-04-01: 0.5 + - threshold: + 2026-04-01: 580.01 + amount: + 2026-04-01: 0.25 + - threshold: + 2026-04-01: 660.01 + amount: + 2026-04-01: 0 +metadata: + amount_unit: /1 + period: week + threshold_unit: currency-GBP + type: single_amount + label: Oxford Council Tax Reduction weekly income-band maximum support rate + reference: + - title: Oxford City Council Council Tax Reduction Scheme 2026 to 2027 + href: https://www.oxford.gov.uk/council-tax-reduction/council-tax-reduction-scheme-2026-27 diff --git a/policyengine_uk/parameters/gov/local_authorities/oxford/council_tax_reduction/means_test/capital_limit.yaml b/policyengine_uk/parameters/gov/local_authorities/oxford/council_tax_reduction/means_test/capital_limit.yaml new file mode 100644 index 000000000..dfbd2ed46 --- /dev/null +++ b/policyengine_uk/parameters/gov/local_authorities/oxford/council_tax_reduction/means_test/capital_limit.yaml @@ -0,0 +1,10 @@ +description: Capital limit for working-age households under the Oxford Council Tax Reduction scheme. +values: + 2026-04-01: 16_000 +metadata: + unit: currency-GBP + period: year + label: Oxford Council Tax Reduction capital limit + reference: + - title: Oxford City Council Council Tax Reduction Scheme 2026 to 2027 + href: https://www.oxford.gov.uk/council-tax-reduction/council-tax-reduction-scheme-2026-27 diff --git a/policyengine_uk/parameters/gov/local_authorities/oxford/council_tax_reduction/means_test/tariff_income_step.yaml b/policyengine_uk/parameters/gov/local_authorities/oxford/council_tax_reduction/means_test/tariff_income_step.yaml new file mode 100644 index 000000000..e09803f84 --- /dev/null +++ b/policyengine_uk/parameters/gov/local_authorities/oxford/council_tax_reduction/means_test/tariff_income_step.yaml @@ -0,0 +1,10 @@ +description: Amount of capital above the tariff threshold that is treated as one weekly pound of income under the Oxford Council Tax Reduction scheme. +values: + 2026-04-01: 250 +metadata: + unit: currency-GBP + period: year + label: Oxford Council Tax Reduction tariff income capital step + reference: + - title: Oxford City Council Council Tax Reduction Scheme 2026 to 2027 + href: https://www.oxford.gov.uk/council-tax-reduction/council-tax-reduction-scheme-2026-27 diff --git a/policyengine_uk/parameters/gov/local_authorities/oxford/council_tax_reduction/means_test/tariff_income_threshold.yaml b/policyengine_uk/parameters/gov/local_authorities/oxford/council_tax_reduction/means_test/tariff_income_threshold.yaml new file mode 100644 index 000000000..1c544fe37 --- /dev/null +++ b/policyengine_uk/parameters/gov/local_authorities/oxford/council_tax_reduction/means_test/tariff_income_threshold.yaml @@ -0,0 +1,10 @@ +description: Capital threshold above which tariff income applies under the Oxford Council Tax Reduction scheme. +values: + 2026-04-01: 6_000 +metadata: + unit: currency-GBP + period: year + label: Oxford Council Tax Reduction tariff income capital threshold + reference: + - title: Oxford City Council Council Tax Reduction Scheme 2026 to 2027 + href: https://www.oxford.gov.uk/council-tax-reduction/council-tax-reduction-scheme-2026-27 diff --git a/policyengine_uk/parameters/gov/local_authorities/oxford/council_tax_reduction/non_dep_deduction/amount.yaml b/policyengine_uk/parameters/gov/local_authorities/oxford/council_tax_reduction/non_dep_deduction/amount.yaml new file mode 100644 index 000000000..dd320305a --- /dev/null +++ b/policyengine_uk/parameters/gov/local_authorities/oxford/council_tax_reduction/non_dep_deduction/amount.yaml @@ -0,0 +1,27 @@ +description: Weekly working-age non-dependant deduction schedule under the Oxford Council Tax Reduction scheme. +brackets: + - threshold: + 2026-04-01: 0 + amount: + 2026-04-01: 5.2 + - threshold: + 2026-04-01: 279 + amount: + 2026-04-01: 10.6 + - threshold: + 2026-04-01: 485 + amount: + 2026-04-01: 13.3 + - threshold: + 2026-04-01: 605 + amount: + 2026-04-01: 15.95 +metadata: + amount_unit: currency-GBP + period: week + threshold_unit: currency-GBP + type: single_amount + label: Oxford Council Tax Reduction working-age non-dependant deduction schedule + reference: + - title: Oxford City Council Council Tax Reduction Scheme 2026 to 2027 + href: https://www.oxford.gov.uk/council-tax-reduction/council-tax-reduction-scheme-2026-27 diff --git a/policyengine_uk/parameters/gov/local_authorities/oxford/council_tax_reduction/non_dep_deduction/remunerative_work_hours.yaml b/policyengine_uk/parameters/gov/local_authorities/oxford/council_tax_reduction/non_dep_deduction/remunerative_work_hours.yaml new file mode 100644 index 000000000..3a4cec088 --- /dev/null +++ b/policyengine_uk/parameters/gov/local_authorities/oxford/council_tax_reduction/non_dep_deduction/remunerative_work_hours.yaml @@ -0,0 +1,10 @@ +description: Weekly hours threshold for remunerative work under Oxford's Council Tax Reduction non-dependant deduction schedule. +values: + 2026-04-01: 16 +metadata: + unit: hour + period: week + label: Oxford Council Tax Reduction non-dependant remunerative work hours threshold + reference: + - title: Oxford City Council Council Tax Reduction Scheme 2026 to 2027 + href: https://www.oxford.gov.uk/council-tax-reduction/council-tax-reduction-scheme-2026-27 diff --git a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction.yaml b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction.yaml index c1fb81a3e..daaec21e2 100644 --- a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction.yaml @@ -722,3 +722,202 @@ output: uc_assessable_capital: 0 council_tax_reduction: 1_800 + +- name: Oxford working-age claimant can receive full support + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 35 + benunits: + benunit: + members: [claimant] + would_claim_uc: false + claims_all_entitled_benefits: true + households: + household: + members: [claimant] + country: ENGLAND + local_authority: OXFORD + council_tax: 1_800 + savings: 0 + output: + council_tax_reduction_scheme_supported: true + simulated_council_tax_reduction_benunit: 1_800 + council_tax_reduction: 1_800 + +- name: Oxford applies the 75 percent band above 480 weekly income + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 35 + benunits: + benunit: + members: [claimant] + would_claim_uc: false + claims_all_entitled_benefits: true + council_tax_reduction_applicable_income: 25_001 + households: + household: + members: [claimant] + country: ENGLAND + local_authority: OXFORD + council_tax: 1_800 + savings: 0 + output: + council_tax_reduction: 1_350 + +- name: Oxford disregards Child Benefit before selecting the income band + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 35 + benunits: + benunit: + members: [claimant] + would_claim_uc: false + claims_all_entitled_benefits: true + council_tax_reduction_applicable_income: 25_200 + child_benefit: 1_000 + households: + household: + members: [claimant] + country: ENGLAND + local_authority: OXFORD + council_tax: 1_800 + savings: 0 + output: + council_tax_reduction: 1_800 + +- name: Oxford tariff income rounds up partial 250 pound capital blocks + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 35 + benunits: + benunit: + members: [claimant] + would_claim_uc: false + claims_all_entitled_benefits: true + council_tax_reduction_applicable_income: 24_960 + households: + household: + members: [claimant] + country: ENGLAND + local_authority: OXFORD + council_tax: 1_800 + savings: 6_001 + output: + council_tax_reduction: 1_350 + +- name: Oxford UC claimant does not add local tariff income to the UC income band + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 35 + benunits: + benunit: + members: [claimant] + would_claim_uc: true + claims_all_entitled_benefits: true + uc_maximum_amount: 24_960 + uc_income_reduction: 0 + council_tax_reduction_applicable_income: 24_960 + households: + household: + members: [claimant] + country: ENGLAND + local_authority: OXFORD + council_tax: 1_800 + savings: 6_001 + output: + council_tax_reduction: 1_800 + +- name: Oxford working-age claimant above capital limit gets no local support + period: 2026 + absolute_error_margin: 0 + input: + people: + claimant: + age: 35 + council_tax_benefit_reported: 500 + benunits: + benunit: + members: [claimant] + claims_all_entitled_benefits: true + households: + household: + members: [claimant] + country: ENGLAND + local_authority: OXFORD + council_tax: 1_800 + savings: 16_001 + output: + council_tax_reduction_scheme_supported: true + council_tax_benefit: 0 + council_tax_reduction: 0 + +- name: Oxford applies its gross-income non-dependant deduction schedule + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 35 + non_dep: + age: 25 + employment_income: 5_200 + private_pension_income: 26_260 + weekly_hours: 16 + benunits: + claimant_benunit: + members: [claimant] + would_claim_uc: false + claims_all_entitled_benefits: true + non_dep_benunit: + members: [non_dep] + households: + household: + members: [claimant, non_dep] + country: ENGLAND + local_authority: OXFORD + council_tax: 1_800 + savings: 0 + output: + council_tax_reduction: 970.60 + +- name: Oxford does not exempt working-age income-based benefit non-dependants + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 35 + non_dep: + age: 25 + benunits: + claimant_benunit: + members: [claimant] + would_claim_uc: false + claims_all_entitled_benefits: true + non_dep_benunit: + members: [non_dep] + income_support: 1 + households: + household: + members: [claimant, non_dep] + country: ENGLAND + local_authority: OXFORD + council_tax: 1_800 + savings: 0 + output: + council_tax_reduction: 1_529.60 diff --git a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/config.py b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/config.py index ecd0dc6ba..1ee8ea5cb 100644 --- a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/config.py +++ b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/config.py @@ -53,6 +53,14 @@ def is_westminster_working_age(local_authority, country, has_pensioner): ) +def is_oxford(local_authority): + return local_authority == LocalAuthority.OXFORD + + +def is_oxford_working_age(local_authority, country, has_pensioner): + return (country == Country.ENGLAND) & ~has_pensioner & is_oxford(local_authority) + + def is_supported_scheme(country, has_pensioner, local_authority): return ( is_england_pensioner_scheme(country, has_pensioner) @@ -62,4 +70,5 @@ def is_supported_scheme(country, has_pensioner, local_authority): | is_kingston_upon_thames_working_age(local_authority, country, has_pensioner) | is_newham_working_age(local_authority, country, has_pensioner) | is_westminster_working_age(local_authority, country, has_pensioner) + | is_oxford_working_age(local_authority, country, has_pensioner) ) diff --git a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/simulated_council_tax_reduction_benunit.py b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/simulated_council_tax_reduction_benunit.py index 42a37352e..ce3eeb7ee 100644 --- a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/simulated_council_tax_reduction_benunit.py +++ b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/simulated_council_tax_reduction_benunit.py @@ -10,6 +10,7 @@ "kingston_upon_thames_council_tax_reduction", "newham_council_tax_reduction", "westminster_council_tax_reduction", + "oxford_council_tax_reduction", ] diff --git a/policyengine_uk/variables/gov/local_authorities/oxford/council_tax_reduction/oxford_council_tax_reduction.py b/policyengine_uk/variables/gov/local_authorities/oxford/council_tax_reduction/oxford_council_tax_reduction.py new file mode 100644 index 000000000..332b9781c --- /dev/null +++ b/policyengine_uk/variables/gov/local_authorities/oxford/council_tax_reduction/oxford_council_tax_reduction.py @@ -0,0 +1,61 @@ +from policyengine_uk.model_api import * +import numpy as np +from policyengine_uk.variables.gov.local_authorities.council_tax_reduction.config import ( + is_oxford_working_age, +) + + +class oxford_council_tax_reduction(Variable): + value_type = float + entity = BenUnit + label = "Oxford Council Tax Reduction" + definition_period = YEAR + unit = GBP + + def formula(benunit, period, parameters): + ctr = parameters(period).gov.local_authorities.oxford.council_tax_reduction + household = benunit.household + working_age = is_oxford_working_age( + household("local_authority", period), + household("country", period), + household("council_tax_reduction_household_has_pensioner", period), + ) + is_household_head_benunit = benunit("benunit_contains_household_head", period) + would_claim = benunit("would_claim_council_tax_reduction", period) + universal_credit = benunit("universal_credit", period) + has_uc_award = universal_credit > 0 + + capital = household("savings", period) + capital_eligible = capital <= ctr.means_test.capital_limit + weekly_tariff_income = np.ceil( + max_(0, capital - ctr.means_test.tariff_income_threshold) + / ctr.means_test.tariff_income_step + ) + + annual_income = benunit("council_tax_reduction_applicable_income", period) + child_benefit = benunit("child_benefit", period) + weekly_income = max_(0, annual_income - child_benefit) / WEEKS_IN_YEAR + weekly_income += where(has_uc_award, 0, weekly_tariff_income) + relevant_income_based_benefit = benunit( + "council_tax_reduction_relevant_income_based_benefit", + period, + ) + weekly_income = where( + relevant_income_based_benefit & ~has_uc_award, 0, weekly_income + ) + + support_rate = ctr.income_band.maximum_support_rate.calc(weekly_income) + liability = household( + "council_tax_reduction_maximum_eligible_liability", period + ) + non_dep_deductions = benunit( + "oxford_council_tax_reduction_non_dep_deductions", period + ) + award = max_(0, liability * support_rate - non_dep_deductions) + return ( + working_age + * is_household_head_benunit + * would_claim + * capital_eligible + * award + ) diff --git a/policyengine_uk/variables/gov/local_authorities/oxford/council_tax_reduction/oxford_council_tax_reduction_individual_non_dep_deduction.py b/policyengine_uk/variables/gov/local_authorities/oxford/council_tax_reduction/oxford_council_tax_reduction_individual_non_dep_deduction.py new file mode 100644 index 000000000..61c80cecb --- /dev/null +++ b/policyengine_uk/variables/gov/local_authorities/oxford/council_tax_reduction/oxford_council_tax_reduction_individual_non_dep_deduction.py @@ -0,0 +1,33 @@ +from policyengine_uk.model_api import * +from policyengine_uk.variables.gov.local_authorities.council_tax_reduction._legacy import ( + normal_gross_income_non_dep_deduction, +) +from policyengine_uk.variables.gov.local_authorities.council_tax_reduction.config import ( + is_oxford_working_age, +) + + +class oxford_council_tax_reduction_individual_non_dep_deduction(Variable): + value_type = float + entity = Person + label = "Oxford CTR individual non-dependent deduction" + definition_period = YEAR + unit = GBP + defined_for = "council_tax_reduction_individual_non_dep_deduction_eligible" + + def formula(person, period, parameters): + ctr = parameters(period).gov.local_authorities.oxford.council_tax_reduction + household = person.household + working_age = is_oxford_working_age( + household("local_authority", period), + household("country", period), + household("council_tax_reduction_household_has_pensioner", period), + ) + return normal_gross_income_non_dep_deduction( + person, + period, + ctr, + working_age, + exempt_income_based_benefits=False, + exempt_uc_no_earned_income=False, + ) diff --git a/policyengine_uk/variables/gov/local_authorities/oxford/council_tax_reduction/oxford_council_tax_reduction_non_dep_deductions.py b/policyengine_uk/variables/gov/local_authorities/oxford/council_tax_reduction/oxford_council_tax_reduction_non_dep_deductions.py new file mode 100644 index 000000000..ed71bcab0 --- /dev/null +++ b/policyengine_uk/variables/gov/local_authorities/oxford/council_tax_reduction/oxford_council_tax_reduction_non_dep_deductions.py @@ -0,0 +1,19 @@ +from policyengine_uk.model_api import * +from policyengine_uk.variables.gov.local_authorities.council_tax_reduction._legacy import ( + local_non_dep_deductions, +) + + +class oxford_council_tax_reduction_non_dep_deductions(Variable): + value_type = float + entity = BenUnit + label = "Oxford CTR non-dependent deductions" + definition_period = YEAR + unit = GBP + + def formula(benunit, period, parameters): + return local_non_dep_deductions( + benunit, + period, + "oxford_council_tax_reduction_individual_non_dep_deduction", + )