Repository navigation
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
getDateRange falls back to a fixed range until API metadata arrives. Move its last day to 2036-12-31 for the US 2036 extension (PolicyEngine/policyengine-api#3926). Everything else reads the years from economy_options.time_period. mockStateWithMetadata now types its overrides as the payload result, which is where it spreads them. The old Partial<MetadataApiPayload> type rejected economy_options under tsc. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
726ad7c to
7e9e173
Compare
PavelMakarchuk
left a comment
There was a problem hiding this comment.
Code Review: PR #1266 "Fall back to a 2022–2036 date range before metadata loads" (@DTrim99, round 1)
ID legend: C = critical (must fix), SA = should address, G = suggestion.
Source Documents
- Reviewed head SHA: 7e9e173
- Mode: full
- Read: the diff (3 files, +51/-2); the four callers of
getDateRange(app/src/components/flagship/reform/DraftTable.tsx:174,app/src/components/flagship/reform/ProvisionDetails.tsx:103,app/src/pages/reportBuilder/modals/PolicyCreationModal.tsx:101,app/src/pathways/report/components/PolicyParameterSelectorValueSetter.tsx:35) and the gates that decide when they render;app/src/reducers/metadataReducer.ts; the repo rules indocs/engineering/skills/testing.mdanddocs/engineering/skills/repository-guidance.md; PolicyEngine/policyengine-api#3926 and #3928; spm-calculator#51; PyPIspm-calculator. - API metadata ranges (live
/us/metadataand/uk/metadata, 2026-10-27): UStime_period2022–2035 (model 2.2.1); UK 2024–2030 (model 2.132.6). - Runs: the changed test file,
app/src/tests/unit/libs/, the wholeapp/suite, 8 mutants ofgetDateRange, Prettier, ESLint andtsc --noEmit, at the head and with the fixes below applied. Local toolchain: Node 22.23.1, bun 1.3.9 (CI uses Node 26; these tests do not format numbers).
Branch Status
The branch is 1 commit ahead of main and 4 commits behind. None of the 4 commits touch the three changed files, the metadata reducer or the four callers, and git merge-tree is clean. GitHub reports the PR as mergeable. A rebase is not necessary for this review.
What the PR gets right
- The fallback lives in one place (
app/src/libs/metadataUtils.ts:38), and the change is one line.'2036-12-31'is inclusive and matches the metadata branch (${last}-12-31), so there is no off-by-one. - The fixture type fix is correct.
mockStateWithMetadataspreads overrides intoresult, the old type gives TS2353, and all 11 earlier callers pass no overrides. - The tests follow the repo rules: fixtures in
src/tests/fixtures/, grouped constants, Given-When-Then names. The unsorted[2036, 2035, 2022]input also exercises the sort. - No changelog is needed. The repo removed its changelog file in
9e0c5976.
Critical (Must Fix)
None.
Should Address
SA1. The "metadata through 2036" test cannot tell metadata from the fallback
- Where:
app/src/tests/fixtures/libs/metadataUtilsMocks.ts:35-38,app/src/tests/unit/libs/metadataUtils.test.ts:281-290. - Evidence:
EXPECTED_DATE_RANGES.THROUGH_2036andEXPECTED_DATE_RANGES.FALLBACKare the same value (2022-01-01to2036-12-31). A mutant that always returns the fallback passes all 40 tests in the changed file. In the whole suite, only an unrelated component test catches it (app/src/tests/unit/components/flagship/reform/DraftTable.test.tsx:170). A second mutant that fixes the metadata lower bound at2022-01-01passes the whole suite (5901/5901), because no existing test checks the lower bound of a range that starts after 2022. - Why this severity: the new behavior is "2036 only until metadata loads". The tests pin the "2036" half but not the "only until" half, and the PR body uses that half to argue that an early merge is safe. This is a missing test for new behavior. It is not critical: the code is correct, and the whole suite already catches the first mutant.
- Fix (passes with Prettier, ESLint and full
tsc --noEmitclean; kills both mutants). The two new cases mirror today's API:
// app/src/tests/fixtures/libs/metadataUtilsMocks.ts: replace the two constant groups
export const TEST_TIME_PERIOD_YEARS = {
THROUGH_2036: [2036, 2035, 2022],
// The US API's years today: 2022 to 2035, newest first
US_THROUGH_2035: [2035, 2034, 2022],
// The UK API's years today: 2024 to 2030
UK_2024_TO_2030: [2024, 2025, 2030],
NONE: [],
} as const;
export const EXPECTED_DATE_RANGES = {
THROUGH_2036: { minDate: '2022-01-01', maxDate: '2036-12-31' },
US_THROUGH_2035: { minDate: '2022-01-01', maxDate: '2035-12-31' },
UK_2024_TO_2030: { minDate: '2024-01-01', maxDate: '2030-12-31' },
FALLBACK: { minDate: '2022-01-01', maxDate: '2036-12-31' },
} as const;// app/src/tests/unit/libs/metadataUtils.test.ts: inside describe('getDateRange'), after the THROUGH_2036 test
it('given metadata that ends in 2035 then the range ends in 2035, not the fallback', () => {
// Given
const state = mockStateWithTimePeriods(TEST_TIME_PERIOD_YEARS.US_THROUGH_2035) as RootState;
// When
const result = getDateRange(state);
// Then
expect(result).toEqual(EXPECTED_DATE_RANGES.US_THROUGH_2035);
});
it('given metadata from 2024 to 2030 then the range starts in 2024 and ends in 2030', () => {
// Given
const state = mockStateWithTimePeriods(TEST_TIME_PERIOD_YEARS.UK_2024_TO_2030) as RootState;
// When
const result = getDateRange(state);
// Then
expect(result).toEqual(EXPECTED_DATE_RANGES.UK_2024_TO_2030);
});Suggestions
G1. Test the store as it is while metadata loads
- Where:
app/src/tests/unit/libs/metadataUtils.test.ts:292-302. - Evidence: the test "given no time periods yet" uses a fully loaded store (
loading: false, parameters, version1.0.0) withtime_period: []. Before load, the app has the reducer'spendingstate (app/src/reducers/metadataReducer.ts:74-80). The current test is valid for the selector's input, so this is about fidelity, not a bug. - Fix (passes; keep the existing
NONEtest, because it covers "loaded, with an empty year list"). The repo builds this state the same way inapp/src/tests/unit/pages/reportBuilder/useReportSPMSelectionError.test.tsx:114, andtesting.md:182asks for test data in fixtures:
// metadataUtilsMocks.ts: imports
import metadataReducer, { fetchMetadataThunk } from '@/reducers/metadataReducer';
// metadataUtilsMocks.ts: at the end
// Helper to create the RootState that the app has while the metadata request is in flight
export const mockStateWhileMetadataLoads = (): Partial<RootState> => ({
metadata: metadataReducer(undefined, fetchMetadataThunk.pending('request', 'us')),
});// metadataUtils.test.ts: add mockStateWhileMetadataLoads to the fixture imports
it('given the metadata request is still in flight then falls back to 2022 through 2036', () => {
// Given
const state = mockStateWhileMetadataLoads() as RootState;
// When
const result = getDateRange(state);
// Then
expect(result).toEqual(EXPECTED_DATE_RANGES.FALLBACK);
});G2. Say that the fallback is shared with the UK
- Where:
app/src/libs/metadataUtils.ts:34-40; PR body, Summary. - Evidence:
getDateRangetakes no country, and the UK metadata is 2024–2030. The shared fallback was already wrong for the UK at both ends onmain. Users see no effect, but a reader of "US 2036 extension" can conclude that the UK now offers 2036. - Fix: add a comment above the
if(Prettier, ESLint andtscstay clean):
// Applies only until the country's metadata loads, and is shared by the US
// and the UK. Once loaded, the metadata's own years replace it.Add to the Summary: "The fallback is shared with the UK and is not shown in today's UI: the date controls that read it render only after metadata loads."
G3. Refresh two stale statements in the PR body
- Test count. The Test plan says "26 passed". The file now runs 40. The count was correct when written: commit
260dd810(2026-10-22) added 14 search-visibility tests to the file before the rebase. Write: "Vitest,src/tests/unit/libs/metadataUtils.test.ts: 40 passed (43 with the addedgetDateRangecases)." - Merge order. The Summary says "so this PR can merge first". The merge hold was agreed on 2026-10-15, one day later, and PolicyEngine/policyengine-api#3926 lists this PR as "ready but held". Write: "The fallback applies only until metadata arrives, so merging this before #3928 changes nothing that users see. It is held under the merge hold in #3926 until spm-calculator 1.1.0 is on PyPI and 2036 runs end to end."
Investigated and cleared
- Can the fallback let a user pick a 2036 that the API cannot run? No. Each caller renders only after metadata loads:
DraftTableandProvisionDetailswait for a non-empty parameter index (app/src/pages/flagship/Build.page.tsx:45), and the other two need a parameter from metadata. Parameters andtime_periodarrive in one reducer action (app/src/reducers/metadataReducer.ts:99-103). No production path displays the fallback. - Does
getDateRangeset the simulation year? No. The report-year picker readsgetTaxYears(app/src/components/flagship/reform/DraftHeader.tsx:285-289). - Other stale 2035/2036 values. None need a change.
app/src/pathways/report/components/policyParameterSelector/HistoricalValues.tsx:217'2036-12-31'is a chart-axis fallback that is already onmain. - Time zones and inclusive end. Plain
YYYY-MM-DDstrings, parsed bydayjsas local time; the date picker rejects only days aftermaxDate.
Merge timing
Hold the merge under the agreed 2036 hold until spm-calculator 1.1.0 is on PyPI and 2036 runs end to end. Today PyPI ends at 1.0.1, spm-calculator#51 has changes requested, and policyengine-api#3928 is a draft. Any exception needs explicit agreement from @anth-volk and @PavelMakarchuk on #3926. An early merge would not change what users see. The US metadata ends in 2035, loaded metadata replaces the fallback, and no production path shows a date control from the fallback. This PR alone also does not make the app offer 2036; only policyengine-api#3928 does. The app stops at 2035 because the API's years stop at 2035, not because this PR is unmerged. If the 2036 extension is dropped, revert the fallback to 2035-12-31.
Validation Summary
| Check | Result |
|---|---|
| CI at head | 10/10 pass (Tests, Type checking, Lint and format, Build, Visual regression, Storybook, Vercel x2, embedded URLs) |
metadataUtils.test.ts at head |
40 passed |
app/src/tests/unit/libs/ at head |
69 files, 1171 passed |
Whole app/ suite at head |
457 files: 5901 passed, 10 skipped, 0 failed |
| With SA1, G1 and G2 applied | Changed file 43 passed; whole suite 5904 passed |
| Mutation, PR tests (8 mutants) | 6 killed by the changed file. "Always return the fallback" survives the file (caught only by DraftTable.test.tsx:170). "Metadata start fixed at 2022" survives the whole suite |
| Mutation, with fixes | 8 of 8 killed by the changed file |
Prettier, ESLint (--max-warnings 0), tsc --noEmit |
Clean at head and with fixes |
Review Severity: REQUEST_CHANGES
One should-address item is open: the new tests do not prove that loaded metadata replaces the fallback.
Next Steps
- Add the two metadata cases from SA1 and run
bun run vitest -- src/tests/unit/libs/metadataUtils.test.ts. - Optionally add G1 (in-flight store test) and G2 (comment).
- Update the PR body (G2 sentence, G3 test count and merge order).
- Keep the PR unmerged until spm-calculator 1.1.0 is on PyPI and 2036 runs end to end, or until @anth-volk and @PavelMakarchuk agree an exception on policyengine-api#3926.
- Request round 2 after the push.
The existing cases could not tell metadata from the fallback, because both gave 2022 to 2036. New cases use today's US (2022 to 2035) and UK (2024 to 2030) years, so a selector that always returned the fallback, or fixed the lower bound at 2022, now fails. A case for the store while the metadata request is in flight covers the state the app actually has before load. A comment says the fallback is shared by the US and the UK. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
@PavelMakarchuk round 1 is addressed in 1b64c79:
|
PavelMakarchuk
left a comment
There was a problem hiding this comment.
Code Review: PR #1266 "Fall back to a 2022–2036 date range before metadata loads" (@DTrim99, round 2)
ID legend: C = critical (must fix), SA = should address, G = suggestion.
Source Documents
- Reviewed head SHA: 1b64c79
- Mode: incremental (round 2),
7e9e173c71..1b64c79d9e - Scope: one commit,
1b64c79d9"Test that loaded metadata replaces the date-range fallback": a 2-line comment inapp/src/libs/metadataUtils.ts, 3 newgetDateRangecases inapp/src/tests/unit/libs/metadataUtils.test.ts, and new constants and amockStateWhileMetadataLoadshelper inapp/src/tests/fixtures/libs/metadataUtilsMocks.ts. The PR body is updated. - Read: the incremental diff;
getDateRangeat the head; thefetchMetadataThunk.pendingcase inapp/src/reducers/metadataReducer.ts:74-81; the PR body; PyPIspm-calculator; PolicyEngine/policyengine-api#3926 and #3928; spm-calculator#51.
Branch Status
- 2 commits ahead of
mainand 63 behind. None of the 63 commits touch the three changed files or the metadata reducer, andgit merge-treeis clean. GitHub reports the PR as blocked only by the required review. - CI at the head: 10/10 pass, including Type checking, so the new
fetchMetadataThunk.pending('request', 'us')call typechecks in the full project.
Round 1 items
| ID | Status | Evidence |
|---|---|---|
| SA1 | RESOLVED | The two metadata cases are in metadataUtils.test.ts:293-313 (the in-flight case is at :326), with US_THROUGH_2035 and UK_2024_TO_2030 in the fixture. I re-ran both round-1 mutants against the changed file. "Always return the fallback" now fails 2 tests (the 2035 and the 2024–2030 cases). "Metadata start fixed at 2022" now fails the 2024–2030 case. In round 1 the first survived the file and the second survived the whole suite. |
| G1 | RESOLVED | mockStateWhileMetadataLoads builds the store from the real reducer: metadataReducer(undefined, fetchMetadataThunk.pending('request', 'us')). The pending case returns initialState with loading: true (metadataReducer.ts:74-81), so the test pins the in-flight state that users would see, not a hand-written copy of it. The empty-list case stays. |
| G2 | RESOLVED | The comment is at metadataUtils.ts:35-36, and the Summary has the UK sentence. |
| G3 | RESOLVED | The Test plan gives 43 tests (40 after the rebase, plus 3 new cases), which matches my run. The Summary states the merge hold under PolicyEngine/policyengine-api#3926. |
New findings
Critical
None.
Should address
None.
Suggestions
None.
Tests run
| Check | Result |
|---|---|
metadataUtils.test.ts at the head |
43 passed |
| Mutant "always return the fallback" | killed (2 failures) |
| Mutant "metadata start fixed at 2022" | killed (1 failure) |
| CI at the head | 10/10 pass |
Merge timing
The hold from round 1 still applies. Today PyPI spm-calculator ends at 1.0.1, and PolicyEngine/policyengine-api#3928 is still a draft. spm-calculator#51 is approved but not merged or released. Keep this PR unmerged until spm-calculator 1.1.0 is on PyPI and 2036 runs end to end, or until @anth-volk and @PavelMakarchuk agree an exception on #3926. As round 1 found, an early merge would not change what users see.
Review Severity: APPROVE
All round-1 items are resolved, and the new tests now prove that loaded metadata replaces the fallback. Merge only after the 2036 hold lifts.
Summary
Part of the US 2036 extension, tracked in PolicyEngine/policyengine-api#3926.
Until API metadata loads,
getDateRangefalls back to a fixed range. This PR moves the fallback's last day from 2035-12-31 to 2036-12-31.Everything else in the app reads its years from
economy_options.time_periodin the API metadata. 2036 reaches the year pickers once the API lists it in PolicyEngine/policyengine-api#3928. That PR stays a draft until a bundle carries a 2036 SPM forecast. The fallback applies only until metadata arrives, so merging this before #3928 changes nothing that users see. It is held under the merge hold in PolicyEngine/policyengine-api#3926 until spm-calculator 1.1.0 is on PyPI and 2036 runs end to end.The fallback is shared with the UK and is not shown in today's UI: the date controls that read it render only after metadata loads.
Files
app/src/libs/metadataUtils.ts: the fallbackmaxDateis now'2036-12-31'. A comment says it applies only until metadata loads and is shared by the US and the UK.app/src/tests/unit/libs/metadataUtils.test.ts: fivegetDateRangetests.app/src/tests/fixtures/libs/metadataUtilsMocks.ts: addsmockStateWithTimePeriods,mockStateWhileMetadataLoads(the reducer'spendingstate) and the tests' constants.mockStateWithMetadatanow types its overrides asPartial<MetadataApiPayload['result']>, the object it spreads them into. The old type,Partial<MetadataApiPayload>, rejectseconomy_optionsundertsc(TS2353). No earlier caller passed overrides.Test plan
src/tests/unit/libs/metadataUtils.test.ts: 43 passed. That is 40 after the rebase, which picked up 14 search-visibility tests frommain, plus 3 newgetDateRangecases.--max-warnings 0) are clean on the three files.tsc --noEmitis clean on the test and fixture files. This used a narrowed config; CI runs the full project typecheck.🤖 Generated with Claude Code