Skip to content

fix(wallet): report the dashboard net over the month, not over the 7-day series - #193

Merged
argszero merged 1 commit into
mainfrom
fix/dashboard-net-window
Sep 13, 2026
Merged

argszero merged 1 commit into
mainfrom
fix/dashboard-net-window

Conversation

@argszero

Copy link
Copy Markdown
Owner

Summary

GET /api/dashboard returned a net that was computed over a different window than the row that displays it. net was SUM(series) = the last 7 days, while the per-type month rows it sits above are a calendar-month aggregate — and the row is labelled 「本月净变化」 / "Net change this month", inside a card titled 「本月点数变化」 / "Month point changes".

So the panel's total did not equal the rows directly beneath it, on the dashboard (#dash-month-changes) and on the wallet page (#month-changes). It also drifted in both directions across the month: on day of month ≤ 6 the 7-day window still contains days of the previous month, and on day ≥ 9 a row from earlier in this month falls outside the 7-day window.

net is now the signed sum of the month rows the handler has already fetched — income = type IN ('earn','topup','gift'), otherwise expense — the same convention as the sparkline CASE, transactions_trend's net column and routes/ops.rs's month flow. series is unchanged (it is the sparkline's source, correctly labelled as 7 days), no new query is issued, and the frontend needs no change (it already reads Live.dashboard.net).

Severity: display-only, not money. No settlement or enforcement path reads net; stored pts and the other aggregates were already correct.

Related Issue

None — no issue exists for this, and none is fabricated. Verified closingIssuesReferences is empty on purpose.

Changes

  • src/routes/wallet.rs::dashboard: net = signed sum of the already-fetched month rows (type-based direction), instead of series.iter()…sum()
  • Handler + module doc comments now name both windows explicitly (month/net = calendar month; series = last 7 days)
  • No config / data-structure change (no config.example.toml impact); no ui/ change; no i18n key

Tests

  • cargo test — 181 passed (baseline 180 + 1 new)
  • cargo fmt --check — clean
  • cargo clippy --all-targets -- -D warnings — clean
  • New test routes::wallet::tests::dashboard_net_is_the_month_net_not_the_7_day_series

The new test drives the real handler through the router. Its fixture straddles both windows with pure data (no clock faking): consume 10.0 at start of month (always in the month; inside the 7-day window only when day of month ≤ 7), consume 4.0 at now -6 days (always in the 7-day window; inside the month only when day of month ≥ 7), earn 3.0 today (always in both). The assertion is the specification, computed independently by SQL in the test, not a copy of the implementation expression:

assert!((net - month_spec).abs() < 1e-9)

Observed today (day 13), the fixture is discriminating:

month=[{"pts":14.0,"type":"consume"},{"pts":3.0,"type":"earn"}]
series=[{"date":"2026-09-07T00:00:00Z","pts":-4.0},{"date":"2026-09-13T00:00:00Z","pts":3.0}]
net=-11.0 | month_spec=-11 series_spec=-1 discriminating_today=true

A/B: reverting only the net computation back to series ⇒ exactly this one assertion red / 180 green (it reports net = -1 where the month spec is -11).

Calibration recorded in the test comment: the two windows coincide on day 7 of a month (the window is then exactly days 1..7 ⊆ the month), so on that single day any data-only fixture is non-discriminating. The assertion is still the specification; no clock hack was added to force divergence.

Checklist

  • Branch naming follows the convention (fix/)
  • Commit message uses Conventional Commits (fix(wallet): …)
  • Single responsibility, minimal change (one file; display-only field; no API shape change)

…day series

`GET /api/dashboard` returned a `net` computed as `SUM(series)` — the last 7 days —
while the per-type `month` rows it is displayed above are a calendar-month aggregate,
and the row is labelled 本月净变化 / "Net change this month". The panel's total
therefore did not equal the rows directly beneath it (on the dashboard and on the
wallet page), and the 净 row could count the previous month's activity (day of
month <= 6) or miss this month's earlier activity (day of month >= 9).

`net` is now the signed sum of the `month` rows the handler already fetched —
income = `type IN ('earn','topup','gift')`, otherwise expense — the same
convention as the sparkline CASE, `transactions_trend`'s net column and
`routes/ops.rs`'s month flow. `series` is unchanged (the sparkline's source),
and the handler/module docs now name both windows.

Display-only: no settlement or enforcement path reads `net`.
@argszero
argszero merged commit 9fead29 into main Sep 13, 2026
1 check passed
@argszero
argszero deleted the fix/dashboard-net-window branch September 13, 2026 01:24
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.

1 participant