6d224861 came from a --no-checkout worktree whose empty index made the
commit drop every file except two. This restores 05b50d2b's full tree and
correctly adds stacks/stem95su/gdrive-sync.tf + the service-catalog stem95su
entry. Forward-only (parent=6d224861, no force-push); [ci skip] since the
live infra was never applied from the broken commit.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
113 lines
6.5 KiB
Markdown
113 lines
6.5 KiB
Markdown
# 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.
|