docs: what importing a BNPL account changes, and the Zip data decisions
ci / lint-test (push) Successful in 41s

24 Zip statements landed (Dec 2020 - Jun 2026), all balance-asserting at
$0.00. Zip is a credit account, so three existing row types had to be
re-read. Data changes applied via transaction_overrides; this records why.

A ZIPPAY debit leaving the bank duplicates the Zip statement's purchases
only where a Zip statement covers that period. 2 of 8 do, and are now
transfers ($125.97). The other six stay spend: for those periods the bank
debit is the only record the purchase happened, so moving them would have
deleted $703.35 of real spend.

'Account Credit' is cashback, confirmed by the owner and consistent with
Zip labelling its other money-in types explicitly (Payment = repayment,
'Refund <merchant>' = merchant refund). 34 rows, $740, round amounts, no
matching bank debit. Gemini split the identical description 23 'other'
(subtracts from spend) / 11 'transfers' (excluded), so one thing did two
opposite things. Now all 34 shopping+credit, netting against spend the
way refunds already do. Income was rejected for the reason the Up item
sales note gives.

Prezzee gift cards ($1,580) stay spend — stored value only nets out if
every downstream purchase is logged, and for 2020 it is not. Same
reasoning as the ATM-withdrawal rule.

31 uncategorised debits mapped by merchant with merchant_normalized set
too, since the raw descriptions carry card numbers and order UUIDs. All
137 Zip rows are now categorised; $11,135.11 net spend, $10,085.11
transfers.

Flags statement 200 (Sept 2021): extracted without billing_end_date or
balances, so it is the one statement uq_statement_identity cannot cover
(the index is partial on billing_end_date IS NOT NULL) and a re-import
would duplicate it.
This commit is contained in:
2026-08-15 19:11:50 +10:00
parent 2cfaf8e8c8
commit 4f307e9517
+38
View File
@@ -685,6 +685,44 @@ in a review table. Consents expire annually, so expect it.
Undo an import: `DELETE FROM transactions WHERE source = '<source>'`.
### Zip, and the three things a BNPL account needs (2026-08-15)
24 statements, Dec 2020 → Jun 2026, all balance-asserting at $0.00. Zip is a
credit account, so importing it changes how three existing row types must read.
**1. A repayment leaving the bank is a duplicate ONLY where a Zip statement
covers the purchases.** Zip's own `Payment` rows are already `transfers`. On the
bank side, `ZIPPAY*` debits are the same money — but only 2 of 8 fall inside a
Zip statement period. Those two are now `transfers`; **the other six must stay as
spend**, because for those periods the bank debit is the only record the purchase
ever happened. Moving all eight would have deleted $703.35 of real spend. Same
statement-coverage rule as the CSV importer, and the same trap.
**2. `Account Credit` is cashback, and Zip labels its three money-in types
differently.** `Payment` = repayment from the bank. `Refund <merchant>` = a
genuine merchant refund. `Account Credit` = Zip cashback — 34 rows, $740, round
amounts ($20 ×20, $30 ×10, $10 ×4), with no matching debit from any bank account.
Gemini filed the identical description two ways — 23 as `other` (which *subtracts*
from spend) and 11 as `transfers` (excluded entirely) — so one thing was doing two
opposite things. All 34 are now `shopping` + `credit`, **netting against spend**:
cashback lowers what a purchase actually cost, which is how `NET_SPEND_ROWS` /
`SPEND_SIGNED` already treat a refund. Filing it as `income` was rejected for the
reason the Up item sales note gives — it flatters `net = income spent` and mixes
non-earnings into the salary line. Ties to the **Cashback tracking (ING-6)** epic;
a dedicated `cashback` category would need the CHECK constraint, the TS mirror,
and a decision in every analytics fragment, so it was not done here.
**3. A gift card stays spend.** Prezzee is $1,580 of the history. Stored value is
tempting to call `transfers`, but that only works if every downstream purchase is
logged — it is not, for 2020. Identical reasoning to the ATM-withdrawal rule.
All 137 Zip rows are categorised; net spend is $11,135.11 against $10,085.11 of
`transfers`. **Statement 200 (Sept 2021) came through incomplete** — no
`billing_end_date`, no balances, 2 rows — so it is the one statement not covered
by `uq_statement_identity` (the index is partial on `billing_end_date IS NOT
NULL`) and a re-import would duplicate it. Worth re-processing paperless doc 296.
### Trusting extracted statement data
**Balance assertions are the check that works.** `getStatements` computes