fix(reports): compare against an equal-length baseline period - #149
Merged
Conversation
Every percentage in the Spending tab's Change column was measured against a window of a different length than the one it described. For today's "Last 3 months" (Jun 2 - Sep 2, 92 days) the baseline was Feb 2 - May 31 — 118 days, 28% longer, with a 2-day gap. `rangeToDateBounds` returns a *rolling* window, which touches four calendar months, so `shiftDateRange`'s month-span arithmetic counted 4 rather than 3 and the end-of-month snap then stretched the baseline further. Calendar-month arithmetic is only meaningful for a range that actually spans whole months, so it is now used only when the range runs from the 1st to the last day of a month (Apr 1 - Jun 30 still shifts to Jan 1 - Mar 31). A rolling window shifts by its own length instead, so the baseline is always the period immediately before it and exactly as long. Two related defects in the same flow: - The landing view showed no comparison at all. `page.tsx` read a bare /reports as all-time while ReportFilterBar defaulted it to the 3M preset and rendered "Last 3 months" — the same URL and data with two different UIs, and the Change column only appeared once you re-picked the preset you were already on. Both now read `resolveReportDateSelection`, so they cannot drift again. - `formatDateShort` never emitted a year, so a custom 2019 range rendered as "Jan 1 - Mar 31" and a year-over-year comparison label was indistinguishable from the current year's. The year is now shown only when it is not the current one, which leaves recent dates and chart axis ticks unchanged. Closes #143
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.
Closes #143.
Every percentage in the Spending tab's Change column was measured against a window of a different length than the one it described.
28% longer, plus a 2-day gap. The UI stated it itself — every badge read
vs Feb 2 – May 31.Cause
rangeToDateBounds("3M")returns a rolling window (Jun 2 → Sep 2) that touches four calendar months, soshiftDateRange's month-span arithmetic evaluated to 4:and the end-of-month snap then pulled
toback to May 31.Calendar-month arithmetic is only meaningful for a range that genuinely spans whole months, so it now applies only when the range runs from the 1st to the last day of a month.
Apr 1 – Jun 30still shifts toJan 1 – Mar 31(existing test unchanged). A rolling window shifts by its own length instead, so the baseline is always the period immediately before it and exactly as long.Two related defects in the same flow
The landing view showed no comparison at all.
page.tsxread a bare/reportsas all-time, whileReportFilterBardefaulted the same URL to the 3M preset and labelled it "Last 3 months". Identical URL and data, two different UIs — the Change column only appeared after re-picking the preset you were already on. Both now readresolveReportDateSelection, a single shared resolver, so they cannot drift apart again.Dates never showed a year.
formatDateShortformatted{ month, day }only, so a custom 2019 range rendered as "Jan 1 – Mar 31" with nothing saying which year, and a year-over-year comparison label was indistinguishable from this year's. The year now appears only when it is not the current year — recent dates and chart axis ticks (which use this same helper as a tick formatter) are unchanged.Testing
src/lib/date-utils.test.ts— the118 → 92regression pinned directly, equal-length assertion, back/forward symmetry, a property test over 1–200 day spans, month-aligned non-regression, and the year-formatting rule (fixture years derived fromnew Date()per the repo's time-in-tests convention).src/lib/report-date-selection.test.ts— new, covering the bare default,?preset=3Mresolving identically to it, all-time, custom ranges, preset-wins-over-from/to, and half-specified ranges.Typecheck and lint clean. Full suite 1055/1056 — the single failure,
investment-queries.test.ts > returns dayChange from holdings_history, is pre-existing hardcoded-date rot that fails identically onmain.Note on verification
Unlike #142, this one is verified by tests only, not in a live browser. The dev Postgres container and its
ledgr_pgdatavolume were removed during the integration runs (likely the testcontainers reaper), taking the dev dataset with them, so there was no logged-in app to re-check against. The regression is pinned by unit tests at the exact values observed in production before the data was lost.🤖 Generated with Claude Code