Skip to content

Calculate and store households through the v2 household functions - #957

Draft
anth-volk wants to merge 45 commits into
v2plan/1b-107-result-tablesfrom
v2plan/2b-203-household-submit-poll
Draft

anth-volk wants to merge 45 commits into
v2plan/1b-107-result-tablesfrom
v2plan/2b-203-household-submit-poll

Conversation

@anth-volk

@anth-volk anth-volk commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #956

What this changes

The Simulation Entrypoint now calculates and stores households on v2, through the v2 Modal household functions, and checks a household calculation end to end on every staging deployment. Nothing calls these routes yet; the Public API's /v2/household-calculations (PolicyEngine/policyengine-api#4092, a draft that waits for this) and household reports will.

  • Household calculations. POST /v2/household-calculations records a calculation (one stored household under one policy, with its options such as axes, for a catalog version and year) as a simulation run and starts the household function's Modal call. GET /v2/household-calculations/{calculation_id} returns its status and, once it ended, the calculated household with its warnings, the SPM selection it applied (spm_config), its SPM receipt (spm_provenance) and the bundle, or its error. The same calculation submitted again reuses its run while it is unfinished or for 28 days after it succeeded, unless forced.
  • Household reports. POST /v2/household-reports creates a report (a baseline and an optional reform) and starts its first run; POST /v2/household-reports/{report_id}/runs starts another run of it; GET /v2/household-reports/{report_id} returns the newest run with both calculations. One Modal call computes the runs still pending, so a new report's two simulation runs share the call. A report's runs start before the report is created, so a refused reference leaves no report behind.
  • Inputs. The runtime database role cannot read the Public API's tables, so the Public API sends the household in the situation shape with its saved SPM choice (spm; a saved null is no choice) and each policy's parameter values. The database checks that the household, policy and catalog version exist (400 otherwise). The household function and bundle come from the v2 version manifest for the calculation's PolicyEngine.py version, before anything is written (400 when there is none, 503 when the manifest cannot be read).
  • Recording. Each household function takes the runs a call computes as its second argument, and its last step starts report_call_ended with them, whether the calculation returned or raised; a pre-empted call reports nothing, and Modal runs it again under the same call ID. A household result recorder stores each run's household with the call's success. A household function's typed failure (such as an input the country model does not know) fails the runs with household_calculation_failed, with the household error document as the runs' error detail; an outcome missing one of its runs fails them as invalid_result. The safety check covers calls that never report and runs left dispatching.
  • Storage (revision 0005). simulations.options; the household output's warnings, spm_config, spm_provenance and bundle; and error_detail on failed simulation runs and comparison steps.
  • Staging test. After a staging deployment promotes its revision, with the v2 application deployed, the new staging_household_test job stores a one-person US household through the staging Public API, reads its catalog version, submits a forced calculation to the staging stable URL, polls until it succeeds or fails, and reads the result back, within ten minutes. It retries a 503 after its Retry-After wait within that limit. A failure fails the deployment run, so production does not deploy.

Tests

Run locally on the branch head, after rebuilding the branch on the rebased #951 (now on main):

  • entry project: uv run --frozen pytest tests/ — 1047 passed (new: household recorder, service, routes, reports, workers, staging job wiring; cases for a successful call, a failed Modal call, a typed household failure, an outcome missing a run, and a calculation left dispatching that the safety check fails and a resubmission runs again);
  • persistence library against PostgreSQL 17: SIMULATION_TEST_DATABASE_URL=... uv run --frozen pytest tests/ — 210 passed, including the migration cycle with alembic check;
  • executor: 2000 passed, 22 skipped, including the household functions' call reports; contract library: 395 passed;
  • ruff (0.15.22), Pyright for the entry, persistence, contract and executor packages, and the quality guards (including the file-length rule) pass; the simulation client generates with all five household endpoints;
  • each of the branch's own commits, checked out in turn: ruff, the quality guards and the suites of the packages it changes pass.

The staging test itself runs only in the deployed staging job.

Environment variables

New:

  • PUBLIC_API_URL (read by the staging_household_test job as SIMULATION_STAGING_TEST_PUBLIC_API_URL): a new GitHub environment variable in beta, the staging Public API's stable URL. It reaches the job through the job's environment: beta; both staging callers of the reusable workflow pass secrets: inherit. Not set yet: an operator step.
  • The job's other values: SIMULATION_STAGING_TEST_BASE_URL from deploy_entrypoint.outputs.stable_url; SIMULATION_STAGING_TEST_POLICYENGINE_VERSION from prepare.outputs.policyengine_version; SIMULATION_ENTRYPOINT_TEST_AUTH_ISSUER and _AUDIENCE from the beta secrets SIMULATION_ENTRYPOINT_AUTH_ISSUER and _AUDIENCE; SIMULATION_ENTRYPOINT_TEST_AUTH_CLIENT_ID and _CLIENT_SECRET from the beta secrets of the same names (all present, and used by the existing deployed authentication tests); SIMULATION_STAGING_HOUSEHOLD_TEST is a literal 1 in the job.

Reused, no new runtime variable:

  • SIMULATION_CALL_REPORT_MODAL_ENVIRONMENT: from the workflow input modal_environment, written into the Cloud Run environment by the deploy script; the household routes start calls and read the v2 version manifest in that Modal environment.
  • SIMULATION_DATABASE_URL, MODAL_TOKEN_ID, MODAL_TOKEN_SECRET: Google Secret Manager secrets mounted with --set-secrets, named by the beta/prod variables SIMULATION_DATABASE_URL_SECRET_NAME, MODAL_TOKEN_ID_SECRET_NAME and MODAL_TOKEN_SECRET_SECRET_NAME. SIMULATION_DATABASE_URL_SECRET_NAME is not set in either environment yet (an operator step of the earlier pull requests in this chain). Without the database or the Modal environment, the household routes answer 503.
  • SIMULATION_ENTRY_URL and MODAL_IDENTITY_TOKEN in report_call_ended: wired by the call-report change below this one; the household functions read nothing new.

Not verified: that sim's Modal token can spawn the v2 household functions and read the version manifest Dict in each Modal environment; that the deployed-test Auth0 client holds simulation:submit and simulation:read once permissions are enforced (SIMULATION_ENTRYPOINT_ENFORCE_PERMISSIONS is unset in beta, so today missing permissions are only logged); and that the staging Public API writes to the same database as staging sim, which the foreign keys need.

Waits for

A v2 application deployed before the household functions took run IDs (for an older PolicyEngine.py version) refuses them until it is redeployed; its calls fail as worker errors, recorded by the safety check.

CI: the PR description check passed on 2f0406e; no other run had finished when this was last updated.

🤖 Generated with Claude Code

anth-volk and others added 23 commits October 22, 2026 17:00
The main deployment's beta job and the Stage 12 comparison path's staging
deployment now pass promote_entrypoint: true, as production deployments do.
Each staging deployment therefore moves the staging service's stable URL to
the revision it tested, runs the same stable-URL checks as production,
restores the previous revision if they fail, and then deletes the staging
service's older revisions except the promoted one and the one it replaced.

The new rule sim.deploy.staging-promotes-tested-revision requires it: a test
reads every workflow, finds each job that runs the reusable deployment for
staging, and fails unless it passes promote_entrypoint: true. Its proof
fixtures are the beta job before and after this change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…build

The beta environment does not restrict which branch a run may come from,
so a manual run of either deployment workflow from a branch other than main
now moves the staging stable URL to that branch's build until the next
deployment from main.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The prod deployment needs the whole beta deployment, which now includes
the revision clean-up that runs after a staging promotion, so a failed
staging clean-up also stops the production deployment.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e staging clean-up

The repository README already says the prod deployment starts only after
beta's revision clean-up succeeds; the entrypoint's DEPLOYMENT.md listed the
beta jobs prod waits for without the clean-up job.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The v2 household functions take one household in the Public API's situation
shape, with optional axes, and one to ten policies, so a baseline and its
reform are calculated in one call. The request names the PolicyEngine.py
bundle it expects, whether to add missing variables (as the Public API's
/calculate-full route does), and an optional SPM selection whose omission is
kept distinct from an empty choice.

A household keeps its caller's key order through validation and transport,
at the top level and in each group, because the Public API walks a household
in that order and the order of its input errors, warnings and values follows
it. The result carries each policy's calculated household, warnings and SPM
receipt, the applied SPM selection and the bundle provenance; a failure
carries a typed error whose messages and input errors match the Public API's
error responses.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Schema version 2 of the v2 version manifest records, for each country a
worker application serves, whether it is economy-capable (it names a
single-simulation worker) and which function calculates its households, and
the validation receipt names an invocation for each of those functions. The
Simulation Entrypoint's comparison adapter now accepts only a country the
manifest marks as economy-capable, so the report coordinator never receives
a report for a country that only calculates households.

Deployed and rollback revisions read schema version 1 and reject unknown
fields, so the publisher writes schema version 2 under a new key and keeps
writing the schema version 1 forms (with region datasets, without them, and
without cache releases) under the existing keys whenever schema version 1
can describe the manifest. Current readers use the schema version 2 document
while the older keys hold its schema version 1 forms, or, for a manifest
schema version 1 cannot describe, unless the older keys hold a later
validation; otherwise they read the older keys as before, as economy-capable
countries without household functions. Code released before schema version 2
that publishes after a rollback therefore leaves no stale document behind,
and the next publish starts from what it wrote. The publisher records every
bundle country as economy-capable.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The v2 household functions prepare a household exactly as the Public API's
household calculation does before calculating it: /calculate-full adds every
missing yearly, monthly and eternal variable for the year of the first age
entered for "you"; deprecated inputs are dropped, including axes that vary
them, with the Public API's warning text; unknown entity groups, variables,
variables on the wrong entity, axis variables and policy parameters are
reported in the order the household was sent, with the Public API's error
types and messages; and the requested values are the null values, or with
axes every value of a known variable, in document order.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A US household's SPM selection is merged over the installed bundle's
certified defaults with the Public API's rules and error codes: settings for
any other country, or for a US model bundle without canonical SPM, are
unsupported; a model that can run canonical SPM without a certified
configuration fails closed; a forecast hash the bundle lacks is an invalid
setting from a request but an unavailable configuration from a saved
household; and an unknown scenario, county vintage or area is reported with
the Public API's code and message.

After each calculation, the country model's SPM receipt is reduced to the
compact public receipt and checked against the resolved selection. SPM errors
that end a calculation are recognized through wrapped causes, as the Public
API recognizes them. The executor's SPM module now exposes the certified
forecast it already loads once per process.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A v2 household function call is measured as one operation that covers the
whole call, with nested spans for loading the country model, preparing the
household, and calculating it under each policy. The stage plan lists those
names so the household functions open spans only from registered stages.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
calculate_household runs one household request: it checks the request names
the installed bundle, resolves the SPM selection, prepares the household once,
then builds a simulation and calculates the requested values for each policy
in order. It returns each policy's calculated household, warnings and SPM
receipt with the applied SPM selection and bundle provenance, or a typed
failure.

Each step follows the Public API's household calculation: reform values are
converted to each parameter's value type, values are written as its country
module writes them, a value that cannot be calculated is null (one null per
point, with a warning, when the household has axes), and an SPM error ends the
calculation only for a chosen measurement or a configuration the build cannot
certify. Failures carry the Public API's messages: an invalid household
payload, unrecognized inputs, an SPM error, or an error calculating the
household under a policy. Country models are loaded once per process.

The tests calculate real US and UK households, including US households with
axes and with every missing variable added.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… app

Each function takes a household request, calculates it with the executor's
household calculation and returns the result or typed failure as JSON. It
continues the caller's trace through the keyword-only observability context
and measures the call as one v2 household operation.

The functions run in their own image: the CA store, the locked packages and
the source, without the bundle's datasets or the Stage 12 cache, so neither
the image build nor the functions use a secret. Each reserves 2 CPUs and
4 GiB, may use up to 16 GiB for large households with axes, times out after
900 seconds, is not retried, and runs at most 20 containers. The other v2
images keep their layers and order.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nifest

After the country validation functions, release validation calls each
deployed household function with a one-adult, current-law household and
requires a successful calculation with the deployed bundle, reported by that
function, with a number for the household's net income. The release record
keeps each household invocation ID, and the manifest publisher records a
country's household_callable only when the record shows its function was
validated. A household function that fails validation stops the release, as a
failed country validation already does.

The validation household and its check live in the executor package, so the
pre-merge image check can calculate the same household inside the household
image.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The image check now also runs a v2 image smoke. It builds the report
coordinator, UK worker and household images the v2 application deploys, and
the US worker image without its last layer, the Stage 12 cache, which only a
deployment can fetch: the deployment's precompute writes the cache, and the
image build reads it with the deployment's Cloud Storage credential. That
layer adds data files, not Python packages.

In each report image the smoke imports the v2 application module, as Modal
does before it runs a function, and the modules the coordinator, workers and
validation functions import while handling a request. In the household image
it imports the household entry points and calculates the release validation
household for each country, which shows the image calculates without
datasets. The smoke functions use no secret.

Modal builds the smoke's images together. The job's timeout rises from 45 to
90 minutes, because after a relock both the executor smoke and the v2 smoke
download certified datasets.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A household's SPM receipt that the installed country model reports in a form
that cannot be certified now fails with the Public API's reason, checked in
its order: the forecast hash, scenario and geography kind are all required
before they are compared with the selection, then the years mapping, the
runtime versions and the forecast ID. The contract's receipt model words its
year and metro-area errors as the Public API's model does, and a receipt that
is not an object, or whose years are not a mapping, is reported the same way.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…'s codes

The request's spm field now carries the caller's SPM settings as any JSON
value, and the household function checks them as the Public API's routes
do: settings for a country other than the US are unsupported, an explicit
null for the US is invalid, and malformed settings are invalid with the
Public API's message, before the bundle's certification is read. A metro
selection needs an area and any other kind accepts none, judged on the
settings as sent, as the Public API's model judges them. Before, a malformed
selection or an explicit null failed the request's validation instead of
returning a typed SPM failure.

The request's documentation now says which parts of a saved household's
calculation match the Public API's stored-household route (the calculated
household, warnings, SPM receipt and SPM codes) and which do not (two error
messages, which that route words with the household and policy IDs).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The household SPM code now loads the certified forecast with its own
per-process loader instead of a wrapper added to the main path's SPM
module, which returns to its state on main. The household path and the main
path share no SPM code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The household calculation module's documentation now lists each known
difference from the Public API's household calculation, which were described
in several places or not at all: how undefined numbers and dates are written,
the UTC default year, an age given as one value under calculate-full, an
explicit null axes, checking every policy's parameters first, the request
routes' error wording, and requests the contract rejects.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A review asked whether a publish after code released before schema version 2
(as a deployed revert would run) could start from a stale schema version 2
document and drop what that code wrote. The schema version 2 reader already
uses the document only while it is current; these tests pin that down for a
publish: it keeps an application the older code redeployed without household
functions, it takes the default from what the older code wrote, and older
keys that a household-only schema version 2 publish left alone change nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A v2 worker version now returns the simulation callable of an
economy-capable country, and raises LookupError for any other country. The
Simulation Entrypoint's comparison backend stores that callable on the
prepared report and sends it to the coordinator, instead of looking up the
country's worker by matching and sending a value that schema version 2
allows to be null. The adapter already skips a country that is not
economy-capable, so a prepared report always has one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… models

Two real-model tests join the PR checks: a US household without a county
leaves the SPM threshold uncalculated under the inherited county selection,
fails with SPM_GEOGRAPHY_REQUIRED when the selection is chosen, and is
calculated with a national receipt when the national kind is chosen; and the
installed US and UK models calculate the household that release validation
and the image check send.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A US household calculation resolved its SPM selection before the model-load
span opened, and that resolution imports the US model, so a cold container's
model load was measured in none of the household stages. The country model
now loads first, in its own span, and the SPM selection is resolved while the
household is prepared, as the Public API resolves it while preparing a
calculation. A test checks both, and that a saved household's settings are
resolved as saved. The module's list of differences from the Public API now
says that a failure under any one policy returns one failure for the whole
request.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…eholds

The contract's SPM receipt model is shared with the main path, so it returns
to its wording on main. The household SPM code now translates that model's
year and metro-area errors into the Public API's wording when it reports a
receipt it cannot certify, so household messages still match the Public API
and nothing the main path runs changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… limits

The tests' older-publisher documents now carry only the fields those
publishers wrote: no region datasets under the keys that predate them. Two
tests join them: the fixtures hold only released fields, and a publish
refuses an unreadable schema version 2 document.

The migration document now says what a rollback to code released before
schema version 2 costs once current code publishes again: the manifest names
no household functions for versions published before the rollback until each
is published again, and it loses what only the schema version 2 document held.
It also says that current readers do not read a publish by code released
before cache releases, so a rollback that far needs the newer keys deleted
first; the region dataset readers on main already behave this way.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
anth-volk and others added 5 commits October 28, 2026 01:35
… into v2plan/2b-203-household-submit-poll

# Conflicts:
#	changelog_entry.yaml
#	docs/generated/rules.md
#	projects/policyengine-simulation-entry/tests/test_deployment_assets.py
A simulation gains options: whatever else changes what it calculates, such
as a household's axes, stored as a JSON object and empty unless set
(revision 0005). HouseholdCalculationStore.find_or_create_simulation returns
the oldest household simulation with the same country, household, policy,
dynamic and options, compared by value, or creates it, so a household
calculation submitted again reuses its simulation and run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A household output now keeps the calculation's warnings, in order, the
complete SPM selection it applied (spm_config), its SPM receipt
(spm_provenance) and the PolicyEngine.py bundle it ran with, each document
null when it does not apply. They are written in the transaction that marks
the run succeeded and read back unchanged; revision 0005 adds the columns.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
RunError gains an optional detail, a JSON object stored with the failure
(error_detail on a simulation run, comparison_error_detail on a comparison
step) for the run's reader, such as the inputs a household calculation
rejected. Its message still carries no user data, and the detail is never
logged. Only a failed run or step may hold one (revision 0005).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
anth-volk and others added 17 commits October 28, 2026 01:35
Household calls now have a result recorder. Each request names a policy by
its run, so each result maps back to the run it computes: a successful
outcome stores each run's calculated household with its warnings, SPM
receipt, the applied SPM selection and the bundle, in the transaction that
marks the runs succeeded. A typed failure, such as inputs the country model
does not know, fails the runs with household_calculation_failed; its message
names only the failure's code, and the household error document is the
runs' error detail. A recorder's validate signals such a failure with
CalculationFailedError, which run recording records instead of an invalid
result.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
HouseholdCalculationStore starts or reuses a calculation's run, as RunStore
does, and read_calculation returns it as its reader sees it: the run's
status, timings, error and error detail, its simulation, and, once it
succeeded, the computed household. An economy run reads as none. The tests
include a calculation left dispatching: it is failed as abandoned after ten
minutes and submitting it again starts its second attempt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
POST /v2/household-calculations records a household calculation (one
household under one policy, with its options, for a catalog version and
year) and starts its Modal call; GET /v2/household-calculations/{id}
returns its status and, once it ended, its household with its warnings, the
applied SPM selection, the SPM receipt and the bundle, or its error.

The Public API sends the household in the situation shape with its saved
SPM choice and the policy's parameter values; the database checks the
household, policy and catalog version exist. The calculation finds or
creates its simulation and reuses its run while unfinished or once it
succeeded, unless forced. The household function and bundle come from the
v2 version manifest for the calculation's PolicyEngine.py version, before
anything is written, and a pending run is dispatched by whichever
submitter claims it. A version or country without a household function, or
an unknown household, policy or catalog version, gets 400; an unreachable
manifest or database, or a service without the database or the v2 Modal
environment, gets 503.

Tests cover a successful call, a failed Modal call, and a calculation left
dispatching, which the safety check fails so it can be submitted again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… step

calculate_household_us and calculate_household_uk take the simulation runs
sim started the call for as an optional second argument. Given them, the
function's last step, once the calculation and its telemetry have finished,
starts report_call_ended with the call ID and those runs, whether the
calculation returned or raised; a pre-empted call reports nothing, since
Modal runs it again under the same ID. Release validation and the image
check pass no runs, and nothing is reported.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
HouseholdCalculationStore creates household reports and their runs, and
read_report returns a household report with one of its report runs (the
newest, unless one is named) and that run's baseline and reform runs as
household calculations, each with its household once it succeeded. A
report with only a baseline has no reform, a report before its first run
has neither, and an economy report, or another report's run, reads as none.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…oint

POST /v2/household-reports creates a household report (the household
under a baseline and an optional reform, for a year) and starts its first
run; POST /v2/household-reports/{id}/runs starts another run of it, with the
report's own household and policies (400 otherwise); GET
/v2/household-reports/{id} returns it with its newest run and that run's two
calculations. A report run has failed when either calculation failed and
succeeded when both did.

Each report run joins a run of each simulation, reused as any calculation's
run is, and one Modal call computes the runs still pending, so a new
report's two households are calculated together. When another submitter
claimed one of them first, that submitter's call computes it, and this one
starts a call for the rest.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
After a staging deployment promotes its revision, with the v2 application
deployed, the staging_household_test job submits a US household
calculation to the staging stable URL, polls until it succeeds or fails
within ten minutes, and reads the result back. It runs after promotion
because the household function reports the call's end to the stable URL;
a failure fails the deployment run, so production does not deploy.

The calculation refers to rows the Public API owns, so the test reads the
staging Public API's US catalog version for the deployed PolicyEngine.py
version (or its current one) and stores a one-person household there; the
new beta environment variable PUBLIC_API_URL names that service. The test
lives in authenticated_tests and is skipped by every other run of them.
The deployment guide no longer says staging does not promote its revision.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A household outcome with no result for one of its call's runs used to raise
a plain ValueError inside the success transaction, which run recording took
for a database failure: nothing was recorded, the report was answered as
retry-later, and every safety check read the same output again until Modal
no longer held it. The writer now raises CalculationFailedError, and run
recording fails the runs with invalid_result, as it does for an output that
does not validate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The household routes passed an explicit "spm": null to the household
function as a choice, labelled as the saved household's, which the
function rejects for a US household. The household functions' contract
reads a saved null as no choice, so the request now leaves it out and the
bundle's certified defaults apply, as when the field is omitted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Creating a household report inserted the report before starting its runs,
so a catalog version the database refused answered 400 but left a stored
report with no run, whose ID the caller never received. The runs now start
first, and the report and its report run are created only once they exist.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
run_dispatch no longer says no route starts calls. The household modules
name the v1 run_simulation code directly instead of "the main path", which
modal_calls uses for the entrypoint's own run path. The calculation request
says what year means (the household's calculation year, part of the run's
identity, while the household's own periods decide what is calculated) and
that its PolicyEngine.py version must be the catalog version's. The
deployment guide says that a v2 application deployed before the household
functions took run IDs refuses them until it is redeployed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The in-memory store the household tests use reused any run that had not
failed, while RunStore reuses a succeeded run only if it finished within the
reuse window. It now applies the same window, and a test shows a calculation
that succeeded longer ago runs again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rvice

The staging household test stored a household without the net income it
then asked for, so the succeeded run it left, reusable for 28 days, did not
match the household's own content. The stored household now asks for the
same value. The test also failed on the first 503, which the household
routes answer while the database or the version manifest cannot be reached;
it now waits the answer's Retry-After and asks again, within its ten-minute
bound.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The household routes, their request models (and so the OpenAPI schema),
the household service and the entrypoint guide said a succeeded calculation
is reused until forced. RunStore reuses a succeeded run only within the
reuse window, so a later submission runs again under a new calculation ID;
every description now says so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The check that a report's run names the report's own household and
policies compared the submitted policies with the report's simulations
already paired with them, so the same rule was stated twice. It now compares
the report's policy IDs, in order, with the submitted ones, and each
simulation's household with the submitted household; the tests are
unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ehold

The staging household test gave its submission and its polling a
ten-minute limit each; both now share one limit from the start of the test.
It builds the household it calculates from the household it stores, so the
two shapes cannot drift apart, and says that a submission retried after it
was partly written may leave a pending run that nothing reuses. Tests also
cover a retry that would pass the limit and a reform sent for a report of
the baseline alone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@anth-volk
anth-volk force-pushed the v2plan/2b-203-household-submit-poll branch from 9d0e884 to 2f0406e Compare October 27, 2026 22:20

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant