Repository navigation
Conversation
Make x and y optional on ConstituencyImpactRecord and
LocalAuthorityImpactRecord. Both default to None and are left out of
every dump while None (pydantic's field-level exclude_if), so a record
without them validates and its JSON has no x or y keys rather than
"x": null. Apps deployed for older policyengine.py versions are never
rebuilt and keep sending both; their records still validate and keep
the values.
The gateway's /jobs/{job_id} route already drops null fields
(response_model_exclude_none), and the new endpoint tests check the
JSON it returns for a breakdown result and a national result whose
records carry no x or y, and for records that send them as null.
Raise the contract's pydantic floor to 2.12, the first release with
exclude_if, and relock the contract and the four packages whose locks
record its metadata; only that specifier line changes in each lock.
Regenerate the gateway's golden OpenAPI, which no longer lists x and y
as required.
Written red-green: the contract, gateway and generated-client tests
failed first because x and y were required ("Field required", and a
500 from the gateway), and because a null x or y was written to the
dump.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The executor no longer puts x or y on constituency or local-authority
records. It reads only each constituency's code and name from
policyengine.py's lookup, whose x and y columns it now ignores, and no
longer attaches the packaged local-authority positions. The check that
refused a published area without a map position ("missing lookup
coordinates") is removed; the code and lookup-name checks stay. With the
contract leaving absent x and y out of every dump, the result an app
deployed from now on returns has neither key.
Apps deployed for older policyengine.py versions are not rebuilt and
keep sending both, which the contract still accepts.
The beta integration check of a local-area breakdown now requires its
records to carry no x or y, since it runs against the app the same
deployment builds.
Written red-green: the executor tests failed first on the "missing
lookup coordinates" error and on x in the constituency and
local-authority records, and the breakdown check's new cases failed
with "DID NOT RAISE".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The executor no longer ships coordinates_lad22.csv or coordinates_lad23.csv. Nothing reads their x and y any more, but they were also the only list of each boundary version's authority codes, so codes_lad22.csv and codes_lad23.csv replace them with a single code column holding exactly the same codes in the same order. UKLocalAuthorityResources now carries those codes per boundary version (codes) and returns one version's names with names_for, which replaces metadata_for. The load-time checks are unchanged apart from the dropped coordinate comparison: names must exactly cover both lists, and each version's distinguishing codes must match its list. LAD22 and LAD23 detection reads the names and the distinguishing code sets, so it is unchanged. The Stage 12 static runtime file smoke reports lad22_codes and lad23_codes in place of lad22_coordinates and lad23_coordinates, with the same expected counts (374 and 361). A test pins each version's codes to a SHA-256 of the codes the coordinate files listed, and new tests check the loader refuses a repeated code, a list with other distinguishing codes, and a list with x and y columns. Written red-green: the tests failed first because the resources had no codes or names_for, the package still held the coordinate files and no code lists, and the smoke reported the coordinate keys. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
It said map positions are "now" drawn by the front end, which depends on an app change that has not shipped. Say that results carry no map positions and that the app draws them from its own layout files. Docs only; no behaviour changes, so there is no test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 tasks done
This was referenced Oct 28, 2026
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 #918
Same-Repository Draft PR Check
PolicyEngine/policyengine-sim-api, not a personal fork.Summary
New results stop carrying
xandy(each area's integer map position) on UK constituency and local-authority records, and the executor stops shippingcoordinates_lad22.csvandcoordinates_lad23.csv. The contract keeps acceptingxandy, because apps deployed for older .py versions are never rebuilt and keep sending them.Three commits, each with one behavioural change, and a fourth that rewords one sentence of the migration doc:
xandyonConstituencyImpactRecordandLocalAuthorityImpactRecorddefault toNoneand use pydantic's field-levelexclude_if, so a record without them validates and every dump leaves the keys out instead of writingnull. Records with integerxandystill validate and keep them. The contract's pydantic floor rises to 2.12, the first release withexclude_if; the contract and the four packages whose locks record its metadata are relocked, and only that specifier line changes in each lock. The gateway's golden OpenAPI no longer listsxandyas required.xory.codes_lad22.csvandcodes_lad23.csv(onecodecolumn) replace the coordinate files, with exactly the same codes in the same order.UKLocalAuthorityResourcescarriescodesper boundary version andnames_for(...)replacesmetadata_for(...). The Stage 12 static runtime file smoke reportslad22_codesandlad23_codes.How the JSON loses the keys
output.model_dump(mode="json")fromSimulationOutputBuilder.serializeandLocalAreaBreakdownBuilder.serialize; Stage 12 stores the same dump as its aggregate.JobStatusResponseand/jobs/{job_id}serializes it withresponse_model_exclude_none=True, which already drops null fields.exclude_if, the dump itself has noxory, so neither the Modal result, the Stage 12 aggregate nor the gateway JSON carries the keys. The gateway tests check the response JSON for a breakdown and a national result whose records omitxandy, and for records that send them asnull.Boundary-version detection
detect_uk_local_authority_boundary_versionreads the names file and the two hard-coded distinguishing code sets, which this PR doesn't change. Each version's full code list, whichnames_forand the "not part of the detected boundary version" check use, now comes from the code files. The load-time checks stay: the names must exactly cover both lists, and each version's distinguishing codes must match its list. A test pins each list to a SHA-256 of the codes the coordinate files listed (374 for LAD22, 361 for LAD23), and new tests check the loader refuses a repeated code, a list with another version's distinguishing code, and a list withxandycolumns.Deployment code and environment variables
The Modal images change content only:
add_static_runtime_filescopies the wholestatic_runtime_filesdirectory, which now holdsnames.csvand the two code lists.src/modal/v2_static_runtime_files_smoke.pychanges only the keys it reports. No environment variable is added, removed or read differently. For reference, the smoke's wiring is unchanged:MODAL_TOKEN_IDandMODAL_TOKEN_SECRET: GitHub Actions secrets, set in theRun image smokesstep'senv:inpr-image-smoke.yml.PE_UK_PRIVATE_HF_READ_TOKEN: GitHub Actions secret, set in the same step;.github/scripts/modal-image-smoke.shwrites it to the Modal secretpe-uk-private-hf-read-tokenasHUGGING_FACE_TOKEN, which the smoke function attaches throughhf_secret(required_keys=["HUGGING_FACE_TOKEN"]).Compatibility
Every .py version the gateway already routes to keeps its app and keeps sending
xandy; the contract accepts both. Removing the fields from the contract is the stacked PR above this one, which must not merge until no routed version still sends them.Testing
Written red-green: for each commit the tests were written first and seen failing for the expected reason, then the code was written and both were committed together.
Field requiredforxandyand on anullxandyin the dump; the gateway returned 500 for records withoutxandy; the generated client did not read such a breakdown as aLocalAreaBreakdownOutput.xin the constituency and local-authority records; the breakdown check's new cases failed withDID NOT RAISE.codesornames_for, the package still held the coordinate files, and the smoke reported the coordinate keys.Commands run (focused, locally):
uv run --frozen pytestontest_macro_output_uk_geography.py,test_local_area_breakdown.pyandtest_gateway_models.py(contract);test_endpoints.pyandtest_openapi_golden.py(gateway);test_openapi.py(entry);test_simulation_output_geographic.py,test_simulation_output_builder.py,test_uk_local_authority_metadata.py,test_stage12_modal_app.py,test_smoke_app.py,test_stage12_runtime.py,test_stage12_output_planning.py,test_stage12_worker_validation.pyandtest_modal_bundle_image.py(executor).python -m policyengine_simulation_gateway.generate_openapi; the diff removesxandyfrom both records'requiredlists and updates the constituency description../scripts/generate-clients.sh, thenuv run --frozen pytest tests/ -m "not requires_gcp and not beta_only"inpolicyengine-apis-integ: everything passed excepttest_ping, which needs a gateway running on localhost.make check(ruff and Pyright in every package),python -m scripts.rules pyright --sync projects/policyengine-simulation-executor,python scripts/run_quality_guards.pywithGITHUB_BASE_REF=main,python -m scripts.rules render(no changes) and.github/scripts/check-uv-locks.sh.🤖 Generated with Claude Code