Status: accepted. Implemented 2026-08-24 in kevinvu184/up. Builds on ADR 0003 (silver), 0005 and 0009 (gold); first production use of ADR 0003's watermark-reset rebuild. Amended same day: dollar figures and counterparty specifics originally cited in this record were removed — the codebase is public and account specifics are personal data (ADR 0011 records where curation now lives).
Savings rate is net saved over income. The numerator already exists (gold_cashflow.net_cents); the denominator did not, and the obvious definition — positive, settled, transfer_account_id IS NULL — is wrong on real data: a large share of inflows carry transactionType: 'Transfer' but no transferAccount relationship (Up leaves many internal moves relationship-untagged), and much of the rest is the owner's own money arriving from external banks. Up's transactionType attribute, never promoted into silver, is the discriminator: the recurring salary is exactly transactionType: 'Salary', covering every month of the mirrored period, and a stray non-salary credit sharing the employer's description — which a description match would have miscounted — is excluded by type.
Migration 11 adds transaction_type to silver and the transform promotes attributes.transactionType verbatim (optional string, same strictness rules as the other optionals). What counts as income is interpretation, so it lives only in the gold_income SELECT in goldModels.ts, consistent with the two-homes rule from ADR 0005.
gold_income (day x income_type grain, per ADR 0009) counts settled, positive, non-transfer rows where transaction_type is Salary or Interest, plus ATO tax refunds — Direct Credits whose raw_text is 'ATO' (the wire-level payer identifier, present on every observed refund), surfaced as income_type 'Tax Refund'. Deliberately not income: person-to-person receipts (a reimbursement pattern), incoming international payments (gifts), and remaining Direct Credits (movements of own money). Widening the definition is one edit to the WHERE clause.
Existing silver rows gained the column by deleting silver_watermark and letting the cron rebuild from bronze — the mechanism ADR 0003 built for exactly this, used for the first time in production, completing within one budgeted run. Gold then needed its own forced rebuild (gold_last_run deleted), because the restored watermark matched the value gold had already built from — the skip check working correctly, if unexpectedly.
Savings rate is now a query: SUM(gold_cashflow.net_cents) / SUM(gold_income.income_cents) over the same day range, rolled up with the ADR 0009 bucketing patterns; the mart reconciled against silver to the cent on rebuild day. One known caveat moves to the numerator: net_cents counts own-money inflows from external accounts as positive net, so periods with large external transfers in overstate the rate; tightening the numerator is a future record if it matters. The income definition is curated to the account's observed data, which is the point — a remixer edits one WHERE clause, not a taxonomy.