Skip to content

Refresh social rent target to EHS 2024-25 vintage - #455

Open
vahid-ahmadi wants to merge 2 commits into
mainfrom
fix-social-rent-target-vintage
Open

Refresh social rent target to EHS 2024-25 vintage#455
vahid-ahmadi wants to merge 2 commits into
mainfrom
fix-social-rent-target-vintage

Conversation

@vahid-ahmadi

Copy link
Copy Markdown
Collaborator

Partial fix for #454.

The problem

All three housing targets are set for 2025, but they are anchored to vintages two years apart:

target basis vintage
housing/rent_private GBP 1,374/month x 5.4m ONS PRHI, Feb 2026
housing/rent_social GBP 118/week x 5.0m EHS 2023-24

EHS 2024-25 has since published a GBP 129/week mean social rent (local authority GBP 120, housing association GBP 134), so the social target was roughly 10% stale against its own target year.

The change

Social basis moves to GBP 129/week, taking the target from ~GBP 30.7bn to ~GBP 33.5bn, with the reference updated to the EHS 2024-25 chapter. Adds a test pinning the implied weekly mean to the published figure so the target cannot silently go stale against a newer EHS release again.

Also documents a limitation found while investigating #454: these are totals only, so a household-count error and an offsetting mean error cancel. At dataset 1.40.3 the private target is met at 1.04x — but with 6.99m renters at GBP 256/week against a basis of 5.4m at GBP 317/week. Count and mean are each wrong by ~30% and ~19% in opposite directions and the total matches anyway, so housing/rent_private currently validates nothing. Pairing the totals with count constraints, or targeting mean rent by tenure, would fix that; I have left it out here because it changes the loss matrix and cannot be verified without a full rebuild.

What this does not fix

Modelled social rents remain 1.55x the target (5.77m households at GBP 159/week against a basis of 5.0m at GBP 118/week; 1.15x on count, 1.35x on mean). Refreshing the target does not move the data — that is a calibration convergence question, and it is what makes the benefit cap apply to 222k households against DWP's 111k (PolicyEngine/policyengine-uk#1818). Both remain open on #454.

Testing

test_housing_targets.py passes (10 tests). No pipeline or dataset rebuild involved.

vahid-ahmadi and others added 2 commits August 13, 2026 09:28
The three housing targets are all set for 2025, but the social rent basis
used the English Housing Survey 2023-24 mean (GBP 118/week) while the
private rent basis used ONS PRHI from February 2026. Anchoring targets for
the same year to vintages two years apart understated the social basis by
roughly 10% against its own target year. EHS 2024-25 reports GBP 129/week
(local authority GBP 120, housing association GBP 134), moving the target
from ~GBP 30.7bn to ~GBP 33.5bn.

Adds a test pinning the implied weekly mean to the published EHS figure, so
the target cannot silently go stale against a newer release again, and
documents on the module that these are totals only: at dataset 1.40.3 the
private target is met at 1.04x with 6.99m renters at GBP 256/week against a
basis of 5.4m at GBP 317/week, so a count error and an offsetting mean error
cancel and the target validates nothing.

This does not by itself correct modelled social rents, which sit 1.55x above
the target (5.77m households at GBP 159/week). That is a calibration
convergence question tracked in #454.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The previous test asserted that _SOCIAL_RENT_TOTAL / 5.0e6 / 52 fell
between 125 and 135 - recomputing the constant from the constant. It
could not detect a newer EHS release, which is what its docstring and the
PR body claimed. Replaced with an explicit pin on named basis components,
documented as a review guard rather than a staleness check.

Also drops unrelated uv.lock churn from the branch.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@vahid-ahmadi

Copy link
Copy Markdown
Collaborator Author

Pushed two corrections to this PR after an independent audit found problems with it.

1. The staleness test did not test staleness. test_social_rent_basis_matches_published_ehs_mean asserted _SOCIAL_RENT_TOTAL / 5.0e6 / 52 fell between 125 and 135 — recomputing the constant from the constant. It could only fail if someone edited the constant without editing the test, and could never detect a newer EHS release, which is what its docstring and my PR body both claimed. Replaced with an explicit pin on named basis components (_SOCIAL_RENT_WEEKLY_MEAN, _SOCIAL_RENT_HOUSEHOLDS), documented honestly as a review guard. A real staleness check would have to fetch EHS, as ons_tenure.py and ons_households.py do for their sources.

2. Dropped unrelated uv.lock churn that had crept into the branch.

Corrections to the PR description

Direction of effect, which I failed to state. Raising the target from GBP 30.7bn to GBP 33.5bn moves it toward the modelled GBP 47.6bn (1.55x → 1.42x). On the next rebuild the calibration will push modelled social rents slightly up, marginally worsening the over-capping that #454 exists to fix. My claim that "refreshing the target does not move the data" holds only until a rebuild.

Stale residual figures. The "what this does not fix" section quotes 1.55x against a GBP 118/week basis — that is the state before this PR. Post-merge it is ~1.42x against GBP 129/week.

Vintages are still not aligned. The stated goal was targets anchored to the same year; after this PR it is EHS 2024-25 against ONS PRHI February 2026 for a 2025 target — still about a year apart, with the private side now the more anomalous of the two.

"The private rent target validates nothing" was too strong. Household-count targets on the same population already exist (targets/sources/ons_tenure.py), and datasets/local_areas/constituencies/devolved_housing.py already pairs count with total for Wales and Scotland — the exact remedy I described as future work. A 30% count error is a convergence failure, not an undetectable one.

Context that has emerged since

The deeper causes are now filed separately and are larger than this PR: #456 (the loss function under-penalises overshoots; the gradient peaks at ratio 2.0 and declines, and sre(1.55x) is numerically indistinguishable from sre(0.645x), so the optimiser is indifferent between the miss and its own fix) and #457 (dwelling stock matched against household counts; unsatisfiable household-type targets). Also #452, where UC take-up is applied to all benefit units rather than entitled ones.

This PR remains a small, correct provenance fix. It is not a fix for the 1.55x miss, and I would not want it read as one.

@vahid-ahmadi
vahid-ahmadi requested a review from MaxGhenis August 13, 2026 13:05
@vahid-ahmadi

Copy link
Copy Markdown
Collaborator Author

@MaxGhenis review request — small provenance fix, but please read the corrections comment above before merging.

Two things I would want a second opinion on:

  1. It nudges the data the wrong way. Raising the social rent target from GBP 30.7bn to GBP 33.5bn moves it toward the modelled GBP 47.6bn, so the next rebuild will push modelled social rents slightly up — marginally worsening the over-capping in Benefit cap applies to 2x too many households (5.3% of UC vs 1.6% administrative) policyengine-uk#1818. The target value is more correct; the immediate effect on the known problem is adverse.
  2. It does not fix the miss, and I would not want it read as doing so. The real causes are Calibration loss under-penalises overshoots and silently drops targets #456 (the loss under-penalises overshoots past 2x) and Target definitions: dwellings matched to households, and household-type targets that cannot all be satisfied #457 (dwellings matched to household counts, unsatisfiable household-type targets).

Happy for this to wait behind those if you would rather not move the basis twice.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant