fix(analytics): make the displayed numbers mean what they say
ci / lint-test (push) Failing after 44s
ci / lint-test (push) Failing after 44s
Six metric-integrity defects from the UI/IA review, plus two found while verifying the review's own claims against the code. The reconciled-row exclusion existed only in queries.ts. Every analytics route counted the superseded manual rows as spend — 48 rows, $4,474.79 of double count, invisible precisely because the transaction list looked right. It is now one fragment both sides import. The spend-pace chart computed its own totals in the browser: gross amounts, debits only, no personal share, no refunds, fees, interest or itemised loan repayments. On live data it ended July at $4,747.31 under a headline reading $3,597.10 — and its own baseline line was drawn from the split-adjusted monthly totals, so the two series in one chart disagreed with each other. Both now come from /api/analytics/daily, built from the same fragments as the headline. Fees aggregated every statement ever imported with no date filter, under a heading with no period, so a lifetime figure read as a current one and grew forever. Now bounded, labelled, and selectable. Comparisons no longer measure a month in progress against complete ones: the in-progress month is out of every baseline, and a selected current month is compared through the same day. Two the review did not catch: - Every analytics window was a day early. toISOString() on a local-midnight Date converts backwards through UTC. Surfaced only once fees started reporting the range it had used. - /monthly rounded per category, /daily per category-day, so the pace chart ended a few cents off the headline above it. Shared currency needed amending rather than applying. Reading s.currency would have labelled every order row AUD, since an order receipt has no statement and carries its own currency — the opposite convention from a foreign charge on an AUD statement, where amount IS AUD. NATIVE_CURRENCY's COALESCE order keeps the two apart. Balances also now count rows whose AUD value is genuinely unknown instead of netting a foreign figure against AUD ones. Latent today: no foreign transaction is currently split. Tag-filtered balance cards no longer claim "owes you". With a filter on, payments are deliberately not subtracted, so the figure is a split total and settling against it would record a payment for a debt that never was. Split-coverage warnings deliberately omitted (user decision).
This commit is contained in:
@@ -1,7 +1,61 @@
|
||||
# UI and information architecture review
|
||||
|
||||
**Date:** 2026-07-26
|
||||
**Status:** Review and redesign proposal — nothing implemented
|
||||
**Status:** Priority 0 implemented 2026-07-27 (see below). Priorities 1–4 remain
|
||||
proposals.
|
||||
|
||||
## Implementation status — Priority 0 (2026-07-27)
|
||||
|
||||
All six Priority 0 items landed, with three amendments found while verifying the
|
||||
proposals against the code:
|
||||
|
||||
1. **Reconciled source rows** — the exclusion was missing from *all five*
|
||||
analytics routes, not only `/monthly`. It is now one fragment
|
||||
(`EXCLUDE_RECONCILED_SOURCE`) that `queries.ts` also imports, so the two
|
||||
halves cannot drift apart again. Real effect: 48 rows, **$4,474.79** of
|
||||
double-counted spend removed from every category total, mover, Pareto and
|
||||
merchant ranking.
|
||||
2. **Spend pace** — now served by `/api/analytics/daily`, built from the same
|
||||
fragments as the headline. Measured on live data, the old client-side series
|
||||
ended July at **$4,747.31** against a headline of **$3,597.10** — a 32%
|
||||
overstatement of the number directly above it.
|
||||
3. **Fees and interest** — bounded by an explicit period (default 12 months,
|
||||
`months=0` for all time), with the range shown and selectable. The unbounded
|
||||
figure was overstating the last 12 months by roughly **$2,700 of fees**.
|
||||
4. **Split-coverage warning** — *deliberately not implemented* (user decision,
|
||||
2026-07-27).
|
||||
5. **Shared foreign currency** — amended. The obvious fix, reading `s.currency`,
|
||||
would have mislabelled every order row as AUD, because an order receipt has
|
||||
no statement and carries its own currency. Sourcing is now
|
||||
`NATIVE_CURRENCY = COALESCE(s.currency, t.foreign_currency_code, 'AUD')`,
|
||||
whose COALESCE order keeps two opposite denomination conventions apart. Note
|
||||
this change is **latent on today's data**: no foreign transaction is
|
||||
currently split, so nothing on Shared looks different yet.
|
||||
6. **Partial-month comparisons** — the hero average, the top movers and the pace
|
||||
baseline now exclude the in-progress month, and compare through the same day
|
||||
of the month when the selected month is the current one.
|
||||
|
||||
Also fixed while in here, both found by checking rather than by proposal:
|
||||
|
||||
- **Every analytics window was a day early.** `toISOString()` on a local-midnight
|
||||
`Date` converts backwards through UTC in any timezone east of Greenwich. Now
|
||||
`toDateStr()`. This was pre-existing in `/monthly` and `/merchants`.
|
||||
- **Rounding grain.** `/monthly` rounded per category and `/daily` per
|
||||
category-day, so the pace chart ended the month a few cents off its own
|
||||
headline. Both now carry 4dp and round once, at display.
|
||||
|
||||
Guarded by `src/__tests__/integration/analytics-sql.test.ts`.
|
||||
|
||||
The doc's characterisation of `REGULAR_CATEGORIES` (Insights section) is also
|
||||
slightly off: the set has 13 members including rent, utilities, insurance and
|
||||
subscriptions, not the 8 listed. The case for replacing it stands — a flat
|
||||
binary cannot express obligation — but that is the reason, not arbitrary
|
||||
membership. Note too that the proposed Fixed/Essential/Lifestyle model needs a
|
||||
commitment dimension that does not exist yet: `fees` cannot be split into
|
||||
avoidable versus known-annual, `subscriptions` cannot be split into contractual
|
||||
versus cancellable, and the contracted loan repayment is not in the spend stream
|
||||
at all (`SPEND_BASE` keeps only the interest portion). That is a data-model
|
||||
change, not an Insights rework.
|
||||
|
||||
## Executive summary
|
||||
|
||||
|
||||
Reference in New Issue
Block a user