Skip to content

v3 household section: UK suggestions, US SPM geography and calculation provenance - #1386

Draft
anth-volk wants to merge 8 commits into
mainfrom
v2plan/1e-112-v3-uk-us-households
Draft

anth-volk wants to merge 8 commits into
mainfrom
v2plan/1e-112-v3-uk-us-households

Conversation

@anth-volk

Copy link
Copy Markdown
Collaborator

Fixes #1385

What changes

The household section of v3's report (the "Household" tab: the reader describes households, and each runs under the report's baseline and reform) already runs UK households in the UK. This PR finishes US and UK support. Calculations stay on the Public API's v1 household routes: create the household (POST /{country}/household), then calculate it under each policy (GET /{country}/household/{id}/policy/{policy_id}).

  • The country is required, never read as the US. compareHouseholds used the US result rows (the taxes and benefits it lists) for any country without its own, and HouseholdImpactResult defaulted its currency to dollars; both now require the country, and a country without result rows is an error. loadParameterDependencies, suggestedHouseholdInputs and describesAHousehold also require it. The unused simple-input run (FlagshipHouseholdInput, buildHouseholdPayload, runFlagshipHousehold, describeHouseholdInput), which always built a US household, is removed.
  • UK summary rows (Income Tax, National Insurance, Council Tax, Benefits and the UK programmes) were already defined; they are now reached only for a UK household.
  • UK suggestions. app/scripts/build-parameter-dependency-map.py takes --country uk and traces policyengine-uk 2.130.1 with policyengine-core 3.33.0, the versions the Public API calculates with, over 400 generated households (the UK microdata is not public; which parameters a formula reads depends on which formulas run, so the population varies ages, couples, children, pensioners, disability, every region, tenure and council tax band, and each income source). All 1,163 variables calculate; the map has 688 parameter paths and 989 consumed variables (195 KB), loaded only by a UK report's household section. Each country's map loads once, every caller names its country (the validation views ask for the US map by name), and only inputs that describe a household are suggested. For a change to Universal Credit's child element, the suggestions include a child's age and childcare costs.
  • Calculation provenance kept. The run now calls fetchHouseholdCalculationWithBundle and keeps each policy's policyengine_bundle, spm_config and spm_provenance (FlagshipHouseholdRun.provenance). A response without a bundle records null; one calculation for both policies gives the reform the baseline's provenance.
  • US SPM geography and its footnote. The section shows the builder's Supplemental Poverty Measure geography choice: a US household starts on national thresholds, and the reader can choose a county's, with its FIPS code; both are sent with the household. The calculation's SPM settings and receipt show under the summary and the net income breakdown (once when one calculation served both policies; nothing for a response without a receipt, such as any UK household), and the varying earnings and marginal tax rate views show their own. The options that hid the choice and the variation footnote (hideSpmGeography, hideSpmFootnote) are removed, as nothing passes them now.
  • UK acceptance checks from Bug in UK household calculation [MOVE TO API] #358 and UAT: UK Household (PR #556) #562 (tests only; see below).

Live as soon as it merges.

Evidence

  • Bug in UK household calculation [MOVE TO API] #358: the issue's household (an adult earning £100, a dependant aged 10 earning £10, one benefit unit, a BRMA, region and local authority that name different places) calculates locally in policyengine-uk 2.130.1: net income £10,159.73 for 2025 and £10,657.91 for 2026. No calculation was sent to production.
  • The UK map was generated twice from the script, with the same parameter readers and consumers both times.

Tests

New or changed, all passing:

  • householdRun.test.ts: a country without result rows throws; each response's bundle and SPM provenance are kept per policy; one calculation for both policies gives the reform the baseline's; a response without a bundle records null; the run's mechanics on runFlagshipHouseholdPayload.
  • HouseholdImpactResult.test.tsx (new): props without a country do not type-check; a UK result reads in pounds; the point methodology shows under the summary and net income, not under the variation views.
  • HouseholdImpactSection.test.tsx: the builder's stand-in renders the real SPMGeographyChoice. A US household shows it on national thresholds; county thresholds and a FIPS code are sent; a response with an SPM receipt shows the methodology, once when baseline and reform are the same policy; one without shows none; a UK household shows no SPM choice.
  • HouseholdImpactSuggestions.test.tsx (new), parameterDependencies.test.ts, householdSuggestions.test.ts, householdVariableGroups.test.ts: each country's own map; UK suggestions for Universal Credit's child element, never an input only the microdata sets; calls without a country do not type-check, and a call of the map loader without one rejects.
  • UKHouseholdAcceptance.test.tsx (new): Bug in UK household calculation [MOVE TO API] #358's household is sent as described and its result shows; UAT: UK Household (PR #556) #562's couple and child share one benefit unit and one household; an input set on the benefit unit is sent there; the household runs under the report's two policies and reads in pounds. These check existing behavior, so they could not fail first: each was confirmed to fail when the section sent a new household instead of the built one, or called the US routes for a UK household.
  • HouseholdBuilderForm.test.tsx: the SPM choice cannot be hidden.
  • ReportViewHousehold.test.tsx and HouseholdImpactAnalytics.test.tsx: their mocks follow the new calculation call.

Each commit's tests were run failing first, then passing. Full app suite: 5,534 passed, 10 skipped. tsc (app and calculator), ESLint, Stylelint and Prettier are clean.

Environment variables

None added or changed. The household calls use BASE_URL as before: NEXT_PUBLIC_API_URL (calculator, mapped to VITE_API_URL) or VITE_API_URL, from the Vercel build environment, falling back to https://api.policyengine.org. This PR does not read it anywhere new; its Vercel value was not re-checked here.

Before merging

Nothing this PR depends on is pending: it uses only routes the Public API serves today. It goes live when it merges.

Overlaps

🤖 Generated with Claude Code

@vercel

vercel Bot commented Oct 21, 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 Oct 29, 2026 8:44pm UTC
policyengine-website Ready Ready Preview Oct 29, 2026 8:44pm UTC

Request Review

anth-volk and others added 8 commits October 30, 2026 00:38
The household section already runs each household in the report's
country, but the result code still fell back to the US:

- compareHouseholds used the US result rows (its taxes and benefits)
  for any country without its own, and defaulted its country to "us".
  The country is now required, and a country without its own result
  rows is an error instead of a US reading.
- HouseholdImpactResult defaulted its currency country to "us"; the
  section always passes it, and it is now required.
- The unused simple-input run (FlagshipHouseholdInput,
  buildHouseholdPayload, runFlagshipHousehold, describeHouseholdInput
  and their helpers) built a US household with a US state whatever the
  report's country. Nothing calls it since the section moved to the
  household builder, so it is removed, and its run tests now cover
  runFlagshipHouseholdPayload.

Tests first: compareHouseholds for a country without result rows
expected an error and got a US reading; a HouseholdImpactResult props
object without a country carries @ts-expect-error, which tsc reported
as unused until the country became required.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The section's run read only the result of each household calculation on
the Public API's v1 route (fetchHouseholdCalculation), so the response's
policyengine_bundle, spm_config and spm_provenance were dropped. It now
calls fetchHouseholdCalculationWithBundle and keeps them per policy on
the run (FlagshipHouseholdRun.provenance), for the SPM methodology
footnote and for Reproduce in Python. A run that calculates once for
both policies gives the reform the baseline's provenance; a response
without a bundle records null rather than a guessed version.

Tests first: the section's and the run's tests mock only the call that
keeps provenance, and three new run tests expect it per policy; all 29
failed while the run still called fetchHouseholdCalculation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The section hid the household builder's Supplemental Poverty Measure
geography choice (hideSpmGeography) and ran every US household on
national thresholds. It now shows the choice: a US household still
starts on national thresholds, and the reader can pick local thresholds
by county, with a county FIPS code. The household's spm setting and
county_fips are sent with it, as in the household builder.

With a real choice, the methodology footnote says something again:

- Under the summary and the net income breakdown, the result shows the
  calculation's SPM settings and receipt (SPMMethodologyFootnote), read
  from each policy's response. One calculation for both policies shows
  once. A response without a receipt (any UK household) shows nothing.
- The earnings variation and marginal tax rate views no longer hide
  their own variation footnote.

SPMMethodologyFootnote now also takes just a calculation's SPM settings
and receipt, so the v3 run's provenance can be shown without a full
household output.

Tests first: the builder's stand-in renders the real SPMGeographyChoice
unless hidden. The choice on national thresholds, a county choice sent
with its FIPS code, the methodology under a result, and both variation
views keeping their footnote failed before the change (5 tests).

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

The household section leads its variable search with the inputs a
reform's parameters read, from a dependency map traced from the model.
Only policyengine-us had a map, so UK reports suggested nothing.

- build-parameter-dependency-map.py takes --country. The UK map traces
  policyengine-uk 2.130.1 with policyengine-core 3.33.0 (the versions
  the Public API calculates with) over 400 generated households: the UK
  microdata is not public, and which parameters a formula reads does not
  depend on the households, only on which formulas run, so the
  population varies ages, couples, children, pensioners, disability,
  every region, tenure and council tax band, and each income source.
  All 1,163 variables calculate; the map has 688 parameter paths and 989
  consumed variables (195 KB), loaded only by UK reports' household
  section.
- loadParameterDependencies(countryId) loads each country's map once,
  and every caller names its country: the validation surfaces (scorecard
  matches, calibration matches, reform readers and scorecard
  comparisons) ask for the US map by name, and a call without a country
  loads none. A name every object inherits, such as "constructor", is
  not a country with a map.
- The section loads the report's country's map, and suggests only inputs
  a reader describes a household with, so inputs only the microdata sets
  (such as a public-spending imputation) no longer use up the list.

Tests first: the UK map's package and readers, UK suggestions in the
library and in the section, a call without a country rejecting (with
@ts-expect-error, unused until the country became required), and the
inherited-name case all failed before the change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Tests only: the section already sends UK households as built and runs
them in the UK, so these check that behavior against the two issues
rather than drive a change. They pass on the code as it is; each was
confirmed to fail under a temporary change: the section sending a new
household instead of the built one failed three, and the run calling
the US routes for a UK household failed the fourth.

- #358: the issue's household (an adult earning £100, a dependant aged
  10 earning £10, one benefit unit, a BRMA, region and local authority
  naming different places) is sent as described and its result shows.
  The Public API once failed on it; run locally, policyengine-uk 2.130.1
  (the version the Public API pins) calculates it for 2025 (net income
  £10,159.73) and 2026 (£10,657.91).
- #562: a couple and a child share one benefit unit and one household;
  an input set on the benefit unit is sent on the benefit unit; the
  household runs under the report's two policies and reads in pounds.
  Saving and reusing a household, #562's second item, belongs to the
  standalone household report.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
HouseholdBuilderForm's hideSpmGeography and the earnings variation and
marginal tax rate pages' hideSpmFootnote existed only so v3 could hide
them; since v3 shows both, nothing passes them. They are removed, so
every builder shows the SPM geography choice (for a US household) and
every variation chart its methodology footnote.

Tests first: the builder test that hid the SPM choice now passes the
option under @ts-expect-error and expects the choice to show; it
failed (the choice was hidden, and tsc reported the directive as
unused). The breakdowns test that checked v3 did not pass
hideSpmFootnote is removed with the option.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ho reads the bundle

Tests for two rules of the SPM methodology footnote that had none:

- When the report's baseline and reform are the same policy, the
  section calculates once and shows that calculation's methodology
  once, with no reform entry.
- HouseholdImpactResult shows the point calculation's methodology under
  the summary and the net income breakdown, and not under varying
  earnings or marginal tax rates, which show their own.

The comment on FlagshipHouseholdRun.provenance said Reproduce in Python
reads it; v3 does not offer that yet, so it now says the .py bundle is
kept for it.

The tests check behavior the code already has, so they could not fail
first. Each was confirmed to fail under a temporary change: passing the
reform's provenance when one calculation served both policies failed
the first; showing the footnote under every view failed the two
variation cases; showing it under the summary alone failed the net
income case.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
suggestedHouseholdInputs and describesAHousehold still defaulted their
country to "us", the fallback this PR removes elsewhere. Both now take
the household's country as a required argument; suggestedHouseholdInputs
takes its limit and the inputs it lists first as named options instead
of positional ones, so a caller never passes undefined to reach the
country. The section, their only caller, already passed it.

Tests first: a call of each without a country, under @ts-expect-error,
failed the type check while the default existed; the existing tests
now name the country, and four of them failed at runtime against the
old argument order.

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

This branch was successfully deployed

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

v3 household section: finish US and UK households (UK suggestions, US SPM geography, calculation provenance)

1 participant