Repository navigation
Use disclosure-controlled UK geography outputs for constituency and local authority impacts - #860
Open
PavelMakarchuk wants to merge 2 commits into
Open
PavelMakarchuk wants to merge 2 commits into
PavelMakarchuk wants to merge 2 commits into
Conversation
… local authorities The executor built UK constituency and local-authority results with policyengine's deprecated compute_uk_constituency_impacts and compute_longwise_uk_geography_impacts. They apply no minimum sample, so a small area with negative-income households shows an exploding relative change (Cardiff West: -£192 average, +1372% relative), and their output is not disclosure-controlled for licensed survey microdata. Both levels now go through compute_uk_geography_disclosure in one release, nested in regions and the nation, with the constituency/local-authority overlap acknowledged. Blocking findings are resolved by withholding the suggested published cells; region and national reference cells are never withheld, and a release that cannot be resolved publishes no area. The share families, which the output does not publish, are withheld up front. Contract: existing fields keep their types. A withheld area, or one whose relative change is undefined, is left out of the list. Published records gain optional disclosure_tier and 90% interval fields. An empty list is now valid. Stage 12 keeps region_code_oa and source_household_id. The local-authority breakdown no longer strips weights, so the last recorded weight-stripping exception is removed and its baseline is 0. Fixes #859 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A containment finding without a suggested cell names only the outer area. When that area is withheld and the public areas of the other level inside it add up to it (Orkney and Shetland is Orkney plus Shetland), the loop withheld the outer cell again, made no progress, and published no area at all. It now withholds the inner areas that lie wholly inside the outer one, read from the household area pairs as the release reads them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #859
Summary
UK constituency and local-authority results now come from policyengine's disclosure-controlled
compute_uk_geography_disclosure(PolicyEngine/policyengine.py#605, closing PolicyEngine/policyengine.py#589). The executor no longer calls the deprecatedcompute_uk_constituency_impactsorcompute_longwise_uk_geography_impacts. The UK region output is not part of this contract, so nothing else used the deprecated path. The pinned policyengine 6.7.5 already has the disclosure API, so the pin does not change.Why
The deprecated functions apply no minimum sample. They are also not disclosure-controlled for UK Data Service licensed microdata. Production examples (reform
gov.hmrc.income_tax.rates.uk[0].rate0.20 → 0.21, 2026):country/wales): Cardiff West shows −£192 average but +1372% relative, which implies a baseline mean income of about −£14. Caerphilly shows −5.6%, against −0.1% to −2.3% in the neighbouring seats.How it works
compute_uk_geography_disclosure_output(new,simulation_output_geographic.py) runs one policyengine release forconstituencyandlocal_authority. policyengine nests both levels in regions and the nation. The two levels overlap without nesting, so the call acknowledgesunchecked_overlap. The release still checks each area against the areas inside it and around it.source_household_idnow raises a clear error. This matches the existing missing-area-code errors (Admit UK datasets without area codes only when local-area runs are routed, and guard the weight-matrix fallback #727).region_code_oaandsource_household_idin the household frame it stores, next to the two area codes._unweighted_household_frame, the last recorded exception tosim.microdata.weight-stripping-confined. Its baseline goes from 1 to 0 (rules/change, code-owned).Contract: suppressed cells and uncertainty
This change is backward compatible. Every existing field keeps its type.
null, so stored results still validate):disclosure_tier:reportedorapproximate.approximatemeans a thin effective sample: the value is kept, but the interval understates the uncertainty.average_household_income_change_lower/_upperandrelative_household_income_change_lower/_upper: the 90% bootstrap interval, rounded outward.average_household_income_changeandrelative_household_income_changeare now rounded to the order of their standard error. Both now leave out changes of £1 or less a year, as the layer does.populationis still the weighted household count.nullvalues plus a flag, for two reasons. The app's hex map would colour anullas zero. The v2constituency_impactsandlocal_authority_impactstables in policyengine-api have non-null float columns.The golden OpenAPI changes only by these optional fields and by the docstrings.
App follow-up (PolicyEngine/policyengine-app-v2)
societyWideOutputNormalizer.tskeys the rows by name and copiesx,yand both changes. A missing area gives one entry less, and the new fields are ignored, so nothing crashes. An all-withheld level gives{}, and the map then shows "No constituency data available". Follow-ups:disclosure_tier(approximate) and the intervals in the tooltip.Known limits and risks
source_household_idafter its SPI and capital-gains copies are stacked, so up to four rows of one survey household count separately (eFRS source_household_id doesn't name the survey household: SPI copies and capital-gains clones count as separate households policyengine-uk-data#591, fixed by Prebuild single-year datasets into the Modal simulation image (temporary) #597 for 1.58.2 and later, which is not in the bundle yet). This gets better when the bundle moves to a rebuilt eFRS or to Microcosm (Certify the Microcosm UK national and local-area pair with policyengine-uk 2.132.6 policyengine.py#685). The change is still much safer than no floor.DisclosureErrorand the run fails. The run does not drop the breakdown silently. This matches Admit UK datasets without area codes only when local-area runs are routed, and guard the weight-matrix fallback #727.Tests
tests/test_simulation_output_geographic.pyruns the real disclosure layer on weighted frames. It checks the following:source_household_idor missing area codes raise.tests/test_simulation_output_builder.pychecks that both segments share one computation, and that US runs never compute it.tests/test_stage12_output_planning.pycovers the two new retained columns.libs/policyengine-simulation-contract/tests/test_macro_output_uk_geography.pychecks that old records validate and that new fields validate. It also checks thatwithheldis not a valid record tier, that values stay required, and that an empty list is valid.ruff@0.15.22format and check;scripts/run_quality_guards.py;Reviewer test instructions
cd projects/policyengine-simulation-executor && uv sync --extra test && uv run pytest tests/test_simulation_output_geographic.py tests/test_simulation_output_builder.py tests/test_stage12_output_planning.py tests/test_microdata_weight_boundaries.py -qcd libs/policyengine-simulation-contract && uv run pytest tests/test_macro_output_uk_geography.py -qcd projects/policyengine-simulation-gateway && uv run pytest tests/test_openapi_golden.py -q, then review the golden diff. It has only optional fields and docstrings.country/walesanduk, 2026). Then check these points:UK geography disclosure findings remainwarning.🤖 Generated with Claude Code