Repository navigation
Conversation
This was referenced Oct 20, 2026
Catalog format 2 adds what the app's v3 workspace reads from
GET /{country}/metadata and PolicyEngine.py's public records omit:
- model versions: entities (key, plural, label, description, person flag,
roles) and basic inputs;
- variables: value type, definition period, unit, module, input and
hidden-input flags, and enum value labels;
- parameters: the label from the parameter's own metadata, period, and
economy and household flags;
- bracket schedules (Core ParameterScale nodes) as parameter nodes.
The extractor reads them from the country system PolicyEngine.py built
its model from, through the model version's _load_system hook, without
importing Core or a country package. The generated revision adds nullable
columns. Publication never rewrites a stored version: a version published
before format 2 keeps null values and is compared on its original fields,
and the next PolicyEngine.py version is published with everything. The
metadata routes and GET /v2/model-catalog return the new fields.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Format 2 adds parameter and variable fields to the model-catalog document and raises METADATA_CATALOG_FORMAT, which the ETag carries, so a client holding a format 1 body revalidates it as changed instead of receiving 304. No test checked that raise: with the format left at 1, every test passed. The new test lists the fields of each document format and compares them with the fields a built document serves, and checks that the ETag names the format. It fails with the format left at 1, and it fails whenever a field is added or removed without a new format. Test-only change: the format was already raised, so the test passed when written; it was confirmed to fail with METADATA_CATALOG_FORMAT set to 1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The extractor refuses a variable whose Core enum members do not match the possible values PolicyEngine.py lists, and refuses economy, household, is_input_variable or hidden_input values that are not booleans. The pull request describes these checks, but no test exercised them: removing either check left every test passing. Test-only change: the checks already existed, so the tests passed when written; they were confirmed to fail with both checks removed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Format 2 replaced the keyword construction of MetadataCatalogParameterRow with positional unpacking of the ten selected columns. That ties the dataclass field order to the SELECT order: reordering either one would swap values silently, including the adjacent economy and household booleans. Build each row from its named columns instead. Refactor-only change: behavior is unchanged, so no test can fail first; the existing catalog document tests cover the mapped values. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
anth-volk
force-pushed
the
apiv2/1e-catalog-fields
branch
from
October 20, 2026 13:25
f91cdcd to
8642397
Compare
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 #3967
Summary
Catalog format 2 stores, publishes and serves the metadata the app's v3 workspace reads from
GET /{country}/metadatathat the v2 catalog lacked. With #3969 this completes the plan's item 1E-1 (v2 metadata gaps for v3); the gap list is below.entities(key, plural, label, description,is_person, roles),basic_inputs, andcatalog_format.value_type(float,int,bool,str,dateorEnum),definition_period,unit,module_name,is_input_variable,hidden_input,possible_value_labels.metadata_label(the label in the parameter's own metadata, without PolicyEngine.py's generated fallback),period,economy,household.ParameterScalenodes), which PolicyEngine.py omits: 866 US and 41 UK, almost all with labels v3 shows as schedule names.The metadata routes (
/v2/variables,/v2/parameters,/v2/parameters/children,/v2/tax-benefit-models/by-country,/v2/tax-benefit-model-versions) and/v2/model-catalogreturn the new fields; the model-catalog document format number becomes 2, so its ETag changes.Where the extractor reads them (decision for review)
PolicyEngine.py's public records (6.7.5, and 6.8.0) do not carry these fields. The extractor reads them from the country system PolicyEngine.py built its model from, through the model version's
_load_systemhook, by attribute. It imports neither PolicyEngine Core nor a country package, soapi.catalog.extractor-import-isolationstill holds, but_load_systemis not part of PolicyEngine.py's public interface: a release that removes it makes extraction fail with an error (the seed job would stop the deploy). The alternative is to publish these fields in PolicyEngine.py's publicVariable,ParameterandTaxBenefitModelVersionrecords, release it, and read them from there; that change is outside this repository. Values the extractor reads are validated as strictly as the existing fields (type checks, length bounds, enum labels must match the possible values, every public variable and parameter must exist in the country system).Published versions are never rewritten
The generated revision adds nullable columns only. Publication still inserts a version only when it is absent. A version published before format 2 has
catalog_formatnull, keeps null values, and later publications of it compare only the fields it was published with (no bracket-schedule nodes, no format 2 columns). So the first deploy succeeds with the running version unchanged, and the next PolicyEngine.py version published (for example through the automatic bundle-update PRs) carries the new fields. Until then the routes return them as null, which the app's adapter handles with v1's defaults.Gap list: what v3 reads from
GET /{country}/metadataand its v2 sourceFound by following every import from the
(workspace)route group incalculator-appand reading each metadata access, then comparing with the v2 routes./v2/model-catalog(#3969)/v2/parameters/children)/v2/model-catalog(#3969)economyandhouseholdflags (which parameters search and the tree offer)labelas v1 shows it (own metadata label, else the last path segment)metadata_labelhere; the app applies v1's fallbackperiod/v2/parameter-values; the earliest start is0001-01-01(v1:0000-01-01)/v2/model-catalog; the app restores v1's spellingunit,descriptionadds,subtractsdata_typeisstrfor enumsvalue_typeherepossible_value_labelshereis_person; roles are not read)entitiesherebasic_inputshere/v2/regions(code,label,region_type,state_code,state_name)metadata_time_periods,current_law_idversion(readiness)policyengine_versionNot filled here (each needs a decision or belongs to another group):
constituency/Aldershot), while PolicyEngine.py names them by code (constituency/E14…) and builds them only from lookups in a private Cloud Storage bucket. v1'scountry/niiscountry/northern_irelandin v2. This changes which region strings economy reports send, so it belongs with the v2 economy work (2A, 3A).spm.available). v3 requires it before a US household calculation; it depends on the certified SPM artifact, not the catalog. The v2 household calculation path (2B-2, 3B-2) should publish it.addsorsubtractsname a parameter (237 US, 4 UK). PolicyEngine.py resolves the parameter to its 2025 list; v1 gave the parameter path, which v3 evaluates for the household's year.version. v1 gave the country package version (policyengine-us 2.2.1); v2 gives the PolicyEngine.py version (6.7.5). v3 compares it with a report's model version to offer a rerun; that comparison moves with v2 report provenance (3A-3).variableModules,modelled_policies,economy_options.datasets, variablecategoryandindexInModule, parameterindexInModule.Measured parity
The real 6.7.5 catalog was published to a disposable Postgres, served through the v2 routes, converted by the app's adapter (PolicyEngine/policyengine-app-v2#1347) and compared field by field with v1's documents:
2021-06-31, stored as2021-06-30; all 29,310 node labels equal; the only missing v1 nodes are 8 empty ones (__pycache__and similar).adds5,930 of 6,167 (point 4 above).Alembic
72fab86fa97d("store catalog presentation metadata") afterfbb7488ebe8a, generated withuv run alembic -c alembic-v2.ini revision --autogenerateand only formatted. It adds 14 nullable columns and drops them on downgrade.V2_ALEMBIC_HEAD_REVISIONis updated.alembic check(no drift), downgrade one revision, upgrade again;tests/integration/test_alembic_v2_lifecycle.pypasses.countriestable) and 1G (1G-2, Drop the empty simulation-domain tables and retired association columns from the v2 schema #3961) also add v2 revisions. Only one may be open against the chain head at a time: whichever merges second rebases and regenerates its revision on the new head (updatingdown_revision,V2_ALEMBIC_HEAD_REVISIONand the revision-chain test).API contract change
Additive: new optional fields on
MetadataVariable,MetadataParameterSummaryandMetadataModelVersionDetail, and newMetadataEntityandMetadataEntityRoleschemas. Nothing calls them until the app's switch (plan item 4A-1); PolicyEngine/policyengine-app-v2#1346 and #1347 add the unused app code.What stays on the v1 routes
Everything.
GET /{country}/metadataand its callers are unchanged.Testing
tests/unit/v2/test_catalog_extraction.py: presentation metadata read through the country system; parameters without flags default to economy and household as v1 does; enum labels follow the possible values; bracket schedules are added; a model without its country system, records missing from it, enum labels that do not match the possible values, and flags that are not booleans stop extraction.tests/unit/v2/test_metadata_service.py,test_metadata_catalog.py: the routes and the catalog document return the new fields, and versions published before format 2 read as null; the fields a built document serves match the field list recorded for its document format, which the ETag names (this fails if a field changes without a new format).tests/unit/v2/test_catalog_publication.py,test_alembic_v2.py: both comparison modes build matching column lists; the revision is generated, reversible and the chain head.tests/integration/test_v2_catalog_publication.py): new versions store the fields and schedules and republish cleanly; a version published before format 2 is compared and left unchanged (this test fails if publication compares the new fields for such a version); a format 2 version with a changed flag is refused.RUN_V2_CATALOG_COMPATIBILITY=1 RUN_V2_CATALOG_PUBLICATION_QUALIFICATION=1: the installed-catalog test (parameter nodes now 30,213, including the 907 schedules) and the full publication qualification pass.uv run pytest tests/unit/v2 tests/contract tests/unit/test_migration_contract_artifacts.py -q: 882 passed; v2 Postgres integration suites: 49 passed;mypy,ruff format --check .,ruff check .,run_quality_guards.pypassed;export_migration_contracts.py: no changes.tests/unit/test_query_parameters.py tests/unit/test_asgi_factory.py: 968 passed;test_alembic_v2_lifecycle.py,test_v2_catalog_publication.py,test_v2_metadata_routes.pyandtest_v2_user_identity_persistence.pyagainst a disposable Postgres: 42 passed;mypy,ruff format,ruff check,run_quality_guards.pypassed;export_migration_contracts.py: no changes.Merge order and deployment
seed-v2-database.yml, called frompush.ymlfor staging and production). No new environment variables or secrets: the seed job keeps usingV2_DATA_WRITE_DATABASE_URLand the migration job its existing URL; the workflows are unchanged.Possible conflicts
tests/integration/test_v2_catalog_installed.py.🤖 Generated with Claude Code