Repository navigation
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This was referenced Oct 20, 2026
…switch
V3_API_ROUTES in libs/flagship/v3ApiRoutes.ts is the one place v3
chooses between the PolicyEngine API's v1 and v2 routes. It is a
constant, 'v1', so production behaves as before; the switch pull request
changes it to 'v2'.
With 'v2':
- the (workspace) route group loads model metadata through
fetchV2Metadata: /v2/tax-benefit-models/by-country, then
/v2/model-catalog and /v2/regions for that exact catalog version,
converted to the v1 metadata shape (government parameters, v1 labels,
region types and field names), plus the catalog's model and parameter
UUIDs. The legacy calculator stays on GET /{country}/metadata;
- saved reforms in the account (signed in or signed out) become
/v2/policies policies, and /v2/user-policies rows point at them with
policy_id; edits make a new policy and repoint the row. Rows that hold
only a v1 policy (legacy_policy_id) still open, reading their
parameters from GET /{country}/policy/{id} until the one-time
migration links them.
Reforms kept in this browser (no accounts) keep making v1 policies.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The v2 catalog stores the earliest start as 0001-01-01 (Python's first year); v1 metadata wrote 0000-01-01, which the parameter history chart leaves out. fetchV2Metadata now returns v1's spelling. A comparison of the real 6.7.5 catalog through the v2 routes with v1's metadata found this the only systematic difference in parameter values. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Two parts of the v2 code had no test that fails without them: - The calculator's provider tree picks the metadata routes from the selected route group. Only the selection function was tested, and with V3_API_ROUTES at 'v1' its workspace case could not tell v1 from v2. metadataRoutesForRouteGroup now takes the v3 routes as an optional argument (defaulting to V3_API_ROUTES, so behaviour is unchanged), its tests name 'v2', and a new test renders CalculatorProviders on a workspace page and on a legacy calculator page. - useReformStore hands the store the v2 catalog from the metadata store. A new test saves a reform through the hook and checks the POST /v2/policies request names that catalog. The workspace case of the selection tests failed first (it returned 'v1' for 'v2'). The provider test fails when MetadataBootstrap stops passing the routes to useFetchMetadata, and the hook test fails when useReformStore stops passing getV2Catalog. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Making legacy_policy_id optional for v2 rows left both IDs optional in UserPolicyCreateBody, so a body naming neither compiled although the API refuses it. The body is now a union that needs policy_id, legacy_policy_id, or both. The 'v1' | 'v2' union was also declared twice (MetadataRoutes and V3ApiRoutes); V3ApiRoutes now reuses MetadataRoutes. The new type test failed `bun run typecheck` first: its @ts-expect-error on a body with neither ID was unused. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The reform store module's header still said every save makes a v1 policy. It now describes the browser store, the account store on v1 policies before the switch, and the account store on /v2/policies after it. - V1_POLICY_JSON_GC_TIME also sets how long cached v2 policies are kept, so it is now SAVED_POLICY_GC_TIME. - Comments in v3ApiRoutes and the v2 metadata adapter call policyengine-api the Public API. Documentation and naming only, so no test could fail first. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
anth-volk
force-pushed
the
apiv2/1e-v3-metadata-reforms-on-v2
branch
from
October 20, 2026 13:25
493c22e to
99be8fa
Compare
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 #1343
Summary
Adds the v3 workspace's v2 code for model metadata and saved reforms, merged unused (plan item 1E-2). Production behaviour does not change: v3 keeps calling the v1 routes until the switch pull request (plan item 4A-1).
The one place v3 chooses v1 or v2
app/src/libs/flagship/v3ApiRoutes.ts:It is a constant, not a runtime flag or an environment variable, so every build behaves the same. Everything in this PR that can use v2 reads it: the metadata loader for the
(workspace)route group, v3's metadata readiness and retry, and the saved-reform store. Later v2 work for v3 (saved households, reports, household calculations) can key off the same constant so the switch stays one line.What the switch (4A-1) will change
The switch PR changes
'v1'to'v2'inv3ApiRoutes.tsand the matching expectation inapp/src/tests/unit/libs/flagship/v3ApiRoutes.test.ts. From then on:(workspace)route group,MetadataBootstrap(calculator-app/src/app/[countryId]/providers.tsx) loads model metadata withfetchV2Metadata:GET /v2/tax-benefit-models/by-country/{country}selects the catalog version, thenGET /v2/model-catalogandGET /v2/regionsread that exact version. The result is converted to the shapeGET /{country}/metadatahas today (government parameters only, v1's labels and region types, v1's variable and entity field names), plus the catalog's model and parameter UUIDs. v3 counts as ready only once metadata from the v2 routes has loaded, and its retry buttons ask the v2 routes. The legacy calculator, the(calculator)route group, keeps loadingGET /{country}/metadata./v2/policiespolicy for the loaded catalog version and saves a/v2/user-policiesrow withpolicy_id. Editing a reform's parameters creates a new policy and repoints the row withPATCH policy_id(the API then clearslegacy_policy_id); a rename only patches the name. Values are typed as v1 runs type them, and dates keep v1's whole-day periods (including its 2100-12-31 "forever"), as the dual-write copies of v1 policies do, so identical changes deduplicate to one v2 policy. Policies are cached for the session, as v1 policies are today.legacy_policy_id(every reform saved before the switch whose v1 policy has no dual-write copy yet) is listed and opened by reading its parameters throughGET /{country}/policy/{id}, until the one-time migration (4B-1) links it to a v2 policy. Editing it saves a v2 policy and repoints the row. 4B-3 removes this fallback.Unchanged by the switch: reforms kept in this browser (sign-in not configured, or countries without accounts) still make v1 policies, and report runs still create v1 policies and simulations until the v2 report work (3A-3) lands.
Not covered here (known gaps after the switch)
spmis absent from v2 metadata, and v3's US household calculation stays disabled until the v2 household path (2B-2, 3B-2) supplies it.Reform.policyIdholds the v2 policy UUID for v2 rows; the v1 report code that falls back to it needs a v1 ID. The v2 report work (3A-3) replaces that path.Pieces
app/src/api/publicApi/metadata.ts:fetchV2Metadata.app/src/api/reformStore.ts:ApiV2ReformStore,createV2ReformPolicy,parametersFromV2Policy;getReformStore(account, options, routes = V3_API_ROUTES).app/src/reducers/metadataReducer.ts:fetchMetadataThunkaccepts a country (v1, unchanged) or{countryId, routes: 'v2'}; the store remembers which routes its metadata came from and the v2 catalog identities.useFetchMetadata(countryId, routes = 'v1')reloads when the routes differ.app/src/hooks/useFlagshipStores.ts: the reform store gets the loaded catalog and a cached v2 policy reader.app/src/types/v2User.ts:policy_idon the user-policy create and patch bodies. A create body must namepolicy_id,legacy_policy_idor both (a union type), as the API requires.app/src/libs/flagship/v3ApiRoutes.ts:metadataRoutesForRouteGroup(routeGroup, v3Routes = V3_API_ROUTES); the second argument exists so tests can name'v2'.Merge requirements
PATCH /v2/user-policiespolicy_id,GET /v2/model-catalogand the catalog fields this code reads. This PR is stacked on Generate a typed Public API v2 client, with a CI check that it is current #1346, which carries the generated client.Environment variables
None added, read or changed. The v2 code uses the existing
BASE_URL(VITE_API_URL, mapped fromNEXT_PUBLIC_API_URL) and the existing saved-work credentials. No workflow changes in this PR.Testing
fetchV2Metadata(src/tests/unit/api/publicApi/metadata.test.ts): requests pin the selected catalog version and page regions; summary fields, parameters and nodes (v1 labels, flags, the earliest-date spelling), variables (v1 field names, enum labels, defaults), regions (v1 types, national first, district state fields) and catalog identities match v1's shape; a catalog published before format 2 falls back to defaults; API errors propagate.src/tests/unit/api/reformStoreV2.test.ts): thePOST /v2/policiesbody uses catalog UUIDs, typed values and UTC midnights; an unknown parameter sends nothing; v2 policies convert back to dated parameters; create linkspolicy_id; an existing v2 policy is reused; no catalog means no requests; v2 rows read their policy and v1-only rows read the v1 policy; a failed load is listed without its changes; edits repointpolicy_id; renames patch only the name; delete; the store choice by routes and account.metadataReducerroutes (v2 request reads v2 and remembers it; a country alone reads v1; overlapping requests run once),useFetchMetadatareloads on a routes change, and withV3_API_ROUTESmocked to'v2', v3 is not ready on v1 metadata, retry asks v2, and the reform store is the v2 store for signed-in and signed-out users and caches v2 policies.v3ApiRoutes: the constant is'v1'; the workspace route group follows the v3 routes ('v2'when named,V3_API_ROUTESby default), everything else stays on v1.src/tests/unit/calculator/CalculatorProviders.test.tsx): with v3 on'v2', a workspace page fetches metadata from v2 and a legacy calculator page from v1.useReformStorewith v3 on'v2'sendsPOST /v2/policiesfor the catalog in the metadata store (model UUID, parameter UUIDs,policyengine_version).UserPolicyCreateBody(src/tests/unit/types/v2User.test.ts, checked bybun run typecheck): a body naming neither policy does not compile.fetchV2Metadataand compared with v1'sGET /{country}/metadatadocuments. US: parameter labels 100,275 of 100,276 equal, descriptions, units, periods and flags 100% equal, values equal except two parameters with an invalid source date, all node labels equal, variable fields 100% equal except 2 enum labels (integer labels become strings) and 237 parameter-drivenaddslists; regions 487 of 488 identical (the national label). Details in Store the metadata v3 reads from /{country}/metadata in the v2 catalog policyengine-api#3970.bun run typecheck(app and calculator-app),bun run prettier,bun run eslint --max-warnings 0andbun run public-api:checkpass;bun run buildin calculator-app succeeds. Fullbun run vitest: all pass except the twoScorecardComparisonstests that fail identically onmainin my environment.Possible conflicts
Groups 1D (v3 households) and 1F (country list) edit v3 in parallel. Likely overlaps:
useFlagshipStores.tsand household code (1D-2's saved households should useV3_API_ROUTEStoo),providers.tsxandlibs/countries(1F-3), and the generatedopenapi.json/schema.d.ts(regenerate withbun run public-api:refresh).🤖 Generated with Claude Code