From 55ed50b9325065b01157bc897627eb06f5a2d551 Mon Sep 17 00:00:00 2001 From: Viktor Barzin Date: Mon, 1 Jun 2026 21:52:59 +0000 Subject: [PATCH] docs(plans): wealth dashboard consolidation design MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Consolidate the wealth Grafana dashboard 36 -> ~17 panels with zero metric loss: merge the 3 NW/contribution/growth timeseries into 1, the 11 returns/Δ stat cards into 1 returns table, the 2 yearly barcharts into 1 combo, and the 3 net-pay-vs-market-gain panels into 1 (grain dropdown); reorganize into collapsed rows. Also rebuild the projection as a Trend panel (numeric years-from-today x-axis) so it renders regardless of the dashboard time range (fixes empty-by-default). Philosophy: merge duplicates, keep every metric. [ci skip] Co-Authored-By: Claude Opus 4.7 --- ...1-wealth-dashboard-consolidation-design.md | 113 ++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 docs/plans/2026-06-01-wealth-dashboard-consolidation-design.md diff --git a/docs/plans/2026-06-01-wealth-dashboard-consolidation-design.md b/docs/plans/2026-06-01-wealth-dashboard-consolidation-design.md new file mode 100644 index 00000000..87286c8c --- /dev/null +++ b/docs/plans/2026-06-01-wealth-dashboard-consolidation-design.md @@ -0,0 +1,113 @@ +# Wealth Dashboard Consolidation — Design (2026-06-01) + +## Goal + +The `wealth` Grafana dashboard (UID `wealth`) has grown to **36 panels** with +heavy duplication. Consolidate to **~17 panels with ZERO metric loss** by +merging redundant panels, and fix the projection's empty-by-default problem. +Philosophy (user-locked): *merge duplicates, keep every metric* — no metric the +user tracks today is removed. + +## Current state — 36 panels, duplication clusters + +| Cluster | Panels today | Issue | +|---|---|---| +| **1. NW/contribution/growth over time** | "Net worth — total over time", "Net contribution vs market value", "Growth (market value − contribution) over time" | All restate `NW = contribution + growth` | +| **2. Returns/deltas stat cards** | "12mo return/contrib/gain" (3) + "Δ 1d/7d/30d/90d" × (all/mkt) (8) = 11 cards | Same idea, many windows | +| **3. Net pay vs market gain** | "…cumulative", "…per year", "…per month" (3) | Same comparison, 3 grains | +| **4. Yearly bars** | "Yearly investment return %" + "Annual change decomposition" (2) | Same yearly data, two encodings | +| Projection row (5) | text + 3 stats + projection chart | Stats duplicate Overview; chart empty by default (shared time-range) | + +## Target layout — collapsed rows + +### Row: Overview (expanded by default) +- **Keep** 4 snapshot stats: Net worth · Net contribution · Growth · ROI%. +- **NEW "Returns" table** ← merges cluster 2 (11 cards). `table` panel: one row + per window (1d / 7d / 30d / 90d / 12mo), columns **Δ all £ · Δ market £ · + return %**. Reuses the existing per-window latest-vs-N-days-ago SQL, UNION'd + into 5 rows. Preserves every value (12mo contrib = Δall − Δmkt) and adds + return-% for the short windows. + +### Row: Net worth over time +- **NEW merged timeseries** ← cluster 1: two lines — `net_contribution` and + `total_value` (market value) — with the **growth gap shaded** (fillBelowTo / + area between). Optionally a 3rd faint "growth" line (= total_value − + net_contribution). Reuses the "Net contribution vs market value" query. +- **Keep** "Per-account stacked — total value" · "Cash vs invested (stacked)". + +### Row: Returns & contributions +- **NEW yearly combo** ← cluster 4: timeseries panel, `contributions` + + `market_gain` as **bars** (drawStyle=bars via per-series override) + a + **`return_pct` line on a right Y-axis**. One query returns + `year, contributions, market_gain, return_pct` (merges the two existing + yearly queries — both already share the `yearly`/`ep` CTEs). +- **Keep** "Monthly contributions vs market gain" · "Per-account ROI %". + +### Row: Income vs market +- **NEW merged "Net pay vs market gain"** ← cluster 3: one timeseries + a + **`$grain` custom variable** (`cumulative` / `yearly` / `monthly`). The rawSql + switches bucketing on `$grain`. Default `cumulative`. + +### Row: Holdings — **Keep** Positions · Activity log +### Row: RSUs (META) — **Keep** vest cadence · realized PNL + +### Row: Projections (rebuilt) +- **Rebuild the projection chart as a Trend panel** (`type: trend`): numeric + x-axis = **years from today** (0…`$horizon_years`), y = Low / Base / High / + Historical / "Base, no new contributions". The Trend panel renders smooth + multi-series lines on a numeric x — **independent of the dashboard time + range** — so it is ALWAYS visible (fixes empty-by-default). SQL: same FV math + as today, but emit `m.n/12.0 AS years_from_now` instead of a timestamp; format + `table`; panel `xField = years_from_now`. Carry over the dashed/dotted line + overrides + GBP unit. +- **Drop** the 3 projection-row stat cards (NW today / Historical return / + Monthly contribution) — already in Overview (return table + snapshot). **Keep** + the "How to view" text panel only if still useful (with Trend it's no longer + needed — drop it too). **Keep** the 5 template vars (rate_low/base/high, + monthly_contribution, horizon_years). + +## Panel count: 36 → ~17 +4 snapshot + returns table + nw-over-time + per-account + cash-vs-invested + +yearly-combo + monthly-contrib + per-account-ROI + net-pay(merged) + positions + +activity-log + meta-cadence + meta-pnl + projection-trend = **~17**. + +## Merge SQL notes (validate each against live wealth-pg before deploy) +- **Returns table**: 5 `SELECT`s (one per window) UNION ALL, each computing + `Δall = nw_now − nw_{ago}`, `Δmkt = Δall − (contrib_now − contrib_{ago})`, + `ret% = Δmkt / (nw_{ago} + 0.5·Δcontrib)·100` (Modified Dietz, the existing + formula). Window→interval: 1d/7d/30d/90d/12mo. +- **Yearly combo**: extend the "Annual change decomposition" query (already has + `contributions`, `market_gain` per year) to also emit `return_pct` (the + "Yearly investment return %" formula) — same `ep` CTE. +- **Net-pay `$grain`**: one query; `cumulative` = running sums, `yearly`/`monthly` + = period-end deltas (reuse the month-end/year-end delta pattern shipped today). + +## Build / deploy / verify +1. One-off Python builder (`/tmp`, outside repo) loads `wealth.json`: removes the + merged-away panels by title, adds the new merged panels + `$grain` var, + rebuilds the projection as a Trend panel, wraps everything in collapsed rows, + assigns unique ids + clean gridPos. Clone existing panels for schema-39 + fidelity where possible. +2. Validate: `json.load`; unique ids; spot-run every new/merged target's SQL + against live `wealth-pg` (the pg-sync sidecar) with default var values. +3. Deploy: `scripts/tg apply -target='module.monitoring.kubernetes_config_map.grafana_dashboards["wealth.json"]'` + (targeted — monitoring stack carries unrelated drift). `git rebase --autostash + forgejo/master` before push (shared repo). +4. Verify: ConfigMap == local file; user eyeballs each row in Grafana (esp. the + Trend projection renders without touching the time picker, and the returns + table + merged panels show the right numbers). + +## Risks +- **Trend panel** is flagged experimental (since v10.0) but available in v11.2; + confirm `xField` + query `format=table` at build time. +- **Bars + line on one timeseries** (yearly combo) needs per-series `drawStyle` + overrides + a second Y-axis override — verify rendering. +- **`$grain` net-pay** SQL is the fiddliest merge; validate all 3 grains. +- Reorganizing into rows reshuffles gridPos for the whole dashboard — the + builder must lay out rows top-to-bottom without overlaps. +- Keep the contribution-correctness fixes (LOCF view, month-end deltas) intact — + the merged panels read the same `dav_corrected` view. + +## Out of scope +- The `dav_corrected` view + the Fidelity growth-timing cosmetic (separate). +- No new metrics — pure consolidation.