Repository navigation
Conversation
anth-volk
force-pushed
the
v2plan/1f-115-catalog-metadata-fields
branch
from
October 26, 2026 13:37
18d014a to
81bd99b
Compare
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. The v2 integration
check runs it with the installed-catalog test.
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>
anth-volk
force-pushed
the
v2plan/1f-115-catalog-metadata-fields
branch
from
October 26, 2026 14:49
81bd99b to
1f69e91
Compare
…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>
This branch was successfully 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 must not merge before its PolicyEngine.py bump commit. The Public API pins PolicyEngine.py 6.10.0, which does not publish catalog format 2's fields. On it, every catalog extraction stops with "the installed PolicyEngine.py does not publish catalog format 2's fields", the seed job's catalog publication during a deploy included. PolicyEngine.py 6.11.0 does not contain the fields either. The bump commit waits for:
The PRs stacked above this one wait with it.
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 rebased onto the final #4020, which contains
master(now pinning PolicyEngine.py 6.10.0).Schema. Revision
f5c1d9b4685b(onfa7fa1035444) 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_COMPATIBILITY=1) compares every new field with/{country}/metadatafor the US and the UK. Enum labels are compared with their types. The v2 integration check runs it in its existing installed-catalog step.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.What the PolicyEngine.py bump commit must change
policyengine[models]==6.10.0inpyproject.tomlanddocker/Dockerfile→ the release containing Publish the model metadata the Public API can only read through a private method policyengine.py#701, thenuv lock.changelog.d/update-policyengine-bundle-<version>.changed.md("Update the PolicyEngine bundle to .").tests/integration/test_v2_catalog_installed.py: theparameter_nodescount, from 29,307 to 30,214 (the bracket schedules Update PolicyEngine US to 0.434.0 #701 publishes as nodes), and the comment above the counts that names the 6.10.0 bundle. With Update PolicyEngine US to 0.434.0 #701's branch installed on 6.10.0's country packages, every other count and assertion in that test already holds.tests/integration/test_v2_catalog_publication_qualification.py. It also callsextract_installed_catalog, so it fails on the locked 6.10.0 too, and it passes with Update PolicyEngine US to 0.434.0 #701's branch installed (measurement below).Production-scale publication with format 2 fields
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 or added. The v2 integration workflow only adds a test file to an existing step, whose
RUN_V2_CATALOG_COMPATIBILITYstays a literal"1".Testing
GitHub Actions runs on this branch are cancelled at the user's request, so the 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