Repository navigation
Conversation
anth-volk
added this pull request to stack #843
October 19, 2026 22:02
Add the input handling the v2 household functions need, reproducing the Public API's in-process household calculation so v2 results can be compared with it field by field: - country-model metadata read from a tax-benefit system (variables with the Public API's default values, entities and role plurals, gov parameters); - the household year and the calculate-full addition of missing yearly, monthly and eternal variables; - dropping deprecated inputs, with the same warnings; - unrecognized household, axis and policy inputs, with the same types, paths and messages; - the list of values to calculate, with and without axes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add the calculation the v2 household functions run. calculate_household takes one HouseholdExecutionRequest and calculates the household under each requested policy in order, so a baseline and its reform share one invocation, returning per-policy results or a typed failure. It reproduces the Public API's in-process household calculation: the same simulation construction and reform value conversion, requested values, value serialization (infinities as "Infinity"), values that cannot be calculated (null, with a warning when axes are used), warnings, and SPM selection, error codes and compact receipt. Failure codes and messages match the Public API's error responses: bundle mismatch, SPM selection, unrecognized inputs, unparseable household, and any other calculation error. Also: - register the v2_household stage plan for the household function's spans; - add HouseholdBundleProvenance.from_bundle and household_callable_name to the contract; - expose the executor's certified SPM forecast loader. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The v2 Modal application now defines calculate_household_{country} for each
country in the installed PolicyEngine.py bundle (today calculate_household_us
and calculate_household_uk). Each takes one HouseholdExecutionRequest and
returns the per-policy result or typed failure from calculate_household.
- The household image holds the frozen simulation dependency group and no
datasets or cache, and the functions get no Modal secrets: household
calculations read no private data, Cloud Storage or database.
- Sizing: 2 CPUs, 4 GiB reserved with a 16 GiB limit for large households
calculated with every variable at many axis points, a 900-second
timeout, no retries and at most 20 containers.
- Deployment validation calls each household function with a one-adult
current-law household and requires a successful calculation with this
deployment's bundle; the release record keeps the invocation IDs.
- The manifest publisher records a country's household function only when
the release record shows it was validated.
Nothing calls the household functions yet.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A household with an unknown person variable and an unknown axis variable, sent with `axes` last, must list the person's error first and join the message in that order, as the Public API does. The test failed before the contract serialized households in the caller's key order (the fix in the pull request below) and passes with it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Describe the household functions in the v2 worker document: one per bundle country, their image without datasets, no secrets, their limits, and that deployment validation calculates a household with each one. A failed household validation stops manifest publication and the Cloud Run promotion, as any v2 worker validation failure does. Documentation only: no test can fail first. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
anth-volk
force-pushed
the
apiv2/1c-household-functions
branch
from
October 20, 2026 13:24
4d79ea2 to
a4b6872
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 #840
Summary
Second of two stacked pull requests for the household part of the API v2 migration (Step 1, group 1C), on top of #841. It adds a household calculation that reproduces the Public API's in-process household calculation, and one v2 Modal household function per country in the PolicyEngine.py bundle. Nothing calls the new functions yet; the entrypoint household submit and poll routes come in group 2B.
Household calculation (
policyengine_simulation_executor)household_calculation.calculate_householdruns oneHouseholdExecutionRequest: it calculates the household under each policy in order, so a baseline and its reform share one invocation, and returns aHouseholdExecutionResultor a typedHouseholdExecutionFailure. It ports the Public API'shousehold_calculation_service.py,country.pyandspm.pybehavior:household_inputs.py: country-model metadata from the tax-benefit system (variables with the Public API's default values, entities and role plurals,govparameters); the household year and the calculate-full addition of missing yearly, monthly and eternal variables; dropping deprecated inputs with the same warnings; unrecognized household, axis and policy inputs with the same types, paths and messages; the list of values to calculate with and without axes.household_calculation.py: simulation construction and reform value conversion; serialization of requested values (enum names, infinities as"Infinity", one value per axis point); a value that cannot be calculated becomes null, with the Public API's warning when axes are used; per-policy warnings (deprecated inputs, then calculation warnings). Failure codes and messages follow the Public API's error responses, in its order: bundle mismatch, SPM selection, unrecognized inputs, unparseable household (Invalid household payload: ...), other errors (Error calculating household under policy: ...).household_spm.py: the SPM selection merged over the bundle's certified defaults with the Public API's codes (includingSPM_SETTINGS_UNSUPPORTEDoutside the US and the saved-household artifact case), the rule that a chosen measurement fails on a missing SPM input while an inherited one leaves the value null, and the compact SPM receipt checked against the resolved selection.v2_householdstage plan inpolicyengine_simulation_observability.stages(execution, country model load, input normalization, one calculation span per policy).Two deliberate differences from the Public API, both for inputs the Public API answers with an HTTP 500 or with invalid JSON: an undefined number is returned as
"NaN"(the contract carries only finite numbers), and ayouage entered without periods counts as no age when choosing the default year. Date values are returned as ISO strings.Modal functions this deploys
Added to the existing versioned v2 application
policyengine-simulation-v2-py{version}(the version comes from the installed PolicyEngine.py bundle), in both the staging and production Modal environments, by the existingdeploy_stage12_v2job:calculate_household_uscalculate_household_ukmodal-simulation-imagedependency group plus source, with no datasets and no Stage 12 cache. Household calculations read no private data, Cloud Storage or database, so the functions get no Modal secrets. The existing images keep the same layers.validate_v2_worker_release) now calls each household function with a one-adult, current-law household and requires a successful calculation with this deployment's bundle; the release record keeps the invocation IDs. A failing household function therefore fails the v2 deployment, which stops the manifest publication and, as any v2 worker validation failure does, the Cloud Run promotion of that release. This adds one cold start and one calculation per country to the deployment.household_callablefor a country only when the release record shows its household function was validated.docs/migration/stage-12-modal-worker-foundation.mddescribes the household functions and their release validation.Configuration and environment variables
No variable or secret is added. The values involved, and how they reach the functions:
deploy_stage12_v2, "Deploy the additional versioned v2 Modal application"), unchanged:MODAL_TOKEN_IDandMODAL_TOKEN_SECRET(GitHub environment secrets),OBSERVABILITY_SERVICE_NAMESPACE,OBSERVABILITY_TRACE_PROJECT_ID,OBSERVABILITY_LOGGING_PROJECT_ID,OBSERVABILITY_LOG_NAME,OBSERVABILITY_GOOGLE_WORKLOAD_IDENTITY_PROVIDER,OBSERVABILITY_GOOGLE_SERVICE_ACCOUNT_EMAIL,OTEL_EXPORTER_OTLP_ENDPOINT(fromvars.OBSERVABILITY_OTLP_ENDPOINT) andPOLICYENGINE_OTEL_GOOGLE_AUDIENCE(fromvars.OBSERVABILITY_OTLP_GOOGLE_AUDIENCE), all GitHub environment variables passed through the step'senv:block.modal_image_environment()bakes the observability values into the household image at build time, as it does for the existing images.MODAL_TOKEN_IDandMODAL_TOKEN_SECRET(GitHub environment secrets, stepenv:).MODAL_ENVIRONMENT,MODAL_TASK_IDandMODAL_REGIONare set by Modal; telemetry export uses Modal's container identity token (MODAL_IDENTITY_TOKEN) with the baked workload identity settings.MODAL_IDENTITY_TOKENto functions with no attached secrets, and that the Google workload identity provider's attribute conditions accept tokens from the new function names. If either does not hold, telemetry export from the household functions fails; bysim.observability.failures-never-break-requeststhat does not change a calculation result.No operator step is needed before merging.
Coordination
Group 1B edits the report coordinator in the same Modal app in parallel. This pull request changes
build_v2_image(an optional dataset layer) and appends the household functions at the end ofsrc/modal/v2_app.py; it does not changecoordinate_report. A textual conflict is possible if 1B editsbuild_v2_imageor also appends to that file.Tests
tests/test_household_inputs.py,tests/test_household_spm.py,tests/test_household_calculation.py(fake country models for failure paths, and the installed US and UK models for a baseline and reform in one request, axes, calculate-full, unrecognized inputs, deprecated inputs, SPM for the UK, and an invalid household).tests/test_stage12_modal_app.py(functions generated for every bundle country, sizing, no secrets, image without datasets, observability context and runtime shutdown, wrong country refused),tests/test_validate_v2_worker_release.py,tests/test_update_v2_version_manifest.py, contract and stage registry tests.projects/policyengine-simulation-executor:uv run pytest tests→ 1341 passed, 22 skipped (including a test that unrecognized inputs are listed in the order the household was sent, as the Public API lists them).libs/policyengine-simulation-contract→ 236 passed;libs/policyengine-simulation-observability→ 34 passed.calculate_household_usandcalculate_household_ukas global, non-serialized functions resolvable by module attribute.ruff check .andruff format --check projects/*/src libs/*/src(ruff 0.15.22) passed.🤖 Generated with Claude Code