Skip to content

fix(wallet): define the week trend bucket once, Monday-start and inclusive - #215

Merged
argszero merged 1 commit into
mainfrom
fix/trend-week-bucket-alignment
Sep 13, 2026
Merged

argszero merged 1 commit into
mainfrom
fix/trend-week-bucket-alignment

Conversation

@argszero

Copy link
Copy Markdown
Owner

Summary

The transactions trend chart's week bucket was defined twice, and the two definitions disagreed — so the weekly chart zero-filled its bars on 6 of 7 days while the summary cards above it showed real numbers.

layer definition
backend (GET /api/transactions/trend?bucket=week) strftime('%Y-%m-%d', time, 'weekday 1', '-7 days')
front-end axis (ui/js/app.js::txTrendDays) floor 「now」 to the UTC day, then step back 7 days at a time

SQLite's weekday N only advances forward to the next weekday N and is a no-op when the date already is N, so the trailing -7 days pushed a Monday row into the previous week (the other weekdays happened to land in the right week). The front-end axis, meanwhile, kept the weekday of 「now」. The two could only coincide when 「now」 was a Monday.

Measured through the real router with one row per weekday of the calendar week 2026-09-07 (Mon) .. 2026-09-13 (Sun), pts 1..7:

reading value
backend buckets (bucket=week) 2026-08-31 = 1.0 and 2026-09-07 = 27.0 (the Monday row a week early)
real txTrendDays axis keys (clock = Sunday) [2026-08-30, 2026-09-06, 2026-09-13]
points matched between the two 0 of 28
weekdays on which the axis lined up (7-weekday sweep) 1 / 7 (only Monday) — 7 / 7 after the fix

Both layers documented the invariant their own code broke: wallet.rs said // 配 modifier:周一起始 and app.js said (UTC 对齐到桶,与后端 strftime 口径一致). The week bucket is reachable from the shipped UI on two paths — the 「all」 range and any custom range wider than 60 days — and neither had test coverage.

Impact is display / API aggregation, not money: the ledger, balances, settlement and the transaction list are untouched; only this aggregation endpoint and the axis drawn from it.

Changes

  • Backend (src/routes/wallet.rs): the week modifier is now 'weekday 0', '-6 days' — Monday-start and inclusive — verified against sqlite3 for all seven weekdays plus the 00:00:00 / 23:59:59 boundaries. The comment is corrected to 周一起始(含周一当日), with a note that the front-end axis uses the same definition.
  • Front-end (ui/js/app.js): txTrendDays floors the week axis to the same Monday (Date.UTC(y, m, d - ((d.getUTCDay() + 6) % 7))). The hour and day branches are untouched, so their axes are byte-identical before and after.
  • ui/index.html: js/app.js?v=20260913-3 → 20260914-1 (cache-bust convention).
  • No config / data-structure change, so config/config.example.toml needs no update. No schema, no i18n key, no new dependency.

Related Issue

None — the problem was found by a runtime probe, not an issue report.

Tests

  • cargo test — 217 passed (was 216; +1 new test), 0 failed
  • cargo fmt --check — clean; cargo clippy --all-targets -- -D warnings — clean
  • New test added

New test routes::wallet::tests::transactions_trend_week_bucket_starts_on_monday drives the real handler over one calendar week (one row per weekday, 2026-09-07 .. 2026-09-13) and asserts the result is a single bucket starting on that week's Monday, holding all 7 rows (count = 7, expense = 28).

  • Red on the pre-change tree: with only the modifier reverted it reports 2 buckets (the Monday row a week earlier) — and it is the only failure (216 passed / 1 failed), so the assertion discriminates exactly this defect. The existing transactions_trend_buckets (day / hour / illegal-value fallback) stays green.

Front-end evidence (no JS test runner is introduced — the probe over the real source is the evidence, same ruling as earlier UI cycles):

  • txTrendDays is extracted from the real ui/js/app.js by brace matching (the source text, not a re-typed copy) and run with the clock frozen at each of the 7 weekdays. It matches the backend axis on 7/7 weekdays after the fix, vs 1/7 before.
  • Negative control: the day and hour axes are byte-identical pre/post, so the change is scoped to week.

Checklist

  • Branch naming follows the convention (fix/)
  • Commit message uses Conventional Commits (fix(wallet): …)
  • Single responsibility, minimal diff
  • ⛔ No release (the tag is host-authorized only)
  • Not in scope, deliberately: the day / hour buckets, the 「only non-empty buckets are returned」 contract (the front-end zero-fills), the trend endpoint's filter / summary semantics, and the dashboard sparkline (day-only, already aligned)

…usive

The transactions trend chart's `week` bucket was defined twice, and the two
definitions disagreed:

- backend: `strftime('%Y-%m-%d', time, 'weekday 1', '-7 days')`
- front-end axis (`ui/js/app.js::txTrendDays`): floor 「now」 to the UTC day and
  step back 7 days at a time

SQLite's `weekday N` only advances *forward* to the next weekday N and is a
no-op when the date already is N, so the trailing `-7 days` pushed a **Monday**
row into the *previous* week (other weekdays happened to land in the right
week). The front-end axis, meanwhile, kept the weekday of 「now」. The two only
coincided when 「now」 was a Monday, so on the two reachable paths that ask for
`bucket=week` (range 「all」, or a custom range wider than 60 days) the weekly
chart zero-filled every bar except on Mondays: measured through the real
router, one row per weekday of 2026-09-07..2026-09-13 produced backend buckets
`2026-08-31` (1.0) and `2026-09-07` (27.0), while the real `txTrendDays` built
the axis `[2026-08-30, 2026-09-06, 2026-09-13]` and matched **0 of 28** points.

Both layers documented the invariant they broke: `wallet.rs` said
`// 配 modifier:周一起始` and `app.js` said `(UTC 对齐到桶,与后端 strftime 口径一致)`.

Fix: one definition — Monday-start, inclusive — used by both layers.
- backend: `'weekday 0', '-6 days'` (verified against sqlite3 for all seven
  weekdays, including the Monday and Sunday boundaries);
- front-end: the `week` axis is floored to the same Monday
  (`Date.UTC(y, m, d - ((getUTCDay() + 6) % 7))`); `hour`/`day` are untouched.

Tests / evidence:
- new `transactions_trend_week_bucket_starts_on_monday` drives the real handler
  over one calendar week (one row per weekday) and asserts a single bucket whose
  start is that week's Monday, holding all 7 rows (count 7, expense 28). It is
  **red on the pre-change tree** (2 buckets, the Monday row a week earlier) and
  the only failure when just the modifier is reverted.
- the real `txTrendDays` (extracted from `ui/js/app.js`, not re-typed) matches
  the backend axis on **7/7** weekdays after the fix, vs **1/7** before; the
  `day`/`hour` axes are byte-identical before and after.

Scope: display / API aggregation only — no ledger, balance or settlement change.
`ui/index.html` cache-bust bumped.
@argszero
argszero merged commit 1febe59 into main Sep 13, 2026
1 check passed
@argszero
argszero deleted the fix/trend-week-bucket-alignment branch September 13, 2026 16:32
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