Repository navigation
Store the metadata v3 reads in the v2 catalog from PolicyEngine.py's public records (catalog format 2) - #4023
Merged
Merged
Conversation
anth-volk
force-pushed
the
v2plan/1f-115-catalog-metadata-fields
branch
from
October 26, 2026 13:37
18d014a to
81bd99b
Compare
anth-volk
force-pushed
the
v2plan/1f-115-catalog-metadata-fields
branch
from
October 26, 2026 14:49
81bd99b to
1f69e91
Compare
This was referenced Oct 26, 2026
Draft
anth-volk
force-pushed
the
v2plan/1f-115-catalog-metadata-fields
branch
from
November 2, 2026 14:06
f803106 to
294c50f
Compare
anth-volk
changed the base branch from
v2plan/1f-123-region-reference-files
to
master
November 2, 2026 14:08
anth-volk
force-pushed
the
v2plan/1f-115-catalog-metadata-fields
branch
from
November 2, 2026 14:52
294c50f to
75f4a31
Compare
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #4023 +/- ##
===========================================
+ Coverage 51.51% 88.13% +36.62%
===========================================
Files 267 279 +12
Lines 15026 16380 +1354
Branches 2399 2668 +269
===========================================
+ Hits 7740 14437 +6697
+ Misses 6647 1282 -5365
- Partials 639 661 +22 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
PolicyEngine.py 6.12.0 publishes the model metadata catalog format 2 reads: entities, basic inputs, variable flags, enum labels, modules, parameter-named parts, parameter flags and bracket schedules. The rest of this change builds on it. Its country packages are unchanged from 6.11.1 (policyengine-core 3.33.3, policyengine-us 2.2.1, policyengine-uk 2.151.0). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Catalog format 2 stores the metadata the app's v3 workspace reads, which PolicyEngine.py now publishes in its public records: - model versions: the catalog format, entities and basic inputs; - variables: the input and hidden-input flags, the module, the parameter named by adds or subtracts, and enum value labels; - parameters: the economy and household flags. Revision f5c1d9b4685b was autogenerated against the v2 configuration and adds every column as nullable with no default, so catalog versions published before format 2 keep them empty. It writes no data. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…g format 2
The extractor now reads, only from PolicyEngine.py's public records:
- each model version's entities (key, plural, person flag) and basic
inputs;
- each variable's input and hidden-input flags, module, the parameter path
its adds or subtracts names, and its enum values' declared labels;
- each parameter's economy and household flags.
Bracket schedules already arrive as public parameter nodes, with their
stored label or none. Extraction fails with a specific message when the
public model publishes no entities or basic inputs, has other than one
person entity, names a basic input or a parameter path that is not
published, gives enum labels that do not match the possible values or are
not JSON scalars, or gives a flag that is not a boolean.
normalize_model_metadata normalizes this metadata apart from the certified
data release, and an opt-in parity test compares every new field for the
US and the UK with what /{country}/metadata builds. CI does not run it,
because it extracts every variable and parameter of the installed models;
set RUN_V2_CATALOG_METADATA_PARITY=1 to run it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Publication now stages and inserts each model version's catalog format, entities and basic inputs, each variable's input and hidden-input flags, module, parameter paths and enum values (as ordered value and label objects), and each parameter's economy and household flags. Publication never rewrites a stored version. When the same PolicyEngine.py version is published again, a version stored before catalog format 2 (a null catalog_format) is compared without format 2's fields, so it keeps them empty, and a version stored with format 2 must match on them too. The Stage 9 metadata document describes the format, and the changelog fragment covers the whole change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Extraction now checks first that the installed PolicyEngine.py publishes catalog format 2's fields on the model version, every variable and every parameter, and fails naming each missing one. On 6.7.5 it used to stop at the first enum variable with "enum values do not match its possible values", which hid the cause. An enum label that is not JSON-compatible, such as NaN, now fails naming its variable and value. Entities or basic inputs that are present but not a sequence fail with "must be a sequence", and the Stage 9 document says that an empty basic-input list is stored as empty. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The parity test compared enum labels with ==, which treats 1, 1.0 and True as equal, so a label stored with the wrong type passed. It now compares each label's type name as well. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The publication tests stored only null enum values and parameter paths, so the JSON inserts and the comparison on republication never ran on stored values. A fixture now adds an enum variable with a string and an integer label, and a variable whose adds and subtracts are named by parameters. The tests check that they are stored exactly, the integer label as an integer, that republishing the same catalog changes nothing, and that a changed enum label or parameter path fails without changing the stored rows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
TaxBenefitModelVersion.entities and Variable.enum_values were typed as lists of untyped dictionaries. They are now lists of the TypedDicts CatalogEntity (key, plural, is_person) and CatalogEnumValue (value and declared label), which match EntityRecord and EnumValueRecord, and publication builds the stored JSON through them, so readers of these columns get the keys and value types without checking the shape again. The column types are unchanged, so there is no schema change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The catalog fixed its own default datasets (populace_us_2024 and microcosm_uk_2024_25) and refused a release that named another. Each country's default dataset now comes from the installed PolicyEngine.py release's manifest. Publication still stops if the manifest names no default, if the release does not certify it, or if the release routes national regions to another dataset; those messages now name the datasets instead of a reviewed selection. Region scoping's argument is renamed to default_dataset, and the Stage 9 document and the changelog fragment describe the release's default. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…2's fields The Stage 9 document, the format 2 field check's docstring and its test named only 6.7.5 as a release without the fields; the locked 6.10.0 lacks them too. The Stage 9 regions table also names the UK local-area dataset as the one 6.10.0 uses, like the rows above it. Documentation only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…2 columns
The app's v3 household builder and breakdown need each variable's unit
(currency formatting) and definition period ("per month" or "per year"
labels, household validation), and PolicyEngine.py's public Variable
record carries both. The v2 catalog stored neither.
Revision f5c1d9b4685b is not merged yet, so it was regenerated with
autogenerate against the v2 configuration, keeping its ID through
--rev-id and its parent fa7fa1035444. It now adds 13 nullable columns
with no defaults, including variables.unit (128 characters) and
variables.definition_period (32 characters), and writes no data.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…py's public records
Extraction now reads each variable's unit and definition period from
PolicyEngine.py's public Variable record (both optional strings, at most
128 and 32 characters), and the format 2 field check names them when a
release does not publish them. 6.7.5 and the locked 6.10.0 already carry
these two, so on 6.10.0 the check still names only the other fields.
The parity test compares both with the unit and definitionPeriod that
/{country}/metadata builds for every US and UK variable.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…on is published Publication now stages and inserts each variable's unit and definition period, and a stored format 2 version must match on both when the same PolicyEngine.py version is published again; a version stored before format 2 keeps them empty. The Stage 9 document and the changelog fragment list both fields and say that 6.7.5 and the locked 6.10.0 already publish these two. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…names After the unit and definition period were added, the round-trip, republication, parity and extraction tests and the fixture helper still named only the earlier fields, so a failure did not say which field it covers. They now name the unit and period (the fixture helper becomes add_format_2_variable_records, with a docstring listing what each variable fills), and the staging-row test's list of every staged column name is called staged_column_names. Tests only; behavior is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Extraction wrote the length limits of the module name, the two parameter paths, the unit and the definition period a second time, beside the Variable model's max_length. It now reads each limit from the column that stores the field (VARIABLE_TEXT_LIMITS), so the two cannot drift apart, and a test feeds each field one character too many and checks that extraction stops with the field's name and limit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…6.10.0 The Public API now installs PolicyEngine.py 6.11.1. Like 6.10.0, it publishes only the variables' unit and definition period of catalog format 2's fields, and its release names the same default and local-area datasets, so only the version changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Catalog format 2's revision f5c1d9b4685b now follows fbb7488ebe8a, so the Stage 12A document no longer names the current head. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Public API now locks PolicyEngine.py 6.12.0, which publishes them, so 6.11.1 is one of the earlier releases that publish only the variables' unit and definition period. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
anth-volk
force-pushed
the
v2plan/1f-115-catalog-metadata-fields
branch
from
November 2, 2026 17:52
75f4a31 to
8196728
Compare
anth-volk
marked this pull request as ready for review
November 3, 2026 14:09
This branch was previously 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 #4022
Important
This PR is the base of a stack (#4042, #4046, #4049) on
master. It no longer sits on the region reference files PR (#4020) or the PRs below it: the format 2 work is ported onto master's region code.Its first commit bumps PolicyEngine.py to 6.12.0, the release containing PolicyEngine/policyengine.py#701, which publishes catalog format 2's fields. On 6.11.1 every catalog extraction stops with "the installed PolicyEngine.py does not publish catalog format 2's fields".
uv.locklocks 6.12.0, whose country packages are unchanged from 6.11.1 (policyengine-core 3.33.3, policyengine-us 2.2.1, policyengine-uk 2.151.0). Merging also waits for the simulation API to support that bundle (the "Ensure PolicyEngine bundle is supported by simulation API" check).Summary
Catalog format 2 stores the metadata the app's v3 workspace reads, taken only from PolicyEngine.py's public records (PolicyEngine/policyengine.py#701), never from a country package's tax-benefit system or a private PolicyEngine.py method. This branch is based on
master; its first commit pins PolicyEngine.py 6.12.0.Schema. Revision
f5c1d9b4685b(on master's countries revisionfb1a3e40d3d6) was autogenerated against the v2 configuration with no corrections. It adds nullable columns with no defaults, so catalog versions published earlier keep them empty, and it writes no data:tax_benefit_model_versions:catalog_format,entities,basic_inputs;variables:is_input_variable,hidden_input,module_name,adds_parameter_path,subtracts_parameter_path,enum_values,unit,definition_period;parameters:economy,household.entitiesandenum_valuesare typed withCatalogEntityandCatalogEnumValue.Extraction.
normalize_model_metadatareads entities (key, plural, person flag), basic inputs, variable flags and modules, the parameter path thataddsorsubtractsnames (beside the list PolicyEngine.py resolves, which stays), enum values with their declared labels (an integer label stays an integer), each variable's unit and definition period, and parameter flags.currency-USD) and the definition period (year,monthoreternity) are needed by the app's household builder and breakdown on v2: currency formatting, "per month" and "per year" labels, and household validation. PolicyEngine.py's publicVariablerecord carries both (6.7.5 and 6.10.0 already do).Default datasets come from PolicyEngine.py. By the user's decision, each country's default dataset is the
default_datasetthe installed PolicyEngine.py release names in its manifest. This PR removes the Public API's own list (REVIEWED_DEFAULT_DATASETS). Publication still stops if the manifest names no default, if the release does not certify it, or if the release routes national regions to another dataset.Storage and comparison. Publication stages and inserts the new fields. When the same PolicyEngine.py version is published again, a version stored before format 2 (null
catalog_format) is compared without the format 2 fields and keeps them empty; a format 2 version must match on them too. Publication never rewrites a stored version.Parity check.
tests/integration/test_v2_catalog_metadata_parity.py(opt-in throughRUN_V2_CATALOG_METADATA_PARITY=1) compares every new field with/{country}/metadatafor the US and the UK. Enum labels are compared with their types. CI does not run it: it loads the complete installed models, which CI no longer does since Replace the full-catalog integration tests with selected folders of the installed models #4150.Master's catalog tests. The value-shape catalog (
tests/fixtures/v2_catalog_value_shapes.py) gives its added variables and parameters the format 2 fields, and the installed-folders test (tests/integration/test_v2_catalog_installed_folders.py) passes the real models' entities and an empty basic-input list to extraction, since its reduced models publish no variables.Parity with
/{country}/metadataMeasured with PolicyEngine.py #701's branch installed locally (6.10.0 with the new fields). 0 mismatches in every field:
addssubtractsgovparameters (economy and household flags)Every
govbracket schedule is a catalog node, and its stored label matches the label/{country}/metadatashows when one is stored.With #701's branch installed, the full installed extraction succeeds:
populace_us_2024; 51 states and 436 congressional districts as row filters onpopulace_us_2024_acs_local. Region data verification against the real files passes.microcosm_uk_2024_25; the national region and four nations on it; 650 constituencies and 361 local authorities as row filters onmicrocosm_uk_2024_25_dense. Region data verification of the UK files could not run locally, for lack of read access to the UK data, so it first runs in the seed job.The PolicyEngine.py bump
policyengine[models]==6.12.0inpyproject.tomlanddocker/Dockerfileand adds the bundle update's changelog fragment,changelog.d/update-policyengine-bundle-6.12.0.changed.md.uv.lockfor 6.12.0. The installed-catalog counts thattests/integration/test_v2_catalog_installed.pypinned no longer need updating: Replace the full-catalog integration tests with selected folders of the installed models #4150 removed that test.Production-scale publication with format 2 fields
Measured on the earlier stack, which included the region reference files PR (#4020). Without it, regions come from PolicyEngine.py's region registry, as on
master. #4150 has since removed this test frommaster; the numbers below are kept as a record.RUN_V2_CATALOG_PUBLICATION_QUALIFICATION=1 pytest tests/integration/test_v2_catalog_publication_qualification.py, with #701's branch installed and a disposable Postgres 17, passed:Migration notes
/{country}/metadataand the other v1 routes are unchanged.Environment variables
None read by the application, and no workflow changes. The parity test reads
RUN_V2_CATALOG_METADATA_PARITY, which no CI job sets; a developer sets it to1to run the test locally.Testing
On
masterafter #4150, with PolicyEngine.py 6.12.0 installed. The stack was rebased ontomasterat0f2477293. Its heads are75f4a31ea(this PR),718795501(#4042),33f77cba2(#4046) and9905cfe07(#4049). Each head passed:scripts/run_quality_guards.py, withscripts/export_migration_contracts.pyproducing no changes;test_v2_catalog_installed_folders.pyand the value-shape publication test.The parity test also passed with
RUN_V2_CATALOG_METADATA_PARITY=1: 8 of 8. One review of the conflict resolutions found no remaining mistakes; its two wording and authorship findings are fixed.The table below records the original commits' runs, before this PR moved onto
master; the revision then followed the dropped saved-policy renamefa7fa1035444. Onmasterit followsfb1a3e40d3d6, and each commit's changed tests pass there; the integration tests ran against a local Postgres, apart from the parity and installed-catalog tests, which need 6.12.0.At the time, GitHub Actions runs on this branch were cancelled at the user's request, so that evidence is local. Commits are listed from the bottom of the branch.
Review. Two review rounds. The first found no substantiated bugs, and its quality findings are fixed. The second reviewed the default-dataset change and also found no substantiated bugs; its findings on this description and the documentation are fixed. A third review, of the unit and definition period, found no substantiated bugs; its quality findings (test names, and limits written twice) are fixed.
d66267943columns and revisionKeyError: 'catalog_format'; revision chain and new-revision tests (no child offa7fa1035444); lifecycle testKeyErrorwith the revision removedalembic checkclean14673dc46extraction and parity testCATALOG_FORMAT,normalize_model_metadata); workflow test did not find the parity test07b7b0cfestorage and comparisonb8a75d12cnaming missing fieldsa94b2e2d4label types in the parity testcca58e31around-trip and change detectione46611fcatyped JSON columnsImportError: CatalogEntity494f43465default datasets from the release1f69e9144documentation: 6.10.0 also lacks the fields5c565e61funit and definition period columns; revisionf5c1d9b4685bregenerated with autogenerate (--rev-id f5c1d9b4685b, parentfa7fa1035444), now 13 columnsKeyError: 'unit'; revision testassert 11 == 13; lifecycle test on the previous revisionKeyError: 'unit'alembic checkclean; 1,129 v2 unit tests376846411extracting the unit and definition periodunit, missing-field and type messages); parity testAttributeError: 'VariableRecord' object has no attribute 'unit'7c0eac46fstoring and comparing them[3, 6, …]; stored values missing; a changed unit or period "DID NOT RAISE"7e0fb9518test and fixture names that name the unit and periodf803106eetext limits from the column lengthsImportError: VARIABLE_TEXT_LIMITS; the over-long cases passed before too, as the limits already existedOn the final branch, under the locked PolicyEngine.py 6.10.0:
make testset: 3,521 passed;alembic check: no new operations;scripts/run_quality_guards.py,python -m scripts.rules validateandguard: clean;scripts/export_migration_contracts.py: no changes;extract_installed_catalogfail with the missing-fields message:test_v2_catalog_installed.py(1 test),test_v2_catalog_publication_qualification.py(1 test) and every parity test (8). That is expected until the bump commit.With #701's branch installed:
make testset: 3,507 passed (before the final rebase);test_v2_catalog_installed.py: differs only in theparameter_nodescount listed above.There are no inline
# noqasuppressions and no changes underrules/.🤖 Generated with Claude Code