Skip to content

Flag transfers that only touch one Ledgr account - #156

Merged
KenTaniguchi-R merged 1 commit into
mainfrom
feat/transfer-review-queue
Sep 5, 2026
Merged

Flag transfers that only touch one Ledgr account#156
KenTaniguchi-R merged 1 commit into
mainfrom
feat/transfer-review-queue

Conversation

@KenTaniguchi-R

Copy link
Copy Markdown
Owner

Summary

  • Extends the existing (auto-pairing) transfer detector with a single-leg name/memo pattern pass: known card-payoff memos and named self-transfers are auto-excluded from spend/income (transferSource="pattern"); bare P2P processor names (Zelle, Venmo, Cash App, PayPal) go to a new review queue instead of auto-excluding (transferSource="suggested").
  • New review queue UI: a dashboard nudge (TransferReviewNudge) and a confirm/reject card dialog at /transactions?mode=review-transfers, modeled on the existing categorization review dialog but purpose-built (binary confirm/reject vs. picking a category).
  • New MCP write tool mark_transaction_transfer — closes a gap where an assistant session had no tool available to fix a misclassified transfer at all.
  • No DB migration: transferSource is a text column with a TS-level enum, so widening it is additive only (confirmed via drizzle-kit generate — no schema diff).

Test plan

  • pnpm typecheck — clean
  • pnpm lint — clean
  • pnpm test — 1118/1119 pass (1 pre-existing unrelated investment-queries date-rot failure on main)
  • New unit tests: src/lib/transfer-patterns.test.ts (pattern matcher, incl. property test)
  • New integration tests: tests/integration/transfer-detection.test.ts (single-leg pass: pattern/suggested/manual_rejected/idempotency/pair-preferred-over-pattern), tests/integration/transaction-detail.test.ts (confirm/reject actions + household isolation), tests/integration/transaction-queries.test.ts (getSuggestedTransfers/getSuggestedTransferCount)
  • Manually verified against the real local dev DB (not just testcontainers): ran applyTransferDetection against transactions shaped like this week's real ones — "Apple GS Savings Transfer" and "Applecard Gsbank Payment" auto-excluded immediately; "Zelle" landed in the suggested queue and correctly confirmed via confirmTransferSuggestionScoped; a bare personal name ("Bahar Rabiei", no processor keyword) correctly left untouched — documented as an intentional scope limit, not a bug.

🤖 Generated with Claude Code

The existing pairing-based detector can only match a transfer across two of
the household's own linked accounts, so a credit-card payoff to an
unconnected card, a savings transfer to an unconnected bank, or a P2P payment
never gets caught — it just sits in spending/income forever.

Adds a single-leg pattern pass to applyTransferDetection: known card-payoff
memos and named self-transfers (transfer + savings/brokerage/ira) are trusted
immediately (transferSource="pattern"); bare P2P processor names (Zelle,
Venmo, Cash App, PayPal) are too ambiguous to auto-exclude, so they go to a
new review queue (transferSource="suggested") instead — a dashboard nudge and
a confirm/reject card dialog at /transactions?mode=review-transfers. Also
exposes mark_transaction_transfer over MCP, closing the gap where an assistant
session had no tool to fix a misclassified transfer at all.
@KenTaniguchi-R
KenTaniguchi-R merged commit 4020ad0 into main Sep 5, 2026
4 of 5 checks passed
@KenTaniguchi-R
KenTaniguchi-R deleted the feat/transfer-review-queue branch September 5, 2026 03:56
KenTaniguchi-R added a commit that referenced this pull request Sep 5, 2026
…/income (#157)

Alpaca (and other Plaid-linked brokerage) fills and clearing fees were
landing in the Transactions tab miscategorized as "Investment Income"
and inflating spending/income totals across Transactions, Reports,
Budgets, and the Dashboard.

Reuses the existing isTransfer/transferSource mechanism from the
transfer-detection feature (#156): any transaction on an
account.type === "investment" account is deterministically tagged
transferSource="investment_account" at sync time, which every
spend/income aggregator already excludes via isTransfer=false. Rows
still appear in the Transactions list, labeled "Investment" instead of
the wrong category. A backfill job retags already-synced history
(pnpm backfill-investment-activity) and, like the transfer tiers,
never overwrites a manual/manual_rejected user correction.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant