feat(balance): per-security drill-down + latent gain (#216) #224

Merged
maximus merged 1 commit from issue-216-drilldown-gain into main 2026-06-10 01:07:57 +00:00

1 commit

Author SHA1 Message Date
le king fu
76ddad66c9 feat(balance): per-security drill-down + latent gain in accounts table (#216)
Surface unrealized (latent) gain on the existing balance surfaces, no new
visualization (decision 2026-06-04).

Service:
- AccountLatestSnapshot gains account `kind` + `latest_snapshot_line_id`, so
  the table knows which rows are detailed and where their holdings live.
- getAccountLatentGainByLine(lineId) folds a line's holdings through the
  existing computeUnrealizedGain (shares the book_cost=0 / NULL -> N/A guard).
- rollupLatentGain(accounts): pure aggregation by asset class, by envelope
  (vehicle_type, 'none' bucket), and grand total; per-bucket % denominator is
  the known book_cost only (null when none), mirroring the aggregate guard.

Hook (useBalanceOverview):
- Prefetches per-detailed-account latent gain in parallel (failures isolated),
  exposes latentGainByAccount + latentGainRollup. Simple accounts skipped.

UI:
- BalanceAccountsTable: expandable detailed rows -> per-security value + latent
  gain %; a latent-gain column (shown only when a detailed account has one);
  a summary block aggregating latent gain by asset class / envelope.
- BalanceOverviewCard: total latent gain line (hidden without detailed accounts).
- N/A rendered (never divide-by-zero) when book_cost is NULL or 0; partial-%
  flag when some positions lack a cost basis.

Native non-CAD currency display de-scoped this round (untestable while all
securities are CAD). Modified Dietz return columns (#204) unchanged.

i18n: balance.latentGain.* in FR + EN. Focused unit tests for
getAccountLatentGainByLine and rollupLatentGain (grouping, 'none' envelope,
null-% / unknown book_cost, empty).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 13:49:57 -04:00