Repository navigation
v3 standalone household report and saved households, unreachable until v3 moves to the v2 routes - #1392
Draft
anth-volk wants to merge 29 commits into
Conversation
…tores them Saved households in v3 will live on the Public API's /v2/households (immutable, deduplicated content) and /v2/user-households (the user's name for one), so the app needs the v2 household document: - types/v2Households.ts: the wire types for both routes. The PATCH body includes household_id, which points a saved household at another stored household once the Public API accepts it. - householdToV2CreateBody builds the document exactly as the Public API translates a v1 household (translate_legacy_household): ids in name order, v1 names kept as source_name, group members as each person's memberships, the SPM settings on the document, and no label. So a household v3 saves deduplicates with the dual-write copy of the same v1 household. A person outside a collection, which v2 rejects, fails here first, naming the collection. - householdFromV2Item reads a stored household back into the app's model, with the stored id, people in the builder's order, and a record's id as its name when it has no v1 name. Tests first: the codec tests failed to import the missing module. The fixtures' documents were checked against the Public API's own translation: for the US single parent, the UK single parent and the US couple with two children, translate_legacy_household on the same v1 households gives the same canonical content hash and default year. Nothing uses it yet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The store behind v3's saved households, with accounts on, signed in or signed out as the browser's unauthenticated user: - save: POST /v2/households (which returns the existing household for identical content), then POST /v2/user-households with the label and the v2 household_id; - list, most recently updated first; a row naming only a v1 household, or whose household fails to load, is listed as unavailable rather than failing the list; - open, rename (PATCH of the name) and delete (the row only: stored households are shared); - saveChanges: edited content is stored, and one PATCH points the saved household at it (household_id) together with any new label, so the saved household keeps its id, name and creation date. Identical content changes no row. If the PATCH fails, the reader is told the changes were not saved; the saved household is as it was. Pointing a saved household at another stored household needs the Public API's PATCH /v2/user-households to accept household_id; until it does, nothing reaches this code. Tests first: the store's tests (15) failed to import the missing module, then pass against Public API responses routed by method and path. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
useSavedHouseholds lists the session's saved households (signed in, or
signed out as the browser's unauthenticated user), useSavedHousehold
opens one, and save, rename, delete and saveChanges are mutations that
read the list and every opened saved household again once they
succeed. Cached saved households are keyed by whose they are, as v3's
other saved work is. Without accounts nothing is listed or asked for,
and saving fails plainly ("Saved households need an account.") rather
than saving in the browser.
Tests first: the hook tests (6) failed to import the missing module.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SaveHouseholdForm, for the household in the builder: a "Household label" field and a Save button. A new household is saved under the label. For a saved household, "Save changes" saves only what changed: edited content is stored and the saved household pointed at it, with any new label in the same request, so the saved household keeps its id; a new label alone only renames it. A household counts as edited only when its content differs as POST /v2/households takes it (haveSameStoredContent in the codec), since a household read back from the Public API lists its groups in name order and would otherwise read as changed. A failed save says why; without accounts the form says saved households are not available, with nothing to press. Tests first: the form's tests (9) failed to import the missing component, and the comparison's tests (4) failed with haveSameStoredContent not a function. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SavedHouseholdPicker lists the session's saved households in a country, each by its label (or "Untitled household") with who it describes, in the year its inputs are in, and hands the chosen one on with its household. The saved household the builder holds is marked as chosen. One that cannot open (its row names only a v1 household, or its household failed to load) is listed but cannot be chosen. While loading, on a failed load, with none saved, and without accounts, it says so. savedHouseholdDisplay holds what the save form, this picker and the saved-household list share: the name, the description, the notice shown instead of a list, and the "not available" text the save form now imports. Tests first: the picker's tests (8) failed to import the missing component. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SavedHouseholdsPage ("Your households") lists the session's saved
households, each by its name with who it describes, to rename or
delete:
- Rename opens the shared rename dialog; the new label is saved with a
PATCH of the name. A failed rename keeps the dialog open with the
reason.
- Delete asks first, then deletes the saved row only (the stored
household stays). A failed delete names the household and the reason
above the list, and the household stays listed.
- A saved household that cannot open is listed and can still be renamed
or deleted.
Without accounts it says saved households are not available. No route
renders the page yet.
Tests first: the page's tests (8) failed to import the missing page.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Issue #1095 asks for code that goes from a household result straight to a pandas DataFrame for charts. getHouseholdReproducibilityCode takes its options as named fields ({ earningVariation, includeDataFrame }) rather than positional booleans, and includeDataFrame appends: - for a point calculation, one row per household result (net income, market income, tax, benefits); - for an earnings variation, one row per earnings level, with the household's employment income (summed to the household) beside each result, ready to chart. The four variables exist in the US and UK models. Generated blocks were run locally: for a UK household under a personal allowance reform against policyengine-uk 2.130.1, and for a US household under current law against policyengine-us 2.2.1, the point block prints four rows and the variation block 401. HouseholdReproducibility offers the switch ("Include a pandas DataFrame") only when offerDataFrame is passed. Nothing passes it yet, so the existing household report's Reproduce in Python is unchanged. Tests first: the code tests failed (no DataFrame code; the options object did not type-check) and the component's switch test failed (no such switch). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…old report Issue #1096: "Varying your earnings" never offers tip income, even for a household with tips, because the variable list leaves out every input variable, and tip_income is an input. - EarningsVariationSubPage takes alsoOfferInputs: inputs offered beside the calculated variables when the variation returns values for them. Other inputs stay out. - householdAmountInputs lists the nonzero amounts a household itself sets (tip income, for one with tips), leaving out the earnings the chart varies and age, which every household carries. - HouseholdBreakdownSource takes householdInputs, which reach only the earnings variation. Nothing passes them yet, so the existing household report and v3's report section offer the same variables as before; the standalone household report will pass the household's inputs. The SALT cap, the issue's other example, is calculated, so the list already offers it. Tests first: householdReport.test.ts failed to import the missing module; the variation page offered no tip income when asked; the breakdowns did not pass the household's inputs on; the new props did not type-check. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e household report HouseholdBuilderPanel is the household builder as v3 configures it: inputs grouped by topic with the country's featured inputs first, a variable search that lists only the household's own state's programs, only inputs that describe a household and no twin of a field the builder already shows, the country's names for people (a UK household's children), labelled inputs, and the SPM geography choice the builder shows for a US household. The report section's household card now renders it, passing its suggestions and analytics callbacks, so the standalone household report can use the same configuration instead of a copy that could drift. The section behaves as before: its tests, which render a stand-in for the builder and check what it is given, pass unchanged. Tests first: the panel's tests (5) failed to import the missing component; the filter test also failed until the test's model information listed employment income as a basic field, showing the twin rule is checked. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…households HouseholdNetIncomeResult shows a household's result when there is no reform to compare: its annual net income in whole dollars or pounds, then the v2 breakdowns under that one policy (the net income tree first, varying earnings and marginal tax rates), with the calculation's SPM methodology under the net income tree and not under the variation views, which show their own. householdResultParts holds what it shares with HouseholdImpactResult: the money formatting, the card style and the output tabs. The impact result now uses them and behaves as before (its tests and the section's analytics tests pass unchanged). Nothing renders the new result yet; the standalone household report will, for a household with no reform. Tests first: the result's tests (4) failed to import the missing component. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
HouseholdReportPage reports one household under a baseline and, optionally, a reform: - the net income headline, calculated once: the change in net income with a reform (HouseholdImpactResult), or net income alone for a baseline-only household, including one whose reform is the baseline (HouseholdNetIncomeResult); - the net income tree, varying earnings and marginal tax rates, with the amounts the household sets, such as tip income, offered in varying earnings (#1096); - the household's inputs, the policy summary, and Reproduce in Python with the pandas DataFrame option (#1095); each Python block is given its own policy by id (so a policy that fails to load is never given to the other block) and pinned to the .py bundle its own calculation reported; - a year the reader chooses from the model's tax years (#498): the household's inputs move to it with the report builder's copyHouseholdToYear, a household or saved household from another year opens in the report's year, and a year change clears the result; - the household builder as v3 configures it (HouseholdBuilderPanel; a new US household starts on national poverty thresholds), the saved-household picker and the save form, whose confirmation or error stays on screen after a save. Calculate is disabled while the household on screen is the one calculated; after a failure, "Try again" asks for the same household once more. The result keeps describing the household calculated after the builder changes. It calculates through the Public API's v1 household routes, as the report section does. newV3Household, the report section's new household (with a UK nation's place), moves to libs/flagship/householdReport so both use it; the section behaves as before. Tests first: the page's tests (23) failed to import the missing page. Two then failed for their own setup (one rendered nothing; one stored a different household from the one sent) and were fixed in the tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…oves to v2 householdEntryPoints.ts gathers the standalone household report, the saved households page, the saved-household picker and the save form, and says what the change that moves v3 to the Public API's v2 routes does to reach them: enable Build's "A household" option, render the report for household reports saved through /v2/reports, and add a saved households route. No runtime flag or environment variable. householdEntryPoints.test.tsx checks that: - Build's "A household" option is listed but cannot be chosen; - no file in the app or the calculator imports a household-only module (the report, the saved households page and list, the picker, the save form and their shared parts, the hooks, the store and the v2 codec), by an @/ specifier or a relative path, except those modules themselves; - the entry points are the four above. Tests first: the tests failed to import the missing module. The import check was confirmed to fail, naming Report.page.tsx, when that page imported the saved households page by a relative path. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review found that the household report's "Try again" asked for the household the builder held whenever it differed from the one that failed, without checking that it could run, so a household the Calculate button refused (for example county poverty thresholds without a county) could reach the Public API. The report section already tried the failed household again in that case. The request handling the two shared by copy now lives in one hook, useRequestedHouseholdRun: what was asked for, running, failed and the result, asking again for the same household, whether the builder's household is the one shown, and retry, which asks for the household as it is now only when it can run, and otherwise for the household that failed. The report section and the household report both use it; the section's analytics and behavior are unchanged. Tests first: a report test that fails a calculation, edits the household into one that cannot run and presses "Try again" failed (the edited household, with tip income, was sent); it passes now, and the section's tests pass unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… screen
Review found that after a save the form kept saying "Saved" when the
household was then edited (or moved to another year), next to an
enabled "Save changes"; only a label edit cleared it. The form now
remembers the household and label its last save stored, and shows
"Saved" only while the builder's household has the same stored content
and the label is unchanged. A failed save clears it.
Tests first: a test that saves, then gives the form an edited household,
failed ("Saved" was still shown); it passes now.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…he report opens one Review found two uneven year rules: - householdFromV2Item took a stored household's year from the latest year its values name, not the stored default_year. A household with inputs in two years read back in the later one, and one whose values name no year (which the v2 document allows) read back with no year, showing "No year set" and always counting as changed. It now reads back in default_year. - The household report opens a household that cannot move into the report's year (inputs in several years) in its own year, with the reason, but the saved-household picker refused to open one at all. The picker's choice now opens the same way. Tests first: the codec's two cases failed (2027 and null instead of 2026), and a report test that opens a two-year saved household from the picker in a later report year failed (the builder kept its household); they pass now. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review found the saved-household queries were keyed only by whose they are, while the store lists one country's saved households, unlike v3's other saved work, which keys by user and country. In a query client shared across countries, the same user's list for one country was shown for the other. savedHouseholdKeys now include the store's country, and changes invalidate that country's queries. Tests first: a hook test that lists a user's US households, then switches the same user to the UK with the same query client, failed (the US list was shown); it passes now. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review found that every read of the saved-household list (on load, and
again after each save, rename or delete) sent a GET /v2/households/{id}
for every saved row. Stored households never change, so the store now
keeps each one by its v2 id for its life: a household it has just saved
or saved changes to is kept from the response, and a failed fetch is not
kept, so the next read asks again.
Tests first: listing twice and opening the saved household sent three
household reads (now one), and listing right after a save sent one (now
none); a household that failed to load is asked for again on the next
list.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…holds Review found that the saved-household delete dialog copied the saved reform delete dialog's markup and classes. DeleteConfirmationDialog now holds them: a Delete button that asks first, naming what is deleted and what the delete does, with a compact form for a row of a list. DeleteReformDialog keeps its text (including what happens to the reform's reports) and now renders it; the saved-household list uses it directly, and its copied dialog is removed. The saved reforms page behaves as before: its tests pass unchanged. Tests first: the shared dialog's tests (4) failed to import the missing component. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… test data in fixtures Review found hard-coded values in the new components and test data written into the report page's test: - HouseholdNetIncomeResult's heading gap and headline radius now use spacing tokens; the save form's label field and the saved households page take their widths from Tailwind utilities. - The report page test's policy responses, reform parameter, validation answers and model years move to a fixtures module. No behavior changes, so there was no failing test to write first; the affected tests pass unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ned changes nothing Review found a race: opening a saved household from the picker while a save was still running gave the save form a new key, but the earlier form's save still reported its result when it finished, so the page pointed the builder's saved household back at the row that save made. The picker then marked that row as chosen, and "Save changes" would have sent the opened household's content to that other saved row. SaveHouseholdForm now reports nothing for a save that finishes after the form has gone. The save itself still completes, and the saved households list is read again as after any save. Tests first: a report test that starts saving a new household, opens a saved one before the save finishes, then lets it finish, failed (the picker marked the new row and "Save changes" was enabled); it passes now. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review found that while a household calculated, the report disabled the builder and the year, but the saved-household picker still opened another household, which replaced the builder's household and threw the running calculation away without notice. SavedHouseholdPicker takes disabled, and the report passes it while a calculation runs. Tests first: a picker test with disabled and a report test that opens nothing while calculating failed (the options could be chosen); they pass now. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review found that the cache of stored households added earlier lived in each store instance, and each hook call makes its own store, so the save form, the picker and the list each kept their own: a list read after a save in the same page still fetched the household just saved. ApiSavedHouseholdStore now takes a StoredHouseholdCache. The hooks give every store the session's query client, keyed by country and v2 id (stored households are the same for every user and never change); a store used on its own keeps them for its life, as before. A household just saved or saved to is kept from the response, and a failed fetch is not kept. Tests first: a hook test that saves through one hook and lists through another failed (the list fetched the saved household); it passes now, and the store's own cache tests pass unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…re not saved Review found that saveChanges said "Your changes were not saved: …" only when pointing the saved household at the edit failed; when storing the edited content failed first, the reader saw the Public API's raw message, although in both cases nothing changed. Both now raise SavedHouseholdChangesNotSavedError. Tests first: a store test whose POST /v2/households fails during saveChanges failed (a raw V2ApiError); it passes now. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…th households Review found that the household report copied the report section's Calculate button with its validation message, the run's progress (with a second expected-duration constant) and the run's failure panel, and that the baseline-only result copied the impact result's headline styles. householdRunParts now holds HouseholdCalculateRow, HouseholdRunProgress, HouseholdRunFailure and the expected run duration, and householdResultParts the headline's caption and figure styles. The report section, the impact result, the household report and the baseline-only result use them, with the same text and styles as before. No behavior changes, so there was no failing test to write first; the section's, the results' and the report's tests pass unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rough unknown Review found that the codec built and read documents as plain Record<string, unknown> objects and converted them with "as unknown as", so the country-specific document types never checked its output. The collections table now names V2GroupCollection and V1GroupCollection keys (derived from the document and v1 types), the codec builds and reads groups as typed collections, and the only remaining assertions are for values passed through as stored. The codec's tests (exact documents for US and UK households, matching the Public API's translation, and round trips) pass unchanged. A test that a stored household without a default year reads back in the year its values name is added; it passed at once, as the household model already falls back to that year. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…is not an edit
Review noted that a saved household opened in a report of another year
counted as edited at once: the report moves it into its year, so its
stored content differed, "Save changes" was enabled on opening, and a
save of a new label alone also stored the moved household and pointed
the saved household at it.
The form now compares the builder's household with the saved household
moved into the builder's year (when it can move). Opening it is not an
edit, a new label alone renames it, and an edit after opening is saved
as before, in the report's year.
Tests first: a report test that opens a 2026 saved household in a 2027
report failed ("Save changes" was enabled); it now also checks that a
new label sends only a PATCH of the name.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review noted that only a caller passing the save's result back as `saved` turned "Save household" into "Save changes": with a caller that did not, the button stayed enabled and a second press saved the household again as another saved household. The form now keeps the saved household its last successful save made or changed and edits that one, whether or not the caller passes it back; a saved household the caller passes still comes first. Tests first: a form test with no onSaved that saves a new household failed (no "Save changes"); it passes now, with only the one save sent. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…pply Review noted that the picker's options set their border color inline, which overrode the hover border class, and that the household output tabs (shared with the report section) did the same with their text color, so neither hover color ever showed. The colors that change on hover now come from classes (the same tokens), and only the chosen option or tab sets its color without a hover. A style-only change, so there was no failing test to write first; the picker's, the results' and the section's tests pass unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s folder, one card style Review noted three smaller points: - The saved-household hooks, the picker and the household report took their country as a plain string (or restated 'us' | 'uk'), while the store and wire types use AssociationCountryId; they now use it, and the list's cast to ListedSavedHousehold[] is removed. - The codec's fixture moves to the fixtures folder that mirrors the codec's own (models/household). - The report's sections reuse the household result card style instead of repeating its border, radius, background and padding. No behavior changes, so there was no failing test to write first; the affected tests pass unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
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 #1391
Stacked on #1386.
What changes
The code for v3's standalone household report and saved households. No route or control reaches it yet (see "Unreachable until v3 moves to the v2 routes"), so users see no change.
v2 household documents (
models/household/v2DocumentCodec.ts,types/v2Households.ts). The app writes a household as the Public API's/v2/householdsdocument exactly as the Public API translates a v1 household (translate_legacy_household): people and groups take ids in name order, v1 names are kept assource_name, group members become each person's memberships, the SPM settings go on the document, and no label is sent. So a household v3 saves deduplicates with the dual-write copy of the same v1 household. A stored household reads back into the app's model in its storeddefault_year.haveSameStoredContentcompares two households asPOST /v2/householdstakes them, since a stored household reads back with its groups in name order.Saved households (
api/savedHouseholdStore.ts,hooks/useSavedHouseholds.ts), with accounts on, signed in or signed out as the browser's unauthenticated user:POST /v2/households(which returns the existing household for identical content), thenPOST /v2/user-householdswith the label and the v2household_id;PATCHof the name) and delete (the row only: stored households are shared);PATCH /v2/user-households/{id}points the saved household at it (household_id) with any new label, so it keeps its id; identical content changes no row; either step failing says "Your changes were not saved";Without accounts there are no saved households: each control says so, and nothing is saved in the browser.
Saved household interface (
components/flagship/household/): a "Household label" field with Save (SaveHouseholdForm: a new household, changes to a saved one, or a rename alone; moving a saved household into the report's year is not an edit; "Saved" shows only while it describes what is on screen); a picker (SavedHouseholdPicker); andSavedHouseholds.page("Your households") with Rename and Delete, which asks first.Standalone household report (
pages/flagship/HouseholdReport.page.tsx), for one household under a baseline and, optionally, a reform:HouseholdImpactResult), or net income alone for a baseline-only household (HouseholdNetIncomeResult), including one whose reform is the baseline;HouseholdBreakdowns);HouseholdSubPage), the policy summary (PolicySubPage) and Reproduce in Python (HouseholdReproducibility), each Python block given its own policy by id and pinned to the .py bundle its own calculation reported;copyHouseholdToYear; a household or saved household with inputs in several years opens in its own year, with the reason;EarningsVariationSubPage's newalsoOfferInputs, passed only by this report; the SALT cap, the issue's other example, is a calculated variable the list already offers);getHouseholdReproducibilityCode's newincludeDataFrameoption andHouseholdReproducibility'sofferDataFrame, passed only by this report). Blocks generated for a UK household under a personal allowance reform (run against policyengine-uk 2.130.1) and for a US household under current law (policyengine-us 2.2.1) ran locally: the point block prints a four-row table and the earnings variation block a 401-row one;It calculates through the Public API's v1 household routes, as the report section does; "Try again" never sends a household that cannot run.
Shared with the live report section and the saved reforms page, behaving as before (their existing tests pass unchanged):
HouseholdBuilderPanel: the builder configuration the section used, now used by both;useRequestedHouseholdRun(the run request: asking again for the same household, retrying only a household that can run),householdRunParts(Calculate, progress and failure) andhouseholdResultParts(money formatting, card, headline and output tabs; the tabs' hover color now applies);newV3Household, the section's new household, moved tolibs/flagship/householdReport;DeleteConfirmationDialog, now rendered byDeleteReformDialogand the saved households list.The existing household report output and the report section offer the same earnings variables and Python code as before: the new options default off.
Unreachable until v3 moves to the v2 routes
No runtime flag or environment variable. Everything that works only on the v2 routes is gathered in
app/src/pages/flagship/householdEntryPoints.ts, which nothing routed imports.householdEntryPoints.test.tsxchecks that Build's "A household" option cannot be chosen, and that no file in the app or the calculator imports the household-only modules, by an@/specifier or a relative path, except those modules themselves (confirmed to fail, namingReport.page.tsx, when that page imported one by a relative path). A report whose simulations are households still shows "This report cannot open here" (ReportLoadStates.test.tsx).What the change that moves v3 to the v2 routes does to reach them:
components/flagship/reform/PopulationPicker.tsx): choosing it offersSavedHouseholdPickeror a new household, and Build's run opensHouseholdReportPagewith that household, the draft reform, and the run bar's year and baseline.HouseholdReportPageinReport.page.tsx'shouseholdReportbranch for household reports saved through/v2/reports.calculator-app/src/app/[countryId]/(workspace)/households/page.tsxrenderingSavedHouseholdsPage), a "households" screen inworkspaceRoutes.tsandlibs/flagship/shellRoutes.ts, and a link in the workspace navigation.Before then, the report's calculations move to
/v2/household-calculations, and saving changes to a saved household needs the Public API'sPATCH /v2/user-households/{id}to accepthousehold_id(today its update model accepts onlylegacy_household_id,nameanddescription, so that request is refused and the form says the changes were not saved).Tests
New or changed, all passing (188 tests in these files):
v2DocumentCodec.test.ts(15): US and UK documents match the Public API's translation (checked againsttranslate_legacy_householdfor three households: same canonical content hash and default year); no label is sent; a person outside a collection fails, naming it; read-back keeps the stored id anddefault_year(including inputs in two years, values without a year, and no default year).savedHouseholdStore.test.ts(19): save, list (order, unavailable rows), open, rename, delete; save changes (one PATCH keeping the id, identical content, either step failing); each stored household fetched once, a failed fetch asked again.useSavedHouseholds.test.tsx(8): the hooks against routed Public API responses, including no accounts, a country change for the same user, and one stored-household cache across hooks.SaveHouseholdForm.test.tsx(11),SavedHouseholdPicker.test.tsx(9),SavedHouseholds.page.test.tsx(8),DeleteConfirmationDialog.test.tsx(4),HouseholdBuilderPanel.test.tsx(5),HouseholdNetIncomeResult.test.tsx(4).HouseholdReport.page.test.tsx(28): a reform's change and a baseline-only household's net income; baseline-only breakdowns; the inputs, policy and Reproduce in Python sections, each block with its own policy and bundle; trying again (including after an edit that cannot run); Calculate disabled for the household shown; years (Add user-selectable year to standalone household creation #498); the result describing the household calculated; tip income in varying earnings (App does not display all possible variables in "Varying your earnings" #1096); national poverty thresholds for a new US household; a UK household in pounds; saved households (save, save changes, a failed save, opening from the picker or on entry, another year, a save finishing after another household is opened, no picker during a calculation, no accounts).householdEntryPoints.test.tsx(5),reproducibilityCode.test.ts(+3),HouseholdReproducibility.test.tsx(+2),EarningsVariationInputs.test.tsx(2),HouseholdBreakdownsInputs.test.tsx(2),householdReport.test.ts(1).Each commit's tests were run failing first, then passing; refactor and style commits say so where there was no behavior to fail first. Full app suite on this branch: 5,660 passed, 10 skipped (Node 26).
tsc(app, website and calculator), ESLint, Stylelint and Prettier are clean. The PR checks (lint and format, type checking, tests, build, embedded site URLs, visual regression) pass on this PR.Three review rounds were run on this branch: the first found two bugs (a retry could send a household that cannot run; "Saved" stayed after an edit) and the second one (a save finishing after another saved household was opened pointed the form at the wrong saved household); each was fixed with a failing test first, with the material quality findings. The third found none.
Environment variables
None added or changed, and no route reaches the new code. When reached, it uses the app's existing clients:
VITE_API_URL(set asNEXT_PUBLIC_API_URLorVITE_API_URL) for the Public API's address, and the existing sign-in configuration (NEXT_PUBLIC_AUTH_ENABLED,NEXT_PUBLIC_AUTH0_DOMAIN,NEXT_PUBLIC_AUTH0_CLIENT_ID,NEXT_PUBLIC_AUTH0_AUDIENCE, or theirVITE_names), all from the Vercel build environment and mapped incalculator-app/next.config.ts. Their Vercel values were not re-checked here.Before merging
household_idonPATCH /v2/user-households; that change is needed before v3 moves to the v2 routes.Overlaps
utils/reproducibilityCode.tsand dropsspmConfigfromHouseholdReproduction. Whichever merges second adapts: here the options object and the DataFrame code are independent of the SPM settings, and the household report passes each block'sspmConfig, which would then be left out./v2/policiesalso add v2 client code here; the household wire types are in their owntypes/v2Households.tsand use the existingV2UserClientandAccountAssociationRoutes.🤖 Generated with Claude Code