From ccd7e6d32209091fe8c0e07af290a03b4d19c595 Mon Sep 17 00:00:00 2001 From: Vahid Ahmadi Date: Wed, 25 Mar 2026 13:46:12 +0000 Subject: [PATCH 1/2] Fix private rent target and add social rent calibration target MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Private rent: update from 4.7m England-only renters to 5.4m UK-wide, and from £1,400/mo to ONS PRHI £1,374/mo (~£89bn total) - Social rent: new target from EHS 2023-24 mean £118/wk × 5.0m UK social renters (~£30.7bn total) - Wire up housing/rent_social in compute and loss matrix dispatch - Add unit tests for all housing targets Fixes #317 Co-Authored-By: Claude Opus 4.6 --- changelog.d/fixed/317.md | 1 + .../targets/build_loss_matrix.py | 6 +- policyengine_uk_data/targets/compute/other.py | 5 +- .../targets/sources/housing.py | 45 ++++++++--- .../tests/test_housing_targets.py | 77 +++++++++++++++++++ 5 files changed, 121 insertions(+), 13 deletions(-) create mode 100644 changelog.d/fixed/317.md create mode 100644 policyengine_uk_data/tests/test_housing_targets.py diff --git a/changelog.d/fixed/317.md b/changelog.d/fixed/317.md new file mode 100644 index 00000000..738028e7 --- /dev/null +++ b/changelog.d/fixed/317.md @@ -0,0 +1 @@ +Fix private rent target to use UK-wide figures and add social rent calibration target (#317). diff --git a/policyengine_uk_data/targets/build_loss_matrix.py b/policyengine_uk_data/targets/build_loss_matrix.py index 6cf6e1ab..bace2957 100644 --- a/policyengine_uk_data/targets/build_loss_matrix.py +++ b/policyengine_uk_data/targets/build_loss_matrix.py @@ -287,7 +287,11 @@ def _compute_column(target: Target, ctx: _SimContext, year: int) -> np.ndarray | return compute_vehicles(target, ctx) # Housing - if name in ("housing/total_mortgage", "housing/rent_private"): + if name in ( + "housing/total_mortgage", + "housing/rent_private", + "housing/rent_social", + ): return compute_housing(target, ctx) # Land and property wealth (ONS National Balance Sheet) diff --git a/policyengine_uk_data/targets/compute/other.py b/policyengine_uk_data/targets/compute/other.py index b86418cf..a4f209fb 100644 --- a/policyengine_uk_data/targets/compute/other.py +++ b/policyengine_uk_data/targets/compute/other.py @@ -27,13 +27,16 @@ def compute_vehicles(target, ctx) -> np.ndarray: def compute_housing(target, ctx) -> np.ndarray: - """Compute housing targets (mortgage, private rent).""" + """Compute housing targets (mortgage, private rent, social rent).""" name = target.name if name == "housing/total_mortgage": return ctx.pe("mortgage_capital_repayment") + ctx.pe( "mortgage_interest_repayment" ) tenure = ctx.sim.calculate("tenure_type", map_to="household").values + if name == "housing/rent_social": + is_social = (tenure == "RENT_FROM_COUNCIL") | (tenure == "RENT_FROM_HA") + return ctx.pe("rent") * is_social return ctx.pe("rent") * (tenure == "RENT_PRIVATELY") diff --git a/policyengine_uk_data/targets/sources/housing.py b/policyengine_uk_data/targets/sources/housing.py index 98cff99b..4a11198a 100644 --- a/policyengine_uk_data/targets/sources/housing.py +++ b/policyengine_uk_data/targets/sources/housing.py @@ -1,19 +1,34 @@ """Housing affordability targets. -Total mortgage payments and private rent from ONS/English Housing Survey. +Total mortgage payments, private rent, and social rent. Sources: -- ONS PRHI: https://www.ons.gov.uk/economy/inflationandpriceindices/bulletins/privaterentandhousepricesuk/january2025 -- English Housing Survey mortgage data +- ONS PRHI Feb 2026: UK avg private rent £1,374/month + https://www.ons.gov.uk/economy/inflationandpriceindices/bulletins/privaterentandhousepricesuk/march2026 +- English Housing Survey 2023-24: avg social rent £118/week + https://www.gov.uk/government/statistics/english-housing-survey-2023-to-2024-rented-sectors +- EHS + devolved stats: ~5.4m private renters, ~5.0m social renters UK-wide """ from policyengine_uk_data.targets.schema import Target, Unit -# Estimated total annual housing costs (£) -# Private rent: avg £1,400/month × 12 × 4.7m private renters -# Mortgage: avg £1,100/month × 12 × 7.5m owner-occupiers with mortgage -_PRIVATE_RENT_TOTAL = 1_400 * 12 * 4.7e6 -_MORTGAGE_TOTAL = 1_100 * 12 * 7.5e6 +# Private rent: ONS PRHI UK avg £1,374/month × 5.4m UK private renters +_PRIVATE_RENT_TOTAL = 1_374 * 12 * 5.4e6 # ~£89bn + +# Mortgage: avg £1,100/month × 7.5m owner-occupiers with mortgage +_MORTGAGE_TOTAL = 1_100 * 12 * 7.5e6 # ~£99bn + +# Social rent: EHS 2023-24 mean £118/week × 52 × 5.0m UK social renters +_SOCIAL_RENT_TOTAL = 118 * 52 * 5.0e6 # ~£30.7bn + +_EHS_REF = ( + "https://www.gov.uk/government/statistics/" + "english-housing-survey-2023-to-2024-rented-sectors" +) +_ONS_RENT_REF = ( + "https://www.ons.gov.uk/economy/inflationandpriceindices/" + "bulletins/privaterentandhousepricesuk/march2026" +) def get_targets() -> list[Target]: @@ -24,14 +39,22 @@ def get_targets() -> list[Target]: source="ons", unit=Unit.GBP, values={2025: _MORTGAGE_TOTAL}, - reference_url="https://www.ons.gov.uk/economy/inflationandpriceindices/bulletins/privaterentandhousepricesuk/january2025", + reference_url=_ONS_RENT_REF, ), Target( name="housing/rent_private", variable="rent", - source="ons", + source="ehs", unit=Unit.GBP, values={2025: _PRIVATE_RENT_TOTAL}, - reference_url="https://www.ons.gov.uk/economy/inflationandpriceindices/bulletins/privaterentandhousepricesuk/january2025", + reference_url=_ONS_RENT_REF, + ), + Target( + name="housing/rent_social", + variable="rent", + source="ehs", + unit=Unit.GBP, + values={2025: _SOCIAL_RENT_TOTAL}, + reference_url=_EHS_REF, ), ] diff --git a/policyengine_uk_data/tests/test_housing_targets.py b/policyengine_uk_data/tests/test_housing_targets.py new file mode 100644 index 00000000..15bdb8df --- /dev/null +++ b/policyengine_uk_data/tests/test_housing_targets.py @@ -0,0 +1,77 @@ +"""Tests for housing affordability calibration targets. + +See: https://github.com/PolicyEngine/policyengine-uk-data/issues/317 +""" + +from policyengine_uk_data.targets.sources.housing import ( + _MORTGAGE_TOTAL, + _PRIVATE_RENT_TOTAL, + _SOCIAL_RENT_TOTAL, + get_targets, +) + + +# ── Target values ──────────────────────────────────────────────────── + + +def test_private_rent_total_in_range(): + """Private rent total should be £70bn-£110bn (5.4m × ~£1,374/mo).""" + assert 70e9 < _PRIVATE_RENT_TOTAL < 110e9 + + +def test_social_rent_total_in_range(): + """Social rent total should be £20bn-£40bn (5.0m × ~£118/wk).""" + assert 20e9 < _SOCIAL_RENT_TOTAL < 40e9 + + +def test_mortgage_total_in_range(): + """Mortgage total should be £80bn-£120bn (7.5m × ~£1,100/mo).""" + assert 80e9 < _MORTGAGE_TOTAL < 120e9 + + +def test_social_rent_less_than_private(): + """Total social rent should be less than total private rent.""" + assert _SOCIAL_RENT_TOTAL < _PRIVATE_RENT_TOTAL + + +# ── Target structure ───────────────────────────────────────────────── + + +def test_get_targets_returns_three(): + """get_targets() should return mortgage, private rent, and social rent.""" + targets = get_targets() + assert len(targets) == 3 + + +def test_target_names(): + """Target names should match expected values.""" + names = {t.name for t in get_targets()} + assert names == { + "housing/total_mortgage", + "housing/rent_private", + "housing/rent_social", + } + + +def test_all_targets_have_2025(): + """All housing targets should have a value for 2025.""" + for t in get_targets(): + assert 2025 in t.values, f"{t.name} missing value for 2025" + + +def test_social_rent_target_variable(): + """Social rent target should use the rent variable.""" + targets = get_targets() + social = next(t for t in targets if t.name == "housing/rent_social") + assert social.variable == "rent" + + +def test_targets_in_registry(): + """Housing targets should appear in the global registry.""" + from policyengine_uk_data.targets import get_all_targets + + targets = get_all_targets(year=2025) + names = {t.name for t in targets} + assert "housing/rent_social" in names + assert "housing/rent_private" in names + assert "housing/total_mortgage" in names From 3802497cea4fab4797c91ab8908b28cbac1d95d7 Mon Sep 17 00:00:00 2001 From: Vahid Ahmadi Date: Fri, 27 Mar 2026 15:10:21 +0000 Subject: [PATCH 2/2] Move changelog entry to correct location Co-Authored-By: Claude Opus 4.6 --- changelog.d/{fixed => }/317.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename changelog.d/{fixed => }/317.md (100%) diff --git a/changelog.d/fixed/317.md b/changelog.d/317.md similarity index 100% rename from changelog.d/fixed/317.md rename to changelog.d/317.md