From e3f06f33344dd919f812b1b5912f63001a36ac15 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sun, 4 Oct 2026 09:15:24 -0400 Subject: [PATCH 1/2] Name each benefit's LHA category and determination; drop dead UC LHA inputs Follow-ups from the round-4 review of #2022: - The HB notebook named the Universal Credit category (LHA_category) for the Housing Benefit cap. It now names housing_benefit_LHA_category (HB Regs 2006 reg 13D) and housing_benefit_LHA_rate. - benunit_lha's docstring said the two benefits read one table. Housing Benefit reads the weekly determination through its own category, Universal Credit the monthly one through LHA_category. BRMA_LHA_rate's label and documentation now say it is the weekly determination read for the UC category, which no formula reads. - Four uncapped_BRMA_LHA_rate inputs in #2006's UC tests stopped mattering once uc_LHA_cap read the published monthly rates. They are replaced by an explicit brma: MAIDSTONE, and the comments cite the published rates (750 and 905 a month). Rent binds in every case, so no expected value changes. Co-Authored-By: Claude Opus 5.5 --- .../lha-hb-category-followups.changed.md | 1 + .../book/programs/gov/dwp/housing-benefit.ipynb | 2 +- .../housing_benefit_meals_deduction.yaml | 5 +++-- .../rent_of_sharers_boarders_and_lodgers.yaml | 17 +++++++++-------- policyengine_uk/utils/lha.py | 14 ++++++++++---- .../variables/gov/dwp/BRMA_LHA_rate.py | 12 ++++++++---- .../gov/dwp/housing_benefit_LHA_rate.py | 2 +- 7 files changed, 33 insertions(+), 20 deletions(-) create mode 100644 changelog.d/lha-hb-category-followups.changed.md diff --git a/changelog.d/lha-hb-category-followups.changed.md b/changelog.d/lha-hb-category-followups.changed.md new file mode 100644 index 0000000000..48c8076464 --- /dev/null +++ b/changelog.d/lha-hb-category-followups.changed.md @@ -0,0 +1 @@ +Document which Local Housing Allowance category and determination Housing Benefit and Universal Credit each read, and drop Universal Credit test inputs that the published rates no longer use. diff --git a/docs/book/programs/gov/dwp/housing-benefit.ipynb b/docs/book/programs/gov/dwp/housing-benefit.ipynb index 2e1ddc4271..324139a598 100644 --- a/docs/book/programs/gov/dwp/housing-benefit.ipynb +++ b/docs/book/programs/gov/dwp/housing-benefit.ipynb @@ -54,7 +54,7 @@ "source": [ "## Local Housing Allowance rates\n", "\n", - "A private renter's eligible rent is capped at the Local Housing Allowance (LHA) for their Broad Rental Market Area (BRMA) and LHA category: shared accommodation, or one to four bedrooms (`LHA_category`). Rent officers determine the rates each January for the year from April. In England this is the Valuation Office Agency, in Wales Rent Officers Wales, in Scotland Rent Service Scotland, and in Northern Ireland the Northern Ireland Housing Executive.\n", + "A private renter's eligible rent is capped at the Local Housing Allowance (LHA) for their Broad Rental Market Area (BRMA) and category of dwelling: shared accommodation, or one to four bedrooms. Housing Benefit has its own category (`housing_benefit_LHA_category`, [reg 13D](https://www.legislation.gov.uk/uksi/2006/213/regulation/13D) of the Housing Benefit Regulations 2006), and its rate is `housing_benefit_LHA_rate`; Universal Credit's category is `LHA_category`. Rent officers determine the rates each January for the year from April. In England this is the Valuation Office Agency, in Wales Rent Officers Wales, in Scotland Rent Service Scotland, and in Northern Ireland the Northern Ireland Housing Executive.\n", "\n", "PolicyEngine reads the published determinations from `parameters/gov/dwp/LHA/lha_published_rates.csv.gz`. `scripts/build_lha_published_rates.py` builds that table from the source files listed, with their hashes, in `lha_published_rates_sources.csv`. For each determination from April 2020, the rate is recomputed from the published 30th percentile rents, following [Schedule 3B](https://www.legislation.gov.uk/uksi/1997/1984/schedule/3B) to the Rent Officers (Housing Benefit Functions) Order 1997:\n", "\n", diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/housing_benefit_meals_deduction.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/housing_benefit_meals_deduction.yaml index 9bd91118a0..903298b41f 100644 --- a/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/housing_benefit_meals_deduction.yaml +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/housing_benefit_meals_deduction.yaml @@ -171,13 +171,14 @@ boarder_family: members: [boarder] meals_included_in_rent: AT_LEAST_THREE_A_DAY - uncapped_BRMA_LHA_rate: 9_000 households: home: members: [householder, boarder] tenure_type: OWNED_OUTRIGHT + brma: MAIDSTONE output: # UC excludes food from service charges only in the social rented # sector and for owner-occupiers (UC Sch 1 para 8(1), (6)(c)); a private - # renter's whole payment is core rent: min(6,000, 9,000) = 6,000. + # renter's whole payment is core rent: min(6,000, Maidstone one-bedroom + # rate 750 a month = 9,000) = 6,000. uc_housing_costs_element: [0, 6_000] diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/rent_of_sharers_boarders_and_lodgers.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/rent_of_sharers_boarders_and_lodgers.yaml index 9f95c277d9..0e02c834d3 100644 --- a/policyengine_uk/tests/policy/baseline/finance/benefit/family/rent_of_sharers_boarders_and_lodgers.yaml +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/rent_of_sharers_boarders_and_lodgers.yaml @@ -135,15 +135,16 @@ members: [householder] lodger_family: members: [lodger] - uncapped_BRMA_LHA_rate: 6_000 households: home: members: [householder, lodger] tenure_type: OWNED_OUTRIGHT + brma: MAIDSTONE output: benunit_rent: [0, 5_200] benunit_tenure_type: [OWNED_OUTRIGHT, RENT_PRIVATELY] - # UC: single, 40 >= 35, 1 bedroom: B. Cap = min(rent 5,200, rate 6,000). + # UC: single, 40 >= 35, 1 bedroom: B. Cap = min(rent 5,200, Maidstone + # one-bedroom rate 750 a month = 9,000). LHA_category: B uc_LHA_cap: [0, 5_200] uc_housing_costs_element: [0, 5_200] @@ -164,17 +165,16 @@ benunits: householder_family: members: [householder] - uncapped_BRMA_LHA_rate: 12_000 daughter: members: [adult_daughter] lodger_family: members: [lodger] - uncapped_BRMA_LHA_rate: 6_000 households: home: members: [householder, adult_daughter, lodger] rent: 10_000 tenure_type: RENT_PRIVATELY + brma: MAIDSTONE output: # The daughter is the householder's non-dependant; the lodger is no one's # (UC Sch 4 para 9(2)(d)), and the householder's household is not the @@ -183,8 +183,9 @@ # One housing cost contribution, £93.02 a month for 2025-26 (Sch 4 para # 14): 93.02 x 12 = 1,116.24. uc_non_dep_deductions: [1_116.24, 0, 0] - # Householder: min(10,000, 12,000) - 1,116.24 = 8,883.76. The lodger's - # element is not reduced: min(5,200, 6,000) = 5,200. + # Householder (two bedrooms, C): min(10,000, Maidstone two-bedroom rate + # 905 a month = 10,860) - 1,116.24 = 8,883.76. The lodger's element is + # not reduced: min(5,200, one-bedroom rate 9,000) = 5,200. uc_housing_costs_element: [8_883.76, 0, 5_200] # UC bedrooms: householder 1 + daughter 1 = 2 (the lodger adds none). LHA_allowed_bedrooms: [2, 1, 1] @@ -359,8 +360,8 @@ # The pensioner head is not eligible for UC, so no other UC claim has # treated the non-dependant as one (UC Sch 4 para 9(2)(f)): they count # in the co-tenant's claim. Bedrooms 1 + 1 = 2: C. Core rent 18,000 / 2 - # = 9,000, below the Maidstone two-bedroom rate (11,892.40); less one - # contribution of 93.02 x 12 = 1,116.24: 7,883.76. + # = 9,000, below the Maidstone two-bedroom rate (905 a month = 10,860); + # less one contribution of 93.02 x 12 = 1,116.24: 7,883.76. uc_non_dependants_counted: [false, true, false] LHA_allowed_bedrooms: [1, 2, 1] LHA_category: [B, C, A] diff --git a/policyengine_uk/utils/lha.py b/policyengine_uk/utils/lha.py index 066e24121e..e8ff422c60 100644 --- a/policyengine_uk/utils/lha.py +++ b/policyengine_uk/utils/lha.py @@ -420,10 +420,16 @@ def benunit_lha( ): """Look up an LHA measure for each benefit unit's BRMA and category. - ``category_variable`` names the category of accommodation: the Universal - Credit one by default, or ``housing_benefit_LHA_category``. Rent officers - determine one rate per BRMA and category, so the two benefits read the - same table; they differ only in which category applies to the renter. + ``universal_credit`` picks the determination: the weekly one under the + Rent Officers (Housing Benefit Functions) Order 1997, or the monthly one + under the Rent Officers (Universal Credit Functions) Order 2013. + ``category_variable`` names the category of accommodation to read it + for: the Universal Credit one by default, or + ``housing_benefit_LHA_category``. Each determination has one rate per + BRMA and category, so the benefits differ only in which determination + and which category they read: Housing Benefit the weekly table through + its own category (``housing_benefit_LHA_rate``), Universal Credit the + monthly table through ``LHA_category`` (``uc_LHA_cap``). """ parameters = benunit.simulation.tax_benefit_system.parameters table = lha_rates(parameters, period.start.year, universal_credit) diff --git a/policyengine_uk/variables/gov/dwp/BRMA_LHA_rate.py b/policyengine_uk/variables/gov/dwp/BRMA_LHA_rate.py index 2fdcb9cac4..4a57dbf046 100644 --- a/policyengine_uk/variables/gov/dwp/BRMA_LHA_rate.py +++ b/policyengine_uk/variables/gov/dwp/BRMA_LHA_rate.py @@ -5,10 +5,14 @@ class BRMA_LHA_rate(Variable): value_type = float entity = BenUnit - label = "LHA rate" + label = "LHA rate (weekly determination, Universal Credit category)" documentation = ( - "Weekly Housing Benefit Local Housing Allowance for the benefit unit's " - "Broad Rental Market Area and LHA category, annualised over 52 weeks" + "The weekly Local Housing Allowance determined for the benefit unit's " + "Broad Rental Market Area, read for its Universal Credit category " + "(LHA_category) and annualised over 52 weeks. Housing Benefit reads " + "the same weekly determination for its own category: see " + "housing_benefit_LHA_rate. Universal Credit uses the monthly " + "determination: see uc_LHA_cap." ) definition_period = YEAR unit = GBP @@ -18,7 +22,7 @@ class BRMA_LHA_rate(Variable): ] def formula(benunit, period, parameters): - """The determined Housing Benefit rate. + """The weekly determination, read for the Universal Credit category. The lower of the BRMA percentile rent and the weekly national maximum, raised to the rate of any smaller category and, from April 2024, to the diff --git a/policyengine_uk/variables/gov/dwp/housing_benefit_LHA_rate.py b/policyengine_uk/variables/gov/dwp/housing_benefit_LHA_rate.py index 4c1c8132ea..1c672d767d 100644 --- a/policyengine_uk/variables/gov/dwp/housing_benefit_LHA_rate.py +++ b/policyengine_uk/variables/gov/dwp/housing_benefit_LHA_rate.py @@ -22,7 +22,7 @@ class housing_benefit_LHA_rate(Variable): def formula(benunit, period, parameters): """The determined Housing Benefit rate for the HB category. - The same determination as ``BRMA_LHA_rate`` (the lower of the BRMA + The weekly determination that ``BRMA_LHA_rate`` reads (the lower of the BRMA percentile rent and the weekly national maximum, the anomalous-rate rule and, from April 2024, the 31 March 2020 minimum: Rent Officers (Housing Benefit Functions) Order 1997, Schedule 3B paragraphs 2, 3 From 12009399e17ff045624bf9300022ff56068b82d3 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sun, 4 Oct 2026 09:23:28 -0400 Subject: [PATCH 2/2] Vary the BRMA, not a dead LHA input; pin the one-bedroom UC rate From the review of this PR: - test_mixed_age_couple_properties drew a BRMA_LHA_rate input that no formula reads, so Hypothesis varied nothing. It now draws the household's BRMA from four North West areas, whose published rates span a similar range (one bedroom, 2026: GBP 88.60 to 178.36 a week). - lha_published_rates.yaml pins Maidstone one-bedroom for 2025-26: VOA GBP 172.60 a week (8,975.20 a year) and DWP GBP 750 a month (9,000), the UC rate the updated fixtures cite. - Rewrap a long docstring line in housing_benefit_LHA_rate. Co-Authored-By: Claude Opus 5.5 --- .../finance/benefit/family/lha_published_rates.yaml | 12 ++++++++++++ .../tests/test_mixed_age_couple_properties.py | 9 ++++++++- .../variables/gov/dwp/housing_benefit_LHA_rate.py | 12 ++++++------ 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/lha_published_rates.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/lha_published_rates.yaml index f9bc046807..d35faa158e 100644 --- a/policyengine_uk/tests/policy/baseline/finance/benefit/family/lha_published_rates.yaml +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/lha_published_rates.yaml @@ -64,6 +64,18 @@ BRMA_LHA_rate: 10_830.04 uc_LHA_cap: 10_860 +- name: Maidstone one bedroom, April 2025, held at April 2024 (SI 2025/5) + period: 2025 + absolute_error_margin: 0.01 + input: + brma: MAIDSTONE + LHA_category: B + benunit_rent: 100_000 + output: + # VOA: 172.60 a week x 52; DWP: 750.00 a month x 12. + BRMA_LHA_rate: 8_975.20 + uc_LHA_cap: 9_000 + - name: Maidstone two bedrooms, April 2026, held at April 2024 (SI 2026/5) period: 2026 absolute_error_margin: 0.01 diff --git a/policyengine_uk/tests/test_mixed_age_couple_properties.py b/policyengine_uk/tests/test_mixed_age_couple_properties.py index a6b08165e9..2034a45b3e 100644 --- a/policyengine_uk/tests/test_mixed_age_couple_properties.py +++ b/policyengine_uk/tests/test_mixed_age_couple_properties.py @@ -53,6 +53,12 @@ ] MIXED_AGE_COUPLE_SHAPES = {"mixed", "mixed_with_dependant"} TENURES = ["RENT_FROM_COUNCIL", "RENT_PRIVATELY", "OWNED_OUTRIGHT"] +NORTH_WEST_BRMAS = [ + "CENTRAL_GREATER_MANCHESTER", + "GREATER_LIVERPOOL", + "EAST_CHESHIRE", + "WEST_CUMBRIA", +] # The three eligibility formulas as they were before the mixed-age couple @@ -179,13 +185,14 @@ def cases(draw): "benunit": { "would_claim_uc": draw(st.booleans()), "would_claim_pc": draw(st.booleans()), - "BRMA_LHA_rate": draw(st.integers(4_000, 12_000)), }, "household": { "tenure_type": tenure, "rent": 0 if tenure == "OWNED_OUTRIGHT" else draw(st.integers(0, 12_000)), "savings": draw(st.sampled_from([0, 5_000, 20_000])), "region": "NORTH_WEST", + # The LHA varies with the BRMA's published rates. + "brma": draw(st.sampled_from(NORTH_WEST_BRMAS)), "council_tax": 1_500, }, } diff --git a/policyengine_uk/variables/gov/dwp/housing_benefit_LHA_rate.py b/policyengine_uk/variables/gov/dwp/housing_benefit_LHA_rate.py index 1c672d767d..cf0bdbab62 100644 --- a/policyengine_uk/variables/gov/dwp/housing_benefit_LHA_rate.py +++ b/policyengine_uk/variables/gov/dwp/housing_benefit_LHA_rate.py @@ -22,12 +22,12 @@ class housing_benefit_LHA_rate(Variable): def formula(benunit, period, parameters): """The determined Housing Benefit rate for the HB category. - The weekly determination that ``BRMA_LHA_rate`` reads (the lower of the BRMA - percentile rent and the weekly national maximum, the anomalous-rate - rule and, from April 2024, the 31 March 2020 minimum: Rent Officers - (Housing Benefit Functions) Order 1997, Schedule 3B paragraphs 2, 3 - and 3A), read for the category in HB Regulations 2006 reg 13D rather - than the Universal Credit one. + The weekly determination that ``BRMA_LHA_rate`` reads (the lower of + the BRMA percentile rent and the weekly national maximum, the + anomalous-rate rule and, from April 2024, the 31 March 2020 minimum: + Rent Officers (Housing Benefit Functions) Order 1997, Schedule 3B + paragraphs 2, 3 and 3A), read for the category in HB Regulations 2006 + reg 13D rather than the Universal Credit one. """ return ( benunit_lha(