Skip to content

Create household reports through POST /reports and calculate them in one household function call - #1057

Draft
anth-volk wants to merge 31 commits into
mainfrom
v2plan/c1-15-household-reports
Draft

anth-volk wants to merge 31 commits into
mainfrom
v2plan/c1-15-household-reports

Conversation

@anth-volk

@anth-volk anth-volk commented Nov 12, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #1055

Same-Repository Draft PR Check

  • This PR is a draft.
  • This PR is from a branch in PolicyEngine/policyengine-sim-api, not a personal fork.

Changelog

  • This PR adds a changelog fragment, changelog.d/c1-15-household-reports.added.md (added).

Summary

The Simulation Entrypoint creates household reports and their household simulations in the Public API's run tables. One household function call per report run calculates them and writes each simulation run's household_result. The Public API's /v2/reports will create household reports through this route.

POST /reports takes household reports

  • The body is told apart from an economy report by report_type: "household". Permission simulation:submit.
  • Its fields:
    • country, label, description;
    • household_id, a household the Public API stores;
    • baseline: {policy_id};
    • reform: {policy_id}, null or omitted for a report of the baseline alone.
  • What it names: each policy_id is null for current law. The report is for the household's default_year. The request names no year, region, dataset, dynamic, .py version or record ID, and has no options. The household's saved SPM selection applies to both sides.
  • Every variable is calculated: each side adds every variable the household lacks, as the Public API's stored-household route does for v3's household report page today, so the report holds every value.
  • Refused fields are located without the type, as for an economy report, by a copy of Calculate budget windows on the detailed-save path, five years at a time #1054's report_request_errors.py. A dynamic_id on a side, or any unknown field, is refused with 422.

What the service does (household_report_submissions.py)

  1. Finds the household function: the v2 version manifest's default worker's household function for the country.

  2. Reads the inputs in one transaction: the catalog version for that worker's .py version, the model, the household and each side's policy values.

  3. Refuses, before writing anything, what is missing or can't be calculated:

    • 400 unknown_catalog_version, unknown_household, unknown_policy (naming the side), no_household_function;
    • 422 missing_parameters, one reason per parameter at body.<side>.policy_id.<name>;
    • 422 household_not_calculable: the household has no default year or can't be read, or the household contract refuses what would be calculated. Each refusal is located at the field it traces to.
  4. Creates the rows in one transaction:

    • a household simulation and a pending simulation run for each side, never reused;
    • the report (type = 'household', household_id, year, the reform's policy, inputs = {});
    • its pending report run, trigger initial.

    No row has a Modal call ID. A foreign-key refusal on insert answers 400 (unknown_household for the household).

  5. Starts one call of the household function, by name, with the new keyword household_report_run_id and a HouseholdReportRunCalculation. That is each side's HouseholdExecutionRequest, whose calculation_id is the side's simulation run.

  6. Records or fails: it records the call's ID on the report run while it is pending with none. If Modal refuses the call or doesn't confirm it within 10 s, it fails the report run and its simulation runs and answers 503 modal_call_refused with report_id; a run the function claimed meanwhile is answered as it stands.

  7. Builds the payload with one public builder, household_report_calculation(household=, year=, baseline=, reform=, worker=). It builds the payload from a report's stored inputs, so a restart of a call that never started (C1 · 19) or a re-run (C1 · 17) uses the same code.

  8. Answers 201: report_id, report_run_id, status, year, and each side's simulation and simulation run (the reform's null for a baseline-only report).

Every other answer is an economy report's: 503 not_configured, manifest_unavailable or database_unavailable before any write, and 500 internal_error.

What the household function does (stage12_runtime/household_report_recording.py)

As the Modal worker role, under its own Modal call ID, the call:

  1. claims the report run with C1 · 8's claim_report_run;
  2. reads which simulation runs the report run names, validates the payload, and refuses one naming other runs;
  3. claims each side's simulation run, calculates it, and writes the HouseholdExecutionResult, or the HouseholdExecutionFailure and a one-line summary, to household_result and error_message;
  4. completes the report run only if every side's run succeeded; otherwise fails it with the first such side's reason.

How it handles each other case:

  • Both sides are always calculated, so each run has its own outcome.
  • A report run another call holds, or one already finished, isn't calculated again.
  • A restarted call keeps a side it already recorded.
  • A pre-emption writes nothing.
  • Any other error, a refused payload included, fails the report run and each of its simulation runs left unfinished, then propagates.
  • The Modal-side writes are in HouseholdReportRunStore, a ReportRunStore, in libs/policyengine-stage12-persistence. Its simulation_runs mapping gains household_result.
  • The stuck-run sweep is unchanged. A household report run has a recorded call, and its simulation runs are judged once it has finished, as an economy report's are. /ready and the roles' grants are unchanged: the Modal worker role already updates simulation_runs.household_result and the report run's status columns.

No add_missing_variables option

By the user's decision, household reports always add the household's missing variables and take no option for it. Their one caller, v3's household report page, needs every value. /v2/household-calculations keeps its own switch for the callers of /calculate, who get only the values they ask for.

Builds on, and what merges first

Environment variables and secrets

No new variable or secret.

Name Provider Wiring Verified
MODAL_WORKER_DATABASE_URL (read by the household functions' new report run writes) Modal secret policyengine-modal-worker-database Unchanged since #903: modal.Secret.from_name(..., required_keys=["MODAL_WORKER_DATABASE_URL"]), attached with secrets=[modal_worker_database_secret] to calculate_household_us and calculate_household_uk; read by household_report_run_store() The attachment, in v2_app.py. Not verified here: that the secret exists in the staging and main Modal environments, and that its role holds the grants there. B1 · 7 and B1 · 2 recorded both.
SIMULATION_SERVICE_DATABASE_URL Secret Manager, name from SIMULATION_SERVICE_DATABASE_URL_SECRET_NAME Unchanged: --set-secrets on every deploy The service writes only the tables and columns #1052 already writes.
MODAL_TOKEN_ID, MODAL_TOKEN_SECRET Secret Manager, names from MODAL_TOKEN_ID_SECRET_NAME, MODAL_TOKEN_SECRET_SECRET_NAME Unchanged since #1046: --set-secrets on every deploy Household reports call the same household functions /household-calculations calls.
SIMULATION_V2_MODAL_ENVIRONMENT, SIMULATION_V2_MANIFEST_NAME Workflow input and literal Unchanged since #1046: env file Yes. Household reports are configured when household calculations are.

Tests

Each change started as a failing test, committed with "(failing)" in its subject and seen failing for the expected reason, then the change that makes it pass. Run by node ID, only the tests this PR adds or changes:

  • libs/policyengine-stage12-persistence: test_household_report_run_store.py, 6 cases.
    • These run against a PostgreSQL 17 database built by the Public API's v2 migration chain at master c4419dcea, with its privileges applied and verified, connected as the Modal worker role.
    • No CI job sets REPORT_RUN_TEST_DATABASE_URL, so they skip in CI, as the other report run store tests do.
  • libs/policyengine-simulation-contract: test_household_report_execution.py, 9 cases.
  • projects/policyengine-simulation-executor: test_stage12_household_reports.py, 22 cases, and 3 new cases in test_stage12_modal_app.py.
  • libs/policyengine-simulation-persistence: test_household_report_store.py, 8 cases, against the same database acting as the simulation service's role.
  • projects/policyengine-simulation-entry:
    • test_household_report_submissions.py, 25 cases;
    • test_household_report_routes.py, 16 cases;
    • 2 new cases in test_household_workers.py;
    • in test_openapi.py: the changed test_economy_routes_declare_every_answer, the new test_a_report_request_is_an_economy_or_a_household_report, and test_normalized_contract_matches_old_gateway, whose schema list changed.
  • projects/policyengine-apis-integ: test_household_report_generated_client.py, 6 cases, against the client regenerated from this branch. Against a client generated from Create economy reports and simulations through POST /reports and POST /simulations #1052's branch, it fails to import HouseholdReportCreated.

Before each commit: CI's pinned Ruff (format and lint), the rules registry render, the quality guards, uv lock --check, and Pyright for the changed packages. No other tests were run here; CI runs the rest.

Review

Read-only adversarial rounds, in the review-code format:

  1. Two bugs:

    • a side whose record changed nothing, or one found failed with no recorded failure, could let the report run succeed;
    • a payload the contract refused, or a claim that raised, wrote nothing and was left to the sweep.

    Both fixed test-first. Its quality findings fixed: the persistence record renamed HouseholdReportSimulation, and the household_not_calculable locations documented.

  2. One bug: that second fix still left the report's simulation runs pending. Fixed: the call reads the report run's own runs once it holds it, and fails them on any later error. Quality findings fixed: the answer for a report run another writer finished, a side's reason always set when it didn't succeed, and the migration doc and docstrings.

  3. One bug: a side whose claim raised and rolled back stayed pending. Fixed: unfinished runs are failed while still active, whoever holds them; household simulation runs are never shared. Documentation findings fixed.

  4. The code round 3's fix added: no bugs. Its quality findings (docs, and tests of existing behaviour) are fixed, with no production code change.

  5. The public payload builder, added after round 4 for C1 · 19: no bugs. Its quality findings are fixed test-first:

    • separate baseline/reform parameters;
    • the country taken from the household;
    • policy_values as the side field's name;
    • duplicate run IDs raise HouseholdReportBuildError.
  6. Removing the add_missing_variables option (the user's decision): no bugs. Its quality findings are fixed:

    • a contract refusal of the option is the service's own error;
    • the Modal-side test fixture stores {};
    • the module docstring is corrected.

Deferred quality findings: some duplication between household_report_submissions and household_calculations (contract error locating, SPM fields), and no Postgres sweep test seeding a household report run.

CI

This PR targets main, so the full pr.yml run applies. The first run, on e25c235d, failed in test_every_model_is_runtime_evaluated_for_ruff: the new contract model's base wasn't in ruff.toml's runtime-evaluated-base-classes. That is fixed in 9b14bf2c.

🤖 Generated with Claude Code

anth-volk and others added 20 commits November 13, 2026 00:20
A household function given a household report run claims the report run
and each of its simulation runs under its call ID, records each run's
calculated household or failure document in household_result, then marks
the report run succeeded. The tests seed household report runs as the
simulation service creates them, with or without a reform. They fail
because HouseholdReportRunStore does not exist yet.

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

HouseholdReportRunStore extends the report run store with the writes a
household function makes for a household report run: a check that the
report run names exactly the call's simulation runs (a reform may be
absent), each simulation run's completion or failure with its household
document in household_result, and the report run's completion. Each
applies only while the call holds its row running. The run tables'
simulation_runs mapping gains household_result, and the condition that a
call holds a row running becomes public so the new store shares it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ort run (failing)

A household report run's calculation is its baseline's household function
request and, optionally, its reform's, one household calculated the same
way, each request's calculation ID naming its simulation run. A household
function given the report run's ID claims the report run, calculates and
records each side, and marks the report run succeeded or failed; a
restarted call keeps a side it already recorded, and a pre-emption records
nothing. The tests fail because the contract model, the recording code and
the functions' household_report_run_id keyword do not exist yet.

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

The household contract gains HouseholdReportRunCalculation: a report run's
baseline request and, optionally, its reform's, each request's calculation
ID naming its simulation run, both calculating one household the same way.

calculate_household_us and _uk take household_report_run_id. With it, the
call claims the report run under its Modal call ID, checks the report run
names the call's simulation runs, then claims, calculates and records each
side: the result or failure document in household_result, a failure's
one-line summary in error_message. Both sides are always calculated; the
report run then succeeds, or fails with the first failed side's summary.
A report run another call holds or that finished is not calculated, a
restarted call keeps a side it already recorded, an unexpected error or
cancellation fails every run left unfinished, and a pre-emption records
nothing. A call naming both a household job and a report run is refused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The service reads a household report's catalog version, stored household
and each side's policy in one transaction, refusing a missing row and a
side's policy naming parameters the catalog version lacks on that side.
It then creates, in one transaction, a household simulation and a pending
simulation run for each side, the report (type household, its household
and options) and its pending report run, with no reform rows for a report
of the baseline alone. The tests fail because the household report
records and store do not exist yet.

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

HouseholdReportStore reads the country's catalog version and model, the
stored household and each side's policy values in one transaction, raising
ReportSideError for the side whose policy is missing or sets parameters
the catalog version lacks. insert_household_report creates, in one
transaction, a household simulation and a pending simulation run for each
side (never reused), the report of type household with its options, and
its pending report run, naming no reform for a report of the baseline
alone. Recording the call and failing an unstarted run reuse the economy
report run writes. references_checked and insert_simulation_with_run lose
their underscores, as #1054 renames them, so the two branches agree.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…he call (failing)

POST /reports takes a household report, told apart from an economy one by
report_type: a stored household, the baseline's policy and optionally the
reform's, and add_missing_variables. The service reads the inputs on the
default worker's catalog version, refuses what is missing or cannot be
calculated before writing, creates the report and its runs, and starts
one household function call for the report run, recording its call ID or
failing the run and its simulation runs. The route answers the economy
codes plus unknown_household, no_household_function and
household_not_calculable, and locates refused fields without the type.
The tests fail because the household report models, service, caller
method and codes do not exist yet.

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

POST /reports takes a household report, told apart from an economy report
by report_type, and refused fields are located without the type, as #1054
locates them for budget windows. HouseholdReportSubmissionService finds
the default worker's household function, reads the catalog version, the
stored household and each side's policy, builds each side's household
contract request for the household's default year with its simulation
run's ID, creates the report and its runs, then starts one household
function call with the report run's ID. It records the call's ID, or
fails the report run and its simulation runs when Modal does not confirm
the call; a run the function claimed first is answered as it stands.

Refusals use the economy answers: 400 adds unknown_household and
no_household_function, 422 adds household_not_calculable for a household
with no default year, one that cannot be read, or one the contract
refuses. A foreign-key refusal of the household answers unknown_household.
ModalHouseholdFunctionCaller gains start_report_run, which passes
household_report_run_id.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Public API sends a household report through the generated client's
HouseholdReportRequest, with or without a reform, and reads the 201 as a
HouseholdReportCreated with its year and sides, an economy report's 201
still as an EconomyReportCreated, and the household refusals with their
codes. Run against a client generated from #1052's base, the tests fail
at import: HouseholdReportCreated does not exist there.

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

The entrypoint README describes POST /reports for household reports: the
request, what the service creates and starts, and its refusal codes. The
persistence READMEs describe the household report records and store, and
the Modal worker role's household report run writes. The changelog
fragment describes the change for the release notes.

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

A side whose record changed nothing, or one found failed with no recorded
failure, fails the report run instead of letting it succeed. The
household function hands the payload over as sent, and the recording code
validates it only once the call holds the report run, so a payload the
contract refuses, or a claim that raises, fails the report run at once
instead of leaving it to the stuck-run sweep. The tests fail because the
recording code takes an already validated calculation, outside its error
handling, and finishes a report run on any side without a failure reason.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…refused payloads at once

A report run now succeeds only when every side's simulation run did. A
side whose record changed nothing, because another writer finished the
run first, fails the report run with household_simulation_run_unavailable,
and a side found failed with no recorded failure fails it with "no failure
was recorded".

The household function hands the payload to run_household_report as
sent. The claim and the payload's validation now run inside the recording
code's error handling, so a payload the contract refuses, or a claim
that raises after it may have committed, fails the report run at once
instead of leaving it, and its simulation runs, to the stuck-run sweep.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review round 1 found HouseholdReportSide naming a Literal of side names
in the contract and a record of new simulation IDs in the persistence
library. The persistence record becomes HouseholdReportSimulation, as the
economy report's is ReportSimulation. The tests fail because the record
still has the old name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e 422s point

The persistence library's record of a household report side's new
simulation and run is HouseholdReportSimulation, so HouseholdReportSide
names only the contract's Literal of side names. The household_not_calculable
code's comment and the entry README now say a contract refusal is located
at the field it traces to: the household, a side's policy, or
add_missing_variables.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…runs (failing)

Once a household function holds a report run, it reads the report run's
baseline and reform simulation runs from the database. A payload the
contract refuses, or a report run naming other runs than the payload,
then fails those runs with the report run, rather than leaving them
pending for the stuck-run sweep; a report run whose runs can't be read
is failed alone. The call answers a report run another writer finished
with that run's status. The tests fail because the store has no read of
a household report run's simulation runs and the call checks them only
against the payload.

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

Once the household function holds a report run, it reads the baseline
and reform simulation runs the report run names
(HouseholdReportRunStore.household_report_simulation_runs, replacing the
check against the payload), then validates the payload and compares its
runs with those. Any error after that read, a refused payload or a
mismatch included, fails the report run and each of its own runs left
unfinished at once; only a failed read leaves the runs to the sweep.

Round 2's quality findings: a side's reason is empty only for a run that
succeeded, so a report run's failure never falls back to a placeholder; a
report run another writer finished is answered with that run's status;
the migration doc describes household report runs, and the docstrings
and README say a report run fails with its first unsucceeded side's
reason.

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

A side's claim can raise after rolling back, leaving its run pending with
no call. When a household report call stops, every one of the report
run's runs it left unfinished is now to be failed while still active,
whether or not this call holds it, as household simulation runs are
never shared. The test of a raising claim fails because the side being
calculated is failed only while this call holds it running. Two more
tests cover a payload that adds a reform the report run lacks, or leaves
out the one it names; they describe existing behaviour and pass.

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

When a household report call stops, each of the report run's simulation
runs it left unfinished is failed while still pending or running, whether
the call holds it or the side's claim rolled back, instead of only while
the call holds it running. Household simulation runs are never shared, so
no other call works on them.

Round 3's quality findings: run_household_report's docstring and the
migration doc say an error in the report run's claim, or in the read of
its runs, fails the report run alone; the v2 app's docstring and sizing
comment describe a report run's call, its two calculations without axes
within the same timeout; the migration doc's household section no longer
says every call is one calculation; and the test module's docstring says
a report run fails when any side's run didn't succeed.

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

Review round 4 found no bugs. Its quality findings:
- the docs now say a claim error fails the report run only if the claim
  applied, and that the failure write for unfinished runs checks no call,
  relying on new simulation runs for each household report and on sides
  this call finished or found held by another call being out of the list;
- the v2 app's docstring and sizing comment say a reform is optional, a
  validation call records nothing, and the simulation service builds the
  sides without axes;
- a test checks that an error leaves alone a side this call recorded or
  found held by another call, and that a raising claim's runs are failed
  with no call. These describe existing behaviour and passed when first
  run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#1052 merged into main as faa0f29, so this branch now builds on main.
The merge brings only the release commit's CHANGELOG.md, VERSION and the
removal of changelog.d/c1-9-economy-reports.added.md, with no conflicts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
anth-volk and others added 9 commits November 13, 2026 01:50
…ated model

HouseholdReportRunCalculation subclasses StrictHouseholdModel from
another module, which ruff doesn't follow, so the guard test
test_every_model_is_runtime_evaluated_for_ruff failed. The base joins
runtime-evaluated-base-classes in ruff.toml, as StrictContractModel is
listed, so ruff keeps the imports the model's annotations need at
runtime.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…puts (failing)

A restart of a household report run whose call never started (C1 · 19),
or a re-run (C1 · 17), rebuilds the household function's payload from the
stored report: its country, household, year, add_missing_variables and
each side's simulation run and policy values. One public builder,
household_report_calculation, is to serve creation and those, using the
year it is given rather than the household's default year now. The tests
fail because the builder exists only inside the service's _prepare.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
household_report_calculation builds the household function's payload from
a household report's stored inputs: country, stored household, year,
add_missing_variables, each side's simulation run and policy values
(HouseholdReportSideInputs), and the worker. Creating a report passes the
household's default year; a restart (C1 · 19) or re-run (C1 · 17) passes
the stored report's year and option, so it calculates what was created.
It refuses a household it can't read, or one the contract refuses, with
household_not_calculable, as creation did. The service's _prepare now
checks the default year and calls it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…r (failing)

Review round 5 found no bugs. Its quality findings for the builder:
- it takes the baseline and an optional reform as their own parameters,
  so no call can leave out the baseline;
- it takes the country from the stored household;
- a side's values are policy_values, which also fit a baseline;
- two sides recording one simulation run raise HouseholdReportBuildError,
  not a raw validation error;
- the baseline-only test checks the baseline's policy values.
The tests fail because the builder still takes a mapping of sides, a
country and a side's reform.

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

household_report_calculation takes the baseline and an optional reform as
parameters instead of a mapping that could leave out the baseline, which
is now always checked first. It takes the country from the stored
household. HouseholdReportSideInputs.reform becomes policy_values, which
fits the baseline too. Two sides recording one simulation run raise
HouseholdReportBuildError. The docstring says a restart reads the
household and policy values as they are now, and that a refusal's
locations are a report request's fields.

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

#1054 (budget windows) merged into main, and also made the POST /reports
body a union. The merge keeps both report types:
- one ReportRequest of all three types (economy, budget_window, household)
  in a new report_api_models module, replacing the two-type unions each
  branch had, and report_request_errors importing it;
- create_report dispatching household reports to _household_report_answer
  and the other two to a new _economy_report_answer, which keeps the
  router function under Ruff's complexity limit;
- response_model listing the three answers, and the route's summary and
  description naming all three;
- both packages' exports and the Stage 12 persistence README keeping
  both sides.
#1054's tests that used "household" as an unknown report type now use
"not_a_report_type", and the unknown-type message lists the three types.
The OpenAPI tests check the three-type discriminator and 201 answers, and
the household request's fields in a test of their own.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…les (failing)

Decided by the user: a household report's only caller, v3's household
report page, needs every variable calculated, as the Public API's stored-household
route always gives it. So a household report has no add_missing_variables
option. The request refuses the field, the report stores no options
(reports.inputs is {}), and each side's household function request adds
missing variables. The payload builder takes no option.
/v2/household-calculations keeps its own switch for /calculate's callers.
The tests fail because the request still takes the option and stores it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Decided by the user: a household report's only caller, v3's household
report page, needs every variable calculated, as the Public API's
stored-household route always gives it. HouseholdReportRequest no longer
takes add_missing_variables, so a body sending it is refused with 422
extra_forbidden. A household report stores no options (reports.inputs is
{}), and household_report_calculation, taking no option, gives every
side's request add_missing_variables=True, so a restart or re-run matches
what was created without reading anything more.

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

The entry README says a household report has no options, adds every
variable the household lacks as the Public API's stored-household route
does, and stores {} in reports.inputs; its refusals are no longer located
at an option. The household_not_calculable code's comment matches.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
anth-volk and others added 2 commits November 13, 2026 03:20
…is the service's fault (failing)

Review round 6 found no bugs. One of its quality findings: a household
report sets add_missing_variables itself, so a contract refusal of it
should raise HouseholdReportBuildError, not a 422 located at a request
field the report no longer takes. The shared mapping still traces it to
the household calculation request's field of that name. The test fails
because the refusal is answered as the caller's.

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

A contract refusal of add_missing_variables on a household report's side
raises HouseholdReportBuildError, as a refusal of any field the service
builds does, instead of a 422 located at a request field the report no
longer takes; the household calculation route's mapping is unchanged.
The module docstring says a household report has no options, and the
Modal-side store tests seed household reports with inputs {}, as the
service now creates them.

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

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.

Create household reports through POST /reports and calculate them in one household function call

1 participant