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
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Read local-authority ONS income uprating assumptions from `policyengine-uk` parameters when available, while keeping a compatibility fallback for the current released dependency.
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,11 @@
get_la_uc_targets,
)
from policyengine_uk_data.targets.sources.local_la_extras import (
get_ons_income_uprating_factors,
load_ons_la_income,
load_household_counts,
load_tenure_data,
load_private_rents,
UPRATING_NET_INCOME_BHC_2020_TO_2025,
UPRATING_HOUSING_COSTS_2020_TO_2025,
)


Expand Down Expand Up @@ -127,6 +126,9 @@ def create_local_authority_target_matrix(
hbai_net_income = sim.calculate("equiv_hbai_household_net_income").values
hbai_net_income_ahc = sim.calculate("equiv_hbai_household_net_income_ahc").values
housing_costs = hbai_net_income - hbai_net_income_ahc
income_bhc_uprating_factor, housing_costs_uprating_factor = (
get_ons_income_uprating_factors(int(time_period))
)

matrix["ons/equiv_net_income_bhc"] = hbai_net_income
matrix["ons/equiv_net_income_ahc"] = hbai_net_income_ahc
Expand All @@ -135,12 +137,12 @@ def create_local_authority_target_matrix(
ons_merged["equiv_net_income_bhc_target"] = (
ons_merged["net_income_bhc"]
* ons_merged["households"]
* UPRATING_NET_INCOME_BHC_2020_TO_2025
* income_bhc_uprating_factor
)
ons_merged["equiv_housing_costs_target"] = (
ons_merged["net_income_bhc"] * ons_merged["households"]
- ons_merged["net_income_ahc"] * ons_merged["households"]
) * UPRATING_HOUSING_COSTS_2020_TO_2025
) * housing_costs_uprating_factor
ons_merged["equiv_net_income_ahc_target"] = (
ons_merged["equiv_net_income_bhc_target"]
- ons_merged["equiv_housing_costs_target"]
Expand Down
36 changes: 33 additions & 3 deletions policyengine_uk_data/targets/sources/local_la_extras.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,19 @@
import logging

import pandas as pd
from policyengine_uk.system import system

from policyengine_uk_data.targets.sources._common import STORAGE

logger = logging.getLogger(__name__)

# Uprating factors from FYE 2020 to 2025 (OBR Nov 2025 EFO)
UPRATING_NET_INCOME_BHC_2020_TO_2025 = 1985.1 / 1467.6
UPRATING_HOUSING_COSTS_2020_TO_2025 = 103.5 / 84.9
# Compatibility fallback until policyengine-uk ships the matching parameters.
_LEGACY_ONS_INCOME_UPRATING_FACTORS = {
2025: (
1985.1 / 1467.6,
103.5 / 84.9,
),
}

_REF_ONS_INCOME = (
"https://www.ons.gov.uk/employmentandlabourmarket/peopleinwork/"
Expand All @@ -35,6 +40,31 @@
)


def get_ons_income_uprating_factors(year: int) -> tuple[float, float]:
"""Return BHC-income and housing-cost uprating factors for LA ONS targets."""
econ_assumptions = system.parameters.gov.economic_assumptions
local_authority_targets = getattr(econ_assumptions, "local_authority_targets", None)

if local_authority_targets is not None:
ons_income = local_authority_targets.ons_income
net_income_bhc = ons_income.net_income_bhc_uprating_factor(f"{year}-01-01")
housing_costs = ons_income.housing_costs_uprating_factor(f"{year}-01-01")
if net_income_bhc is None or housing_costs is None:
raise ValueError(
f"policyengine-uk local authority target uprating factors are "
f"incomplete for {year}."
)
return float(net_income_bhc), float(housing_costs)

if year in _LEGACY_ONS_INCOME_UPRATING_FACTORS:
return _LEGACY_ONS_INCOME_UPRATING_FACTORS[year]

raise ValueError(
f"No ONS LA income uprating factors configured for {year}. "
"Update policyengine-uk or add a compatibility fallback."
)


def load_ons_la_income() -> pd.DataFrame:
"""Load ONS income estimates by local authority.

Expand Down
71 changes: 71 additions & 0 deletions policyengine_uk_data/tests/test_local_la_extras.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
from types import SimpleNamespace

import pytest

from policyengine_uk_data.targets.sources import local_la_extras


def test_get_ons_income_uprating_factors_uses_legacy_2025_fallback():
assert local_la_extras.get_ons_income_uprating_factors(2025) == (
1985.1 / 1467.6,
103.5 / 84.9,
)


def test_get_ons_income_uprating_factors_prefers_policyengine_uk_parameters(
monkeypatch,
):
params = SimpleNamespace(
gov=SimpleNamespace(
economic_assumptions=SimpleNamespace(
local_authority_targets=SimpleNamespace(
ons_income=SimpleNamespace(
net_income_bhc_uprating_factor=lambda _: 1.5,
housing_costs_uprating_factor=lambda _: 1.25,
)
)
)
)
)

monkeypatch.setattr(
local_la_extras,
"system",
SimpleNamespace(parameters=params),
)

assert local_la_extras.get_ons_income_uprating_factors(2025) == (1.5, 1.25)


def test_get_ons_income_uprating_factors_raises_for_unknown_year(monkeypatch):
params = SimpleNamespace(
gov=SimpleNamespace(economic_assumptions=SimpleNamespace())
)
monkeypatch.setattr(
local_la_extras,
"system",
SimpleNamespace(parameters=params),
)

with pytest.raises(ValueError, match="No ONS LA income uprating factors"):
local_la_extras.get_ons_income_uprating_factors(2024)


def test_get_ons_income_uprating_factors_raises_for_partial_upstream_tree(
monkeypatch,
):
params = SimpleNamespace(
gov=SimpleNamespace(
economic_assumptions=SimpleNamespace(
local_authority_targets=SimpleNamespace()
)
)
)
monkeypatch.setattr(
local_la_extras,
"system",
SimpleNamespace(parameters=params),
)

with pytest.raises(AttributeError):
local_la_extras.get_ons_income_uprating_factors(2025)
Loading