Skip to content

Add household_options.time_period to the country metadata - #4159

Merged
anth-volk merged 1 commit into
masterfrom
feat/household-time-period
Nov 3, 2026
Merged

anth-volk merged 1 commit into
masterfrom
feat/household-time-period

Conversation

@anth-volk

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

Copy link
Copy Markdown
Collaborator

Fixes #4160

Summary

Adds a separate household year list, household_options.time_period, to the /{country}/metadata response. Its items have the same shape as economy_options.time_period items: {"name": <int year>, "label": "<year>"}.

Today the app reads economy_options.time_period for every year menu: household reports, society-wide reports and the policy editors. This is the first step towards offering US household calculations for 2022 and 2023 while no longer offering society-wide US runs for those years, whose data is nearly empty. A later PR will narrow the US economy_options.time_period to start at 2024. This PR leaves economy_options unchanged (build_microsimulation_options is identical to master).

Where each country's household years come from

  • US (policyengine_api/household_years.py, us_household_years): the years covered by the bundle's certified SPM forecast (policyengine_api/spm.py, new certified_spm_forecast_years, which reads forecast.years from the forecast the bundle pins by forecast_content_sha256), keeping only the years up to the last year written in the CPI-U parameter's source file (CPI_U_PARAM_PATH, gov.bls.cpi.cpi_u, read with policyengine-us's get_parameter_last_year). The US model answers an SPM calculation in any year outside the forecast with SPM_YEAR_UNAVAILABLE. The list is newest first, like the US society-wide years.
    • If the bundle has no certified SPM forecast, or the forecast cannot be loaded (the cases in which spm metadata already reports available: false), the US household years fall back to the US society-wide years, and a warning is logged. Startup does not fail in that case, as it does not today.
  • UK: the rule for the UK society-wide years, uk_economy_years in policyengine_api/economy_years.py, oldest first like economy_options.time_period.
  • Any other country: a copy of its economy_options.time_period (an empty list if it has none). The Public API serves only us and uk today (SupportedCountryId), so this applies to no served country.

Values with the locked dependencies (policyengine[models]==6.12.0)

  • US: 2035, 2034, …, 2022 (14 years, newest first). The forecast covers 2022–2035, and the CPI-U file ends in 2035.
  • UK: 2024, 2025, …, 2032 (9 years, oldest first), identical to the UK economy_options.time_period.

Startup time

No dataset is loaded and no simulation is run. The SPM forecast is the one spm_metadata already loads at import, and it is cached, so it is still read once per process (_selected_forecast.cache_info() after import: 1 miss, 2 hits). Building the US list takes about 28 ms per country model load, mostly reading the CPI-U YAML file; the UK list takes under 1 ms.

Not changed

  • economy_options for every country.
  • The v2 metadata routes and catalog.
  • The migration contract registry (tests/contract/registry.py) and generated migration documents, which list the /{country}/metadata fields that the v2 migration checks. They don't list household_options.

Testing

Only the new tests were run, each selected by node ID; all 14 pass:

  • tests/unit/test_spm_settings.py::test_certified_forecast_years_are_the_artifacts_years
  • tests/unit/test_spm_settings.py::test_certified_forecast_years_are_none_without_a_certified_forecast ([False], [True])
  • tests/unit/test_spm_settings.py::test_certified_forecast_years_are_none_when_the_forecast_cannot_load
  • tests/unit/test_household_options.py::test_us_household_years_stop_at_the_last_cpi_u_year_newest_first
  • tests/unit/test_household_options.py::test_us_household_years_are_the_society_wide_years_without_a_forecast
  • tests/unit/test_household_options.py::test_a_country_without_household_rules_offers_its_society_wide_years
  • tests/unit/test_household_options.py::test_us_household_years_are_the_certified_forecast_years_to_cpi_u
  • tests/unit/test_household_options.py::test_us_household_years_include_2022_and_2023
  • tests/unit/test_household_options.py::test_uk_household_years_follow_the_uk_society_wide_rule
  • tests/unit/test_household_options.py::test_household_years_have_the_shape_of_society_wide_years ([uk], [us])
  • tests/unit/test_household_options.py::test_us_society_wide_years_are_unchanged
  • tests/unit/test_household_options.py::test_uk_society_wide_years_are_unchanged

Also run: ruff format and ruff check on the changed files (no changes, no findings), python scripts/run_quality_guards.py (all guards pass; spm.py is now 790 lines, under the 800-line limit), and BASE_SHA=origin/master bash .github/scripts/check_changelog_fragment.sh (passes). The values above were read by calling the Flask /{country}/metadata route with a test client.

Changelog fragment: changelog.d/household-time-period.added.md.

🤖 Generated with Claude Code

The app reads one year list, economy_options.time_period, for household
and society-wide year menus alike. Add a separate household list so the
two can differ later. economy_options is unchanged.

- US: the years the bundle's certified SPM forecast covers, up to the
  last year in the CPI-U parameter's source file, newest first like the
  US society-wide years. The forecast is the one the SPM metadata already
  loads, so the Public API loads no extra dataset at startup. Without a
  certified forecast the US household years are the society-wide years.
- UK: the rule for the UK society-wide years (uk_economy_years).
- Any other country: its society-wide years.

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

codecov Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 81.81818% with 6 lines in your changes missing coverage. Please review.
✅ Project coverage is 88.21%. Comparing base (eb96f3a) to head (2bc086d).
⚠️ Report is 50 commits behind head on master.

Files with missing lines Patch % Lines
policyengine_api/household_years.py 55.55% 2 Missing and 2 partials ⚠️
policyengine_api/country.py 86.66% 0 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #4159      +/-   ##
==========================================
- Coverage   88.22%   88.21%   -0.01%     
==========================================
  Files         285      286       +1     
  Lines       16847    16893      +46     
  Branches     2741     2754      +13     
==========================================
+ Hits        14863    14903      +40     
- Misses       1302     1304       +2     
- Partials      682      686       +4     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@anth-volk
anth-volk marked this pull request as ready for review November 3, 2026 20:37
@anth-volk
anth-volk merged commit a69c6e2 into master Nov 3, 2026
22 checks passed
@anth-volk
anth-volk deleted the feat/household-time-period branch November 3, 2026 20:38
anth-volk added a commit to PolicyEngine/policyengine-app-v2 that referenced this pull request Nov 3, 2026
Household readers no longer use economy_options.time_period when the
metadata has no household list. This PR merges only after the Public API
change that adds household_options (PolicyEngine/policyengine-api#4159) is
in production.

- household_options is required in the metadata response type, and
  householdOptions in the metadata state. The reducer starts it, and resets
  it, as { time_period: [] }, the way it does economyOptions.
- getHouseholdYearOptions and getHouseholdDateRange read
  householdOptions.time_period only. With an empty list, the date range is
  still 2022 to 2035, as before the metadata loads.
- The tests of the removed fallback are gone. Every mocked metadata response
  and state now includes household_options, with the same years as its
  economy_options unless the test is about household years.
- The PolicyCreationModal test replaces the mocked state object instead of
  changing it in place, since the memoized selectors keep their result for
  as long as they get the same state object.

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

This branch was previously deployed

1 inactive deployment
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.

Offer household years separately from society-wide years in the country metadata

1 participant