docs: the wallet lane -- storage convention, pairing, lock, skips, replay
ci / lint-test (push) Successful in 48s
ci / lint-test (push) Successful in 48s
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -325,6 +325,42 @@ paid 2024/2025 as `VICROADS` and 2026 as `VICROADS ONLINE PAYM`, so the series
|
||||
splits and the 2025 estimate shows as `expected_not_seen`. Not fuzzy-matched on
|
||||
purpose — see the rejected pg_trgm merchant grouping.
|
||||
|
||||
### The wallet lane (`/api/wallet/ingest`, `lib/wallet-ingestion.ts`)
|
||||
|
||||
iOS notifications → `capture.wallet_events` → n8n **Wallet Interpret**
|
||||
(`CAp1s5cuzJk2mDaI`, local gemma, every 15 min) → this route. A row per tap,
|
||||
statementless, `source='wallet'`, `source_ref='evt:<id>'`; the statement later
|
||||
confirms it through the reconcile queue. Fixed 2026-10-03 after a month abroad
|
||||
(ticket #266); the rules below are each the fix for something that broke.
|
||||
|
||||
- **Storage convention differs from the order lane.** `amount` = `amount_aud` =
|
||||
a *provisional AUD* figure; the native figure is `foreign_currency_amount`.
|
||||
`NATIVE_CURRENCY` reads `amount_aud IS NOT NULL` as "amount is AUD" — without
|
||||
that an INR 524 tap displayed as "INR 9.42".
|
||||
- **Provisional rate:** ledger median over *statement* rows (≥5, carries the
|
||||
card's FX fee) → else ECB via Frankfurter (`rate_ecb`) → else `0` with
|
||||
`no_rate_reference`. Never the native figure as AUD (672006 VND once became
|
||||
A$672,006).
|
||||
- **One tap = two notifications** (issuer app + Wallet, 2–8 s apart), merged.
|
||||
A pair must be **≤10 minutes apart AND from different apps**
|
||||
(`expense_metadata.notified_at` / `notif_app`, migration 0039). The old
|
||||
amount-within-±1-day rule merged the UNICEF $50 into an unrelated $50 charge.
|
||||
- **The whole check→pair→adopt→insert runs under one advisory lock**
|
||||
(`hashtextextended('wallet-ingest',0)`), on the transaction's own connection.
|
||||
n8n posts a poll's items concurrently; without it 11 taps became 22 rows.
|
||||
Prisma cannot deserialise `void` — the lock SELECT needs `::text`.
|
||||
- **A merged half records `wallet_evt:<id>` in the survivor's flags**, so a
|
||||
replay is idempotent. Reconciled wallet rows carry **no** `expense_metadata`
|
||||
(reconcile moves it to the statement twin) — the flag check follows it there.
|
||||
- **Skipped, not booked:** account notices (`overdrawn`, `to avoid fees`, …) and
|
||||
transit-card drawdowns (`Your new/current balance is` — the Octopus *load* is
|
||||
the spend). The fixed-width descriptor parser runs on Westpac bodies only.
|
||||
- **Dated in Australia/Melbourne**, like the statements (UTC slicing filed 28 of
|
||||
134 rows a day early).
|
||||
- **Replay a few events by relabelling their interpretations**
|
||||
(`UPDATE capture.wallet_interpretations SET prompt_version='v1-<why>' WHERE …`),
|
||||
not by bumping `prompt_version`, which re-posts every event ever captured.
|
||||
|
||||
### Cash and reconciliation
|
||||
|
||||
`payment_method` in (`cash`, `credits`) excludes a transaction from
|
||||
|
||||
Reference in New Issue
Block a user