Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion LEGISLATIVE_REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -415,7 +415,22 @@ yet migrated (governed by `uc_migration.housing_benefit` rates).
Source: SI 2006/213 reg.70 ([`uksi/2006/213/regulation/70`](https://www.legislation.gov.uk/uksi/2006/213/regulation/70))

Maximum HB = 100% of eligible rent (subject to LHA caps for private renters). The model does
not compute LHA caps; it uses reported rent directly.
not compute LHA caps; it uses reported rent directly as eligible rent for all tenures.

LHA cap is implemented in `src/variables/benefits.rs` via `lha_monthly_cap()`. When `params.lha`
is present and enabled, eligible rent for private renters (TenureType::RentPrivately) is capped
at the regional LHA rate for the household's bedroom entitlement, computed by
`lha_bedroom_entitlement()` (implements UC Regs 2013 Sch.4 / HB Regs 2006 Sch.B1).

The LHA rates are stored as region×category monthly amounts in `params.lha.rates_monthly`.
Since the FRS suppresses BRMA identifiers for disclosure reasons, the model uses region-level
30th percentile rates (derived from the VOA list of rents via policyengine-uk, uprated using the
ONS Index of Private Housing Rental Prices). This understates within-region variation but
captures the main regional gradient. The 2025/26 baseline uses rates frozen at the 2024/25
reset level (April 2024 reset to 30th percentile, re-frozen April 2025).

Reform scenarios can vary `params.lha.private_rent_index` (multiplicative uprating of all rates,
e.g. 1.10 = 10% increase) without changing the underlying rate table.

### 7.2 Applicable Amount

Expand Down
1 change: 1 addition & 0 deletions changelog.d/added/lha-cap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Implement Local Housing Allowance cap for private renters on HB and UC: bedroom entitlement logic (UC Regs 2013 Sch.4), region×category monthly rate table derived from VOA rent data uprated to 2024/25, and a `private_rent_index` scalar for modelling LHA unfreeze or rate changes.
27 changes: 27 additions & 0 deletions parameters/2025_26.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,33 @@ wealth_tax:
threshold: 10000000.0
rate: 0.01

lha:
# Local Housing Allowance rates for 2025/26.
# LHA was re-frozen in April 2025 at the 2024/25 reset rates.
# 2024/25 rates: 30th percentile of private rents in each BRMA, reset from freeze.
# Source: VOA list of rents (2019–2020 data), uprated to April 2024 using ONS Index of
# Private Housing Rental Prices (index: April 2020 = 109.0, April 2024 ≈ 125.0;
# uprate factor = 1.1468). Aggregated to GOR (region) as FRS suppresses BRMA identifiers.
# Categories: [A=shared, B=1-bed, C=2-bed, D=3-bed, E=4+bed] — monthly amounts (£).
# Row order: NE, NW, Yorkshire, E.Midlands, W.Midlands, E.ofEngland, London,
# S.East, S.West, Wales, Scotland, N.Ireland.
enabled: true
private_rent_index: 1.0 # 1.0 = frozen (no uprating). Reform: set >1.0 to unfreeze.
rates_monthly:
- [323.01, 428.86, 486.06, 566.12, 766.23] # North East
- [332.95, 457.44, 566.12, 657.60, 860.11] # North West
- [323.01, 451.72, 543.26, 600.41, 857.72] # Yorkshire
- [372.71, 486.06, 600.41, 686.18, 914.92] # East Midlands
- [390.10, 514.63, 629.03, 743.38, 972.12] # West Midlands
- [360.23, 629.03, 771.95, 886.35, 1252.29] # East of England
- [580.97, 1200.81, 1469.61, 1735.40, 2172.93] # London
- [392.58, 737.66, 914.92, 1086.46, 1543.90] # South East
- [390.10, 571.83, 737.66, 874.87, 1143.66] # South West
- [299.41, 419.92, 514.63, 566.12, 686.18] # Wales
- [323.01, 468.91, 543.26, 629.03, 886.35] # Scotland
- [273.42, 361.97, 428.86, 514.63, 566.12] # Northern Ireland


labour_supply:
# OBR labour supply elasticities (Slutsky decomposition).
# Source: OBR (2023) "Costing a cut in National Insurance contributions:
Expand Down
20 changes: 20 additions & 0 deletions src/engine/entities.rs
Original file line number Diff line number Diff line change
Expand Up @@ -560,6 +560,26 @@ impl Region {
}
}

/// LHA region index for rate table lookup (0–11, matching rates_monthly row order).
/// Order: NE=0, NW=1, Yorks=2, EM=3, WM=4, EofE=5, London=6, SE=7, SW=8,
/// Wales=9, Scotland=10, NI=11.
pub fn to_lha_region_idx(&self) -> usize {
match self {
Region::NorthEast => 0,
Region::NorthWest => 1,
Region::Yorkshire => 2,
Region::EastMidlands => 3,
Region::WestMidlands => 4,
Region::EastOfEngland => 5,
Region::London => 6,
Region::SouthEast => 7,
Region::SouthWest => 8,
Region::Wales => 9,
Region::Scotland => 10,
Region::NorthernIreland => 11,
}
}

pub fn name(&self) -> &'static str {
match self {
Region::NorthEast => "North East",
Expand Down
67 changes: 67 additions & 0 deletions src/parameters/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ pub struct Parameters {
/// Annual wealth tax (hypothetical — disabled by default).
#[serde(default)]
pub wealth_tax: Option<WealthTaxParams>,
/// Local Housing Allowance cap parameters.
/// When present, caps eligible rent for private renters at the regional LHA rate
/// for their bedroom entitlement category. Authority: HB Regs 2006 reg.13D.
#[serde(default)]
pub lha: Option<LhaParams>,
/// OBR labour supply response elasticities.
/// When enabled, the Slutsky-decomposition elasticities from OBR (2023) are applied
/// to estimate intensive-margin labour supply responses to tax-benefit reforms.
Expand Down Expand Up @@ -564,6 +569,68 @@ impl Default for LabourSupplyParams {
}
}

/// Local Housing Allowance (LHA) parameters.
///
/// LHA caps the eligible rent for private renters on HB/UC at the 30th percentile of
/// local rents in each Broad Rental Market Area (BRMA), by bedroom entitlement category
/// (A = shared, B = 1-bed, C = 2-bed, D = 3-bed, E = 4+-bed).
///
/// Since the FRS suppresses BRMA identifiers for disclosure control, we use region-level
/// 30th percentile rates derived from the VOA rent data (same underlying source as BRMA
/// rates, aggregated to GOR). This understates within-region variation but captures the
/// main regional gradient.
///
/// Rate history:
/// - Frozen: April 2020 – March 2024 (SI 2019/1303)
/// - Reset to 30th percentile: April 2024 (gov.uk announcement, 28 Nov 2023)
/// - Re-frozen: April 2025 (OBR EFO March 2025 assumption)
///
/// Uprating: `private_rent_index` uprates the baseline rates for reform scenarios;
/// e.g. setting `private_rent_index: 1.10` models a 10% LHA increase.
/// For the baseline (frozen), this field is 1.0.
///
/// Source: VOA list of rents (via policyengine-uk), uprated using ONS Index of Private
/// Housing Rental Prices (base: April 2020 = 100, April 2024 ≈ 114.7).
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LhaParams {
/// Whether LHA cap is active. If false, eligible rent = actual rent (pre-reform default).
#[serde(default = "default_true_lha")]
pub enabled: bool,
/// Multiplier applied to all rates for reform scenarios (e.g. 1.1 = 10% increase).
/// For baseline frozen scenario this is 1.0.
#[serde(default = "default_one")]
pub private_rent_index: f64,
/// LHA rates by region (11 regions matching Region enum) then by bedroom category
/// (index 0=shared/A, 1=1-bed/B, 2=2-bed/C, 3=3-bed/D, 4=4+bed/E).
/// Values are monthly amounts (£).
/// Order: NorthEast, NorthWest, Yorkshire, EastMidlands, WestMidlands,
/// EastOfEngland, London, SouthEast, SouthWest, Wales, Scotland, NorthernIreland.
pub rates_monthly: Vec<[f64; 5]>,
}

fn default_true_lha() -> bool { true }
fn default_one() -> f64 { 1.0 }

impl LhaParams {
/// Return the monthly LHA cap (£) for a given region and bedroom entitlement.
///
/// `region_idx` maps to Region::to_rf_code() (0=NE, 1=NW, 2=Yorks, 3=EM, 4=WM,
/// 5=EofE, 6=London, 7=SE, 8=SW, 9=Wales/NI, 10=Scotland, 11=NI).
/// `bedrooms` is the LHA bedroom entitlement (1–4+), or 0 for shared accommodation.
/// Returns `None` if rates_monthly is empty or region_idx out of range.
pub fn monthly_cap(&self, region_idx: usize, bedrooms: u32) -> Option<f64> {
let row = self.rates_monthly.get(region_idx)?;
let col = match bedrooms {
0 => 0, // shared accommodation (Category A)
1 => 1,
2 => 2,
3 => 3,
_ => 4, // 4+ bedrooms → Category E
};
Some(row[col] * self.private_rent_index)
}
}

/// Annual wealth tax parameters (hypothetical — disabled by default).
///
/// No current UK wealth tax exists. These parameters support modelling
Expand Down
Loading
Loading