Repository navigation
Conversation
…ailing) Identical requests are to share simulations, reports and runs: the simulation service finds each simulation and report by a key and joins an unfailed run on the same versions. These tests describe the schema that lets it, and fail until the models, revision and grants exist: - nullable key columns on simulations and reports, unique on the key and its version, with format and completeness checks, and the dataset-independent key on reports, indexed; - a forced marker on simulation and report runs, false by default; - one unfailed, unforced run per definition and version set, enforced by partial unique indexes (in PostgreSQL and in the unit tests' SQLite), leaving budget-window year runs out; - user_report_run_associations, which the Public API's runtime role reads, inserts and deletes, and no other role reads; - the revision's shape and its exact downgrade. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The simulation service is to find each simulation and report by a key and join a run on the current versions instead of creating new rows. Revision 94dc43079186, autogenerated, gives it the schema: - simulations and reports gain nullable key_version and definition_key, unique on both, and reports dataset_independent_key, indexed with key_version; checks require 64 lowercase characters, a positive version and every key column or none. - simulation_runs and report_runs gain forced, false by default, which marks a forced re-run's extra runs. - Partial unique indexes allow one run that hasn't failed and isn't forced per simulation, year, catalog version and package versions, and per report and package versions outside budget windows. They are declared for SQLite too, so the unit tests' databases keep the rule. - user_report_run_associations records each user's request for a report run (CASCADE with the user, RESTRICT on the run, one per user and run), which the runtime role reads, inserts and deletes. The one post-generation correction makes the new table's trigger reuse the v2_report_run_trigger type. Tests that stored two unfailed runs on the same versions now store a forced re-run, give each side or year its own simulation, or fail the first run. CI runs the new integration tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d checks (failing) - A key must be 64 lowercase hexadecimal characters; a non-hexadecimal key of the right length and case is still accepted, so the format tests fail until the check changes. - The revision chain test lists the SPM availability revision under the new head, and the baseline's table count leaves out the new table; the import test counts 48 tables. - The output checks' refused inserts name the check they hit, since the new index would refuse them too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A key's check now accepts only 64 lowercase hexadecimal digits (length 64, and nothing left after trimming the hex digits), the same in PostgreSQL and SQLite. Revision 94dc43079186 is generated again from the models, since Alembic compares checks by name only; its operations differ only in the three key checks, and its one correction is unchanged. - The skill document says an ON CONFLICT with a target needs SELECT as well as INSERT, and that the indexes also refuse a plain re-run after a failed report run whose other side succeeded. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…in SQLite - The simulation-run model tests list the forced column and the one-unfailed-run index among the table's exact columns and indexes. - A new SQLite test inserts keys that aren't 64 lowercase hex digits (uppercase, short, non-hex, blank, empty) and expects the format check to refuse each; with the earlier lowercase-only check it fails. - The skill document names each index's whole condition in its ON CONFLICT example: report runs also need budget_window_run_id IS NULL, or PostgreSQL finds no matching index. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The Simulation and Report docstrings list the country among what each key covers; the User docstring says a deletion removes the user's report-run records; the privileges docstring counts forced among what no role may change about a run. - The Stage 9 and Stage 12A downgrade notes say the revision's downgrade also drops the forced markers and the run indexes. - The SQLite index tests expect the unique index's refusal by name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #4303 +/- ##
==========================================
- Coverage 93.47% 93.45% -0.03%
==========================================
Files 369 369
Lines 21455 21478 +23
Branches 3400 3401 +1
==========================================
+ Hits 20056 20072 +16
- Misses 816 823 +7
Partials 583 583
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
The app finds a report's local-area report by the national report's dataset_independent_key, which holds its own key version, so a lookup names the key alone. The tests expect an index on that column alone and a lookup that uses it; today's index leads with key_version, which a key-only lookup can only scan in full. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The app finds a report's local-area report by the national report's dataset_independent_key, and the key holds its own key version, so a lookup names the key alone. ix_reports_dataset_independent_key replaces the index that led with key_version, which such a lookup could only scan in full. Revision 94dc43079186 is generated again; only that index's create and drop changed, and its one correction is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The lookup test now reads its results before the next statement and checks they are the two reports it stored, and names the index the plan uses in full. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A simulation's key covers everything that changes its numbers, now with the SPM selection; a report's key names the SPM selection once and holds no request options, so the key shared across datasets is the report key without the dataset. Docstrings and the skill document only; no schema change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
C1 · 24's answer 8 gives budget windows the same keys: a report has one window run per version set that hasn't failed, and recalculate is a window run's forced marker. The tests expect budget_window_runs to record its .py version (optional, so older window runs stay unversioned and are never joined) and a partial unique index on the report, catalog version and .py version that leaves out failed and recalculated runs, in PostgreSQL and in the unit tests' SQLite, and the simulation service to find or create a window run against it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
C1 · 24's answer 8: budget windows share runs like other reports. budget_window_runs gains policyengine_version (nullable, so window runs written before it stay unversioned and are never joined), and uq_budget_window_runs_unfailed_unforced_versions allows one window run per report, catalog version and .py version that hasn't failed and isn't recalculated: recalculate is a window run's forced marker. No role may change the version once written. Revision 94dc43079186 is generated again; it adds only the column and the index, and its one correction is unchanged. Household runs already fall under the same run indexes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rt keys master gained revision 1e1e3f079f2c (withheld_area_codes) on the same parent, 0c136a278f29, so revision 94dc43079186 now follows it: its down_revision, V2_ALEMBIC_HEAD_REVISION, the revision chain and single-child lookups in test_alembic_v2, the shape test's parent, the 49-table count, and the skill document's section. Generating 94dc43079186 again on 1e1e3f079f2c gives the same operations. The withheld codes test gives each of its runs a simulation of its own, as a simulation has one unfailed run per version set. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…found The revision's docstring, the skill document and the Stage 9 and Stage 12A downgrade notes say the downgrade also drops budget_window_runs' policyengine_version, and the shape test's docstring lists it among the revision's changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
C1 · 24's answer 6, as the user settled it: the simulation service stops writing reports.label, report_runs.trigger and budget_window_runs.trigger in C1 · 27, and C1 · 41 drops them later. Until then they must be nullable, with no default. The tests expect the three columns nullable in the models and the revision (and required again after its downgrade), the simulation service to store a report and runs without them, and the report read and run lists to answer a missing label or trigger as null. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
C1 · 24's answer 6, as the user settled it: a shared report or run describes no one caller, so the simulation service stops writing reports.label, report_runs.trigger and budget_window_runs.trigger (C1 · 27), and a later revision drops them (C1 · 41). Revision 94dc43079186, generated again, makes the three nullable with no default (reports.description and user_id already were); its downgrade makes them required again. The report read and the run lists pass a missing label or trigger on as null, and the /v2/reports response models allow it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The skill document says a Public API rollback below this revision isn't safe once the simulation service writes null labels and triggers, and the downgrade notes (skill document, Stage 9, Stage 12A) say the downgrade needs every report to have a label and every run a trigger. - The re-run route's description and ReportRunRead's docstring no longer promise a trigger. - Test docstrings name the three nullability changes, and the run transformation test is named for what it checks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Nov 13, 2026
anth-volk
added a commit
to PolicyEngine/policyengine-sim-api
that referenced
this pull request
Nov 14, 2026
…d until it merges The Public API schema job built the tables at 429d348ae, before #4303, so every test needing the key columns, forced markers and nullable per-user columns failed. 63cce8273 is #4303's pushed head, which this branch's models match (B1 · 5's comparison passes locally against it). The job's last step requires a commit on the Public API's master, so it fails until #4303 merges; then the pin moves to #4303's merge commit. 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 #4302
Summary
Revision
94dc43079186(on1e1e3f079f2c), autogenerated, lets the simulation service find each simulation and report by a key and join an existing run instead of creating new rows. It covers economy reports, household reports and budget windows.simulationsandreportsgain nullablekey_versionanddefinition_key, unique on the pair (uq_simulations_key_version_definition_key,uq_reports_key_version_definition_key).reportsalso gainsdataset_independent_key, which a national report and its local-area report share. It is indexed on its own (ix_reports_dataset_independent_key), because the app finds one report by the other's key, and the key holds its own key version.key_version, and every key column or none.simulation_runsandreport_runsgainforced,falseby default. For a budget window,recalculateis the forced marker.uq_simulation_runs_unfailed_unforced_versionson(simulation_id, year, tax_benefit_model_version_id, country_package_version, policyengine_version)wherestatus <> 'failed' AND NOT forced;uq_report_runs_unfailed_unforced_versionson(report_id, country_package_version, policyengine_version)wherebudget_window_run_id IS NULL AND status <> 'failed' AND NOT forced;uq_budget_window_runs_unfailed_unforced_versionson(report_id, tax_benefit_model_version_id, policyengine_version)wherestatus <> 'failed' AND NOT recalculate.budget_window_runsgainspolicyengine_versionfor it; it is nullable, and unversioned window runs are never joined.user_report_run_associationsrecords each user's request for a report run:CASCADE) and the run (RESTRICT, indexed);requested_at(required, no default);trigger(initialormanual, reusingv2_report_run_trigger);reports.label,report_runs.triggerandbudget_window_runs.triggerbecome nullable, with no default.reports.descriptionanduser_idalready were. This lets the simulation service stop writing them. The report read and the run lists answer a missing label or trigger as null, and the/v2/reportsresponse models allow it. A later revision (tracker task C1 · 41) drops them.privileges.py): the runtime role may read, insert and delete the new table's rows, and no other role has access. The simulation service needs nothing new:ON CONFLICTwith a target needsSELECTandINSERT, which it already holds. No role may change a key,forcedor a window run'spolicyengine_version.triggerreuses the existing enum type (create_type=False).It follows the shared-reports design decisions (tracker task C1 · 24):
requested_at, so there is noUPDATEgrant.user_simulation_run_associations.Beyond the design's column list, the simulation-run index also covers
yearandcountry_package_version. That only allows rows the shorter index would refuse.Deploy order and checks
PolicyEngine/policyengine-sim-api). That change uses these indexes withINSERT ... ON CONFLICT DO NOTHINGand stops writing the per-user columns.docs/engineering/skills/alembic-migrations.mdin staging and production first.labelandtriggeras required.Changed existing tests
They stored two unfailed runs on the same versions, which the new indexes refuse. Each keeps its intent:
RunSettings.forcedintests/integration/stored_report_rows.py);test_alembic_v2,test_import_side_effects(49 tables),test_simulation_run_modelsandtest_modelsnow pin the new head, chain, table count, columns and indexes.CI
tests/integration/test_alembic_v2_shared_report_keys.pyjoins.github/scripts/test_alembic_v2_lifecycle.sh.tests/integration/test_v2_shared_report_rows.pyjoins the managed-roles step ofv2-integration-check.yml.V2_MIGRATION_DATABASE_URLandV2_ALEMBIC_DISPOSABLE_TEST, and the integration jobDEPLOYMENT_ENVIRONMENT, as literal jobenv:values for their own PostgreSQL service container.reconcile_v2_privileges.py apply/verifysteps grant and check the new table. Those steps are unchanged.Testing
alembic -c alembic-v2.ini upgrade head, thenscripts/reconcile_v2_privileges.py apply --create-missing-rolesandverify.alembic -c alembic-v2.ini checkfound no new operations, and there is one head. Generating94dc43079186again on1e1e3f079f2cgives the committed operations.masteraffects. Each new test was seen failing first.test_v2_shared_report_rows.pyruns the simulation service'sON CONFLICTfind-or-create, forced, failed and window runs, rows without per-user columns, and the runtime role's records, each as its managed role.lint-imports,python -m scripts.rules validate,python scripts/run_quality_guards.pyand mypy all passed.policyengine_api/data/v2/files shows the 20 errorsmasterhas there, plus 2 of the same kind (SQLModel'sFieldoverloads andsa_type=) on the new table'srequested_atandtrigger.🤖 Generated with Claude Code