Repository navigation
fix(reports): the net-worth line disowned money the card beside it counted - #807
Merged
Merged
Conversation
…unted The series was the only non-reconciliation caller of clearedBalanceAsOf(). Every other surface that states a balance -- the dashboard card, the forecast anchor, the calendar's past-day line, the pot ledger -- reads currentBalanceAsOf(), so the chart could draw a line that contradicted the number printed directly above it with nothing on screen to say which was which. It now reads the same seam. On the shipped sample dataset in EUR on 2026-09-13 the point whose own day IS today moved 643743 -> 613574 and equals the card's 613574 exactly; the 30169 gap was five uncleared JPY cash rows the line refused to count. Earlier points moved by whatever was still unconfirmed on their own day (2026-07-31 918129 -> 900530, 2026-08-31 837048 -> 809958) and 2026-06-30 did not move at all. One difference is left and it is the date: the card asks for today, a point asks for its bucket's last day, so a this_month report still samples the 30th. That residue is now the whole of it rather than one of two causes. Nothing stated the cleared choice. It was introduced unremarked by #272 while five surfaces were being unified onto one balance seam, no C7 acceptance criterion names cleared, and AccountBalanceQuery's own comment on currentBalanceAsOf() already read "Net worth asks this" -- a line the code has contradicted ever since. TheNetWorthSeriesAndTheCardDifferInTwoPlacesTest asserted the disagreement, so it is renamed and rewritten: it now pins the agreement on a shared day and the date-only gap on a bucket that outruns it. NoNetWorthHistoryIsStoredArchTest pinned the sampler to the literal spelling clearedBalanceAsOf while its own message claims only that the sampler still calls "the balance-as-of seam"; widened to either spelling, and confirmed red against the new code under the old regex. Spec: C7-R8 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Wessel Verheij <info@nightworks.io>
lessevv
enabled auto-merge (squash)
September 13, 2026 14:18
📎 Spec referencesThis PR cites the following spec identifiers:
Updated automatically on each push. |
|
lessevv
added a commit
that referenced
this pull request
Sep 13, 2026
…unted (#807) The series was the only non-reconciliation caller of clearedBalanceAsOf(). Every other surface that states a balance -- the dashboard card, the forecast anchor, the calendar's past-day line, the pot ledger -- reads currentBalanceAsOf(), so the chart could draw a line that contradicted the number printed directly above it with nothing on screen to say which was which. It now reads the same seam. On the shipped sample dataset in EUR on 2026-09-13 the point whose own day IS today moved 643743 -> 613574 and equals the card's 613574 exactly; the 30169 gap was five uncleared JPY cash rows the line refused to count. Earlier points moved by whatever was still unconfirmed on their own day (2026-07-31 918129 -> 900530, 2026-08-31 837048 -> 809958) and 2026-06-30 did not move at all. One difference is left and it is the date: the card asks for today, a point asks for its bucket's last day, so a this_month report still samples the 30th. That residue is now the whole of it rather than one of two causes. Nothing stated the cleared choice. It was introduced unremarked by #272 while five surfaces were being unified onto one balance seam, no C7 acceptance criterion names cleared, and AccountBalanceQuery's own comment on currentBalanceAsOf() already read "Net worth asks this" -- a line the code has contradicted ever since. TheNetWorthSeriesAndTheCardDifferInTwoPlacesTest asserted the disagreement, so it is renamed and rewritten: it now pins the agreement on a shared day and the date-only gap on a bucket that outruns it. NoNetWorthHistoryIsStoredArchTest pinned the sampler to the literal spelling clearedBalanceAsOf while its own message claims only that the sampler still calls "the balance-as-of seam"; widened to either spelling, and confirmed red against the new code under the old regex. Spec: C7-R8 Signed-off-by: Wessel Verheij <info@nightworks.io> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.



The problem
NetWorthSeriesQuerywas the only non-reconciliation caller ofclearedBalanceAsOf(). Every other surface that states a balance — the dashboard card, the forecast anchor, the calendar's past-day line, the pot ledger — readscurrentBalanceAsOf(). So the net-worth chart could draw a line that disagreed with the number printed directly above it, with nothing on screen to say which was which.It now reads the same seam.
clearedBalanceAsOf()is left to/reconcile, its only remaining caller, which is the one place the cleared/reconciled window is the right question.Before / after, on the shipped sample dataset
demo-1, EUR, today = 2026-09-13. The dashboard card reads 613574 throughout — it is unchanged by this PR.this_monthbucket)On the one day where the date difference is nil, the series was 30169 away from the card and is now equal to it. The gap was five
unclearedJPY cash rows on Japan Trip Cash (−48000 JPY) that the line refused to count — money the reader had spent, that the card counted and the chart did not. 2026-06-30 does not move because nothing was still unconfirmed by then.One difference is left, and it is the date: the card asks about today, a point about its bucket's last day, so a
this_monthreport still samples the 30th. That residue is now the whole of it rather than one of two causes.Why was it
clearedBalanceAsOf()?No stated reason exists, and the switch contradicts nothing.
git log -Lputs it atce23d0436(fix: five surfaces derived the same balance five ways, and no two agreed #272, "five surfaces derived the same balance five ways, and no two agreed") — a sweep unifying five surfaces onto one balance seam. The line changed from readingaccount.default_currencystraight to calling the seam; the cleared/current choice was never discussed in that commit, in a comment, or in a test name. Every later touch was an unrelated signature change.C7acceptance criterion names cleared. C7-R7 governs on-demand sampling; C7-R8 requires the divergence to be documented. Neither prescribes which rows a point counts.AccountBalanceQuery's own comment abovecurrentBalanceAsOf()already read "Net worth asks this and notcurrentBalance()" — a line the code has contradicted ever since. The comment is now true.Tests
One test asserted the disagreement, and it is changed deliberately.
Modules/Reports/tests/Unit/TheNetWorthSeriesAndTheCardDifferInTwoPlacesTest.phppinned "the card counts every row while the series counts only the cleared ones" as one of two named causes. Renamed toTheNetWorthSeriesAndTheCardDifferOnlyInTheDayTheyAskAboutTest.phpand rewritten: it now pins the agreement on a shared day and the date-only gap on a bucket that outruns it. Its name no longer states an invariant the code contradicts.Red before, green after:
The series read -10000 on the same day the card read -35000 — a 25000 gap on one day, carried entirely by one uncleared row. After:
4 passed.One arch guard was keyed on the old spelling.
tests/Contracts/NoNetWorthHistoryIsStoredArchTest.phppinned the sampler to the literalclearedBalanceAsOf, while its own failure message claims only that the sampler still calls "the balance-as-of seam". Widened to either spelling — which rows a point counts is the sampler's choice, and that guard is not the place it is made. Confirmed load-bearing by inversion: the new code under the old regex goes red.No other test moved.
NetWorthSeriesQueryTestandFxExclusionTestinsert rows without astatus, and the column defaults tocleared;NetWorthIncludesStartingBalanceTestsetsClearedexplicitly. All three are unaffected by the switch.Run: 22 passed across the six affected files, plus the comment/helper arch guards. Pint and PHPStan (level 10, strict) clean on
Modules/Reportsandtests/Contracts.Docs
.docs/features/reports/architecture.mddescribed the gap as two differences whose sum it was, with measured figures. It is now one difference — the date — with the figures re-measured on the same dataset and the same date, and a paragraph recording what the cleared choice did and did not buy.The
.docspage was not the wrong one. It already said "Both readAccountBalanceQueryas of a date, so the anchor is not the difference" — it had been corrected when the card moved offBalanceAnchorResolver. The stale "different anchor path" sentence is in the spec, at10-functional/features/c-insight/c7-reports.md, and is corrected in beatrax-app/spec#110 (auto-merge armed). That sentence was wrong before this PR and would have stayed wrong without it.Spec: C7-R8