Skip to content

v3 households: save each calculated household to its user, shown nowhere - #1505

Merged
anth-volk merged 5 commits into
mainfrom
v3-save-calculated-households
Nov 2, 2026
Merged

anth-volk merged 5 commits into
mainfrom
v3-save-calculated-households

Conversation

@anth-volk

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

Copy link
Copy Markdown
Collaborator

What changes

v3's Household tab (on a report or bill page) calculates households through the Public API's v1 household routes and keeps each household only in the page's state. This PR saves each household it calculates to the user, in the background, as a /v2/user-households row that points at the v1 household (legacy_household_id), as v3 already saves its reforms and reports. Nothing in v3 reads or shows these rows yet: they keep each user's households for the Public API's v2 household routes.

  • Saved only where saved work goes to the account (useV3SavedWork): signed in, or signed out as the browser's unauthenticated user, in the US or UK, and only in the account's country. With sign-in off, nothing is saved.

  • One row per v1 household and user. ApiFlagshipHouseholdStore.saveCalculated looks up the user's row for the v1 household (GET /v2/user-households filtered by legacy_household_id) and creates one only when there is none. The row is named with the household's summary ("Married · 2 children · California"), shortened to the API's 255 characters.

  • Once per page. useSaveCalculatedHousehold saves through one query per country and household, kept for the page's life, so a saved household is not looked up again. The key leaves out the session's user: otherwise a signed-in session that ends without a reload would save the household on screen again as the browser's user, and the next sign-in would merge that row into the account beside the first. A failed save changes nothing on the page: it is logged to the console, and tried again the next time the household's result shows, or when the session's user changes while it shows.

  • Sign-in notices. The household save creates the browser's unauthenticated user on the Public API, so a browser that saved only households now has work to move at sign-in. Both notices now say "The work you saved on this browser…" instead of naming reforms and reports.

  • Corrected comment. runFlagshipHousehold's comment said the API dedupes identical households; it now says the Public API stores a new one on every call.

Not changed: the Account page's delete dialog and signed-out card still name reforms and reports.

Known limits:

  • The Public API's POST /{country}/household stores a new v1 household on every call, so the same inputs calculated again (after a reload, or once the run's five-minute cache entry has gone) get a new v1 ID and a second row. Those rows point at the same v2 household, because the Public API's copies to v2 deduplicate identical content, so a view of saved households can group them by household_id.
  • The Public API has no uniqueness constraint on a user's row for a household. Two tabs saving the same household at the same moment can create two rows, and a household saved both by the browser's unauthenticated user and by the account becomes two rows when sign-in merges them.

Live as soon as it merges.

Tests

New or changed, all passing, each run by name:

  • flagshipHouseholdStore.test.ts (new): a household the user hasn't saved gets one row, after a lookup by legacy_household_id; an existing row means no second one; a long summary is shortened; an ID that isn't a v1 household ID sends nothing.
  • useFlagshipHousehold.test.tsx (new): the same household twice on a page is looked up and saved once, and isn't looked up again after six minutes; two different households are each saved; signed out, it's saved as the browser's unauthenticated user; with sign-in off, in another country, or with no calculated household, nothing is sent; a session that ends on the page saves nothing again; a failed save is logged to the console, stays in its own query, and is tried again when the result shows again.
  • HouseholdImpactSection.test.tsx: a calculated household is passed on to be saved, with its v1 ID and summary.
  • accountUpgrade.test.ts (new): the sign-in notices name no reforms or reports.

Each failed before its code, except three that cover behaviour already in place: the six-minute test (it fails without the session-long cache entry), the two-household test (it fails without the household's ID in the query key) and the retry test.

🤖 Generated with Claude Code

anth-volk and others added 4 commits October 30, 2026 22:20
After the Household tab calculates a household, v3 saves it to the
session's user as a /v2/user-households row pointing at the v1
household (legacy_household_id), as it already saves reforms and
reports through the Public API. Nothing in v3 reads these rows yet;
they keep each user's households for the Public API's v2 household
routes.

- ApiFlagshipHouseholdStore.saveCalculated looks for the user's row
  for the household first and creates one only when there is none,
  named with the household's summary (shortened to the API's limit).
- useSaveCalculatedHousehold saves in the background, once per
  household and session (one query key per household), and only where
  saved work goes to the account: signed in, or signed out as the
  browser's unauthenticated user, in the account's country. A failed
  save changes nothing on the page.

Tests first: the store's and the hook's tests failed to import the
missing code, and the section's test failed because no calculated
household was passed on.

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

- A browser signed out that saved only households (a report someone
  else ran, then a household calculated on it) was told at sign-in
  that its reforms and reports had moved to the account: the household
  save creates the browser's unauthenticated user, so the upgrade no
  longer answers "none". Both sign-in notices now speak of the work
  saved on this browser.
- The save's cache entry is kept for the session (gcTime), so the same
  household is not looked up again after five minutes.
- The hook takes the calculated household as one value (its v1 ID and
  summary), and the report's CountryId.
- Tests: the signed-out save as the browser's unauthenticated user, one
  lookup as well as one save for the same household twice, the failed
  save held by its own query, and the household summary in the shared
  fixtures.

Tests first: the notice test failed on both messages naming reforms
and reports.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A signed-in session whose token could not be renewed ends without a
reload, and saved work moves to the browser's unauthenticated user
(a new session scope). The save's query key held that scope, so the
household on screen was looked up and saved again as the browser's
user, and the next sign-in merged that row into the account beside
the one already there. The key now holds only the country and the v1
household, so a household saved on the page is not saved again.

- The hook's comment says what it does: once saved, not looked up or
  saved again on the page; a failed save is tried again the next time
  the household's result shows.
- A test that shows the same household again after six minutes, which
  fails without the session-long cache entry.
- The fixture's summary is written as the Household tab writes it.

Tests first: the session-end test failed with four requests instead
of the signed-in lookup and save.

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

- V3SavedWork.scope said a session that ends without a reload moves
  every query to new keys; the calculated household's save is not
  keyed by it, so the comment now says so.
- A test that a failed save is tried again when the household's
  result shows again, as the hook's comment says.

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

vercel Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
policyengine-calculator-next Ready Ready Preview Nov 2, 2026 2:08pm UTC
policyengine-website Ready Ready Preview Nov 2, 2026 2:08pm UTC

Request Review

@PavelMakarchuk

Copy link
Copy Markdown
Contributor

Heads-up from Pavel's side: #1511 reworks the v3 Household tab's layout (a row of household tiles, Results/Inputs per household, Compare all, full-width charts). Saving stays here; #1511 saves nothing.

The two overlap in HouseholdImpactSection.tsx only at this PR's useSaveCalculatedHousehold call. #1511's new HouseholdCard keeps result and requested (with requested.summary), so the call goes back in unchanged after const result = …, whichever PR merges second. Two small differences:

#1392 also overlaps (HouseholdImpactResult.tsx); the notes for that are in #1511's description under "Overlap and scope split".

🤖 Generated with Claude Code

@PavelMakarchuk PavelMakarchuk left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: no blocking issues. The save lookup filters on legacy_household_id server-side, the hook sits before any early return, and a shared fetchQuery keeps a doubled effect (strict mode, or the same household twice) to one request. Three small points:

  1. A failed save leaves no trace (inline below).
  2. Stale comment, not in this diff: householdRun.ts above runFlagshipHousehold says "the API dedupes identical ones". It does not: two identical POST /us/household calls today returned 58663 and 58664. Since this PR now saves a row per v1 household, worth correcting here so nobody expects one row per household after a reload.
  3. Test gap: every hook test uses one CALCULATED_HOUSEHOLD. A test with two household ids expecting two POSTs would catch someone dropping householdId from the query key.

Product note, not a defect: a signed-out visitor who only calculated a household now sees "The work you saved on this browser is now in your account" at sign-in, though they saved nothing visible. The description says this is deliberate; flagging it for Pavel's call.

🤖 Generated with Claude Code

Comment thread app/src/hooks/useFlagshipHousehold.ts Outdated
gcTime: Infinity,
retry: false,
})
.catch(() => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Every failure is dropped silently. If the API refuses all saves (a 422 on the body, a 403 for the browser's user), the rows never appear and nobody can tell. A console.error('[useSaveCalculatedHousehold] Save failed:', error) here changes nothing on the page, like runAccountUpgrade does.

🤖 Generated with Claude Code

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 81a90cd: a failed save is now logged with console.error('[useSaveCalculatedHousehold] Save failed:', error), as runAccountUpgrade does, and the page stays as it was. A new test checks the log.

…ehold anew

From review:
- A failed save was dropped silently, so a Public API that refused
  every save (a 422 on the body, a 403 for the browser's user) left
  no trace. It is now logged with console.error, as the sign-in
  upgrade logs its failure; the page is unchanged.
- runFlagshipHousehold's comment said the API dedupes identical
  households. It does not: the Public API's POST /{country}/household
  stores a new one on every call, so each run gets its own household
  ID (and, with this PR, its own saved row).
- A test with two different households expecting two saves, which
  fails if the household's ID is dropped from the save's query key.
- The hook's comment also says a failed save is tried again when the
  session's user changes while the result shows.

Tests first: the logging test failed, as nothing was logged.

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

Copy link
Copy Markdown
Collaborator Author

Thanks. Addressed in 81a90cd:

  1. A failed save is logged to the console (see the inline reply).
  2. The comment above runFlagshipHousehold now says the Public API stores a new household on every call, even for identical inputs, so each run gets its own household ID.
  3. A hook test with two household IDs expects two saves. It fails if householdId is dropped from the save's query key.

Not changed here: the sign-in notice's wording, and the saved row's name (still the household's summary). On #1511: whichever PR merges second keeps the useSaveCalculatedHousehold call after const result = ….

@anth-volk
anth-volk merged commit 4d3f821 into main Nov 2, 2026
10 checks passed
@anth-volk
anth-volk deleted the v3-save-calculated-households branch November 2, 2026 14:31

This branch was successfully deployed

2 active deployments
Preview – policyengine-website — 81a90cd8 Deployed Nov 2, 2026 by vercel[bot]
Preview – policyengine-calculator-next — 81a90cd8 Deployed Nov 2, 2026 by vercel[bot]
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.

2 participants