wealthfolio: dav_corrected — also exclude Schwab synthetic cash flows

The Net-contribution chart was showing huge negative monthly swings
because broker-sync emits a synthetic cash-flow-match DEPOSIT for every
vest BUY and a WITHDRAWAL for every sell-to-cover SELL. Cumulatively
WITHDRAWALs ($1.06M) exceed DEPOSITs ($498k) — the user perceives this
as having "withdrawn" money even though they never moved cash out of
Schwab. The proceeds left for the bank and surface as real DEPOSITs on
the next account (IE/T212) that the user transfers them to.

Extend the dav_corrected view to subtract Schwab cash-flow-match flows
(DEPOSIT-positive, WITHDRAWAL-negative, account-scoped) in addition to
the existing Fidelity unrealised-gains-offset correction. InvestEngine
and Trading212 cash-flow-match entries are REAL deposits and must be
preserved — scope by Schwab account_id only.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Viktor Barzin 2026-05-26 22:52:17 +00:00
parent 30ba6860b9
commit 9b68dbc788

View file

@ -381,32 +381,59 @@ resource "kubernetes_deployment" "wealthfolio" {
total_cost_basis NUMERIC NOT NULL, total_cost_basis NUMERIC NOT NULL,
currency TEXT currency TEXT
); );
-- Drop-in replacement for daily_account_valuation that subtracts -- Drop-in replacement for daily_account_valuation. Net contribution
-- the cumulative pension gains-offset (DEPOSITs emitted by -- is corrected for two classes of "synthetic" flows that broker-sync
-- broker-sync Fidelity provider to reconcile WF totals with the -- emits to make Wealthfolio's bookkeeping balance, but which do NOT
-- PlanViewer reported pot). Wealthfolio's data model treats the -- represent real user contributions/withdrawals:
-- offset as a cash contribution, so without this correction --
-- net_contribution is inflated by the gain and growth shows £0 -- 1. Fidelity pension `unrealised-gains-offset` DEPOSITs emitted
-- for the entire pension. The view re-exports the corrected -- to reconcile WF totals with PlanViewer. Otherwise WF treats
-- value AS net_contribution so panels can use it as a drop-in -- the gain as a contribution, growth shows £0.
-- replacement for the base table. --
-- 2. Schwab RSU `cash-flow-match` DEPOSITs and WITHDRAWALs
-- emitted to pair each vest BUY with a cash DEPOSIT and each
-- sell-to-cover SELL with a cash WITHDRAWAL. The user never
-- transfers cash to Schwab (RSUs are compensation) and the
-- sell proceeds leave the account to bank (counted elsewhere
-- when redeposited to IE/T212). Without correction, Schwab
-- shows huge negative net_contribution because sell proceeds
-- exceed vest cost basis cumulatively.
--
-- Scope: the cash-flow-match filter targets ONLY the Schwab account
-- (account_id below). For InvestEngine / Trading212 the same note
-- pattern marks REAL user deposits, so they must be preserved.
CREATE OR REPLACE VIEW dav_corrected AS CREATE OR REPLACE VIEW dav_corrected AS
WITH all_offsets AS ( WITH synthetic_flows AS (
SELECT account_id, activity_date::date AS effective_date, amount -- Fidelity pension unrealised-gains-offsets (always DEPOSIT).
SELECT account_id,
activity_date::date AS effective_date,
COALESCE(amount, 0) AS synthetic_net
FROM activities FROM activities
WHERE notes LIKE 'fidelity-planviewer:unrealised-gains-offset%' WHERE notes LIKE 'fidelity-planviewer:unrealised-gains-offset%'
UNION ALL
-- Schwab RSU cash-flow-match (DEPOSIT positive, WITHDRAWAL negative).
SELECT account_id,
activity_date::date AS effective_date,
CASE
WHEN activity_type='DEPOSIT' THEN COALESCE(amount, 0)
WHEN activity_type='WITHDRAWAL' THEN -COALESCE(amount, 0)
ELSE 0
END AS synthetic_net
FROM activities
WHERE notes LIKE 'cash-flow-match:%'
AND account_id = '72d34e09-c1a6-41aa-99ea-abe3305ecc4a' -- Schwab
) )
SELECT SELECT
d.id, d.account_id, d.valuation_date, d.account_currency, d.id, d.account_id, d.valuation_date, d.account_currency,
d.base_currency, d.fx_rate_to_base, d.cash_balance, d.base_currency, d.fx_rate_to_base, d.cash_balance,
d.investment_market_value, d.total_value, d.cost_basis, d.investment_market_value, d.total_value, d.cost_basis,
d.net_contribution AS net_contribution_raw, d.net_contribution AS net_contribution_raw,
(d.net_contribution - COALESCE(SUM(o.amount), 0)) AS net_contribution, (d.net_contribution - COALESCE(SUM(s.synthetic_net), 0)) AS net_contribution,
COALESCE(SUM(o.amount), 0) AS pension_gains_offset COALESCE(SUM(s.synthetic_net), 0) AS synthetic_adjustment
FROM daily_account_valuation d FROM daily_account_valuation d
LEFT JOIN all_offsets o LEFT JOIN synthetic_flows s
ON o.account_id = d.account_id ON s.account_id = d.account_id
AND o.effective_date <= d.valuation_date AND s.effective_date <= d.valuation_date
GROUP BY d.id, d.account_id, d.valuation_date, d.account_currency, GROUP BY d.id, d.account_id, d.valuation_date, d.account_currency,
d.base_currency, d.fx_rate_to_base, d.cash_balance, d.base_currency, d.fx_rate_to_base, d.cash_balance,
d.investment_market_value, d.total_value, d.cost_basis, d.investment_market_value, d.total_value, d.cost_basis,