# Internal transfer recognition in FinanceManager

Use this reference when implementing or repairing budget import/review flows where own-account transfers are being misclassified as expenses or income.

## Problem pattern

Bank imports can contain misleading merchant text that looks like spending, while the real business meaning is an internal transfer between the user's own accounts. Example class: Raiffeisen/TWINT/Swisslos text that should map to an AKB household-account transfer. These must never distort budget expenses, income, category analysis, or Budget vs Ist.

## Durable workflow

1. Start with read-only runtime inventory and schema inspection.
   - Count matching candidates only; do not print amounts in chat.
   - Search by description/source/account fields and by own-account pair heuristics.
   - Preserve the user's finance secrecy rule: no real amounts/IDs/API keys in chat.
2. Write RED tests first.
   - Recognition: source/account + description pattern becomes `classification = transfer_candidate`.
   - Budget neutrality: candidate is excluded from effective expenses, effective income, budget status, category analysis, fixed costs, and Budget vs Ist.
   - Review placement: candidate appears only in the internal-transfers review tab.
   - Confirm flow: confirm creates an internal transfer, not expense/income.
   - Matching: counterparty within a small date window and equal/similar opposite amount becomes matched; missing counterparty remains review-required.
   - Batch safety: normal expense batch confirm skips/blocks `transfer_candidate`.
   - Audit: classification and confirm decisions produce audit events.
3. Persist a reusable rule, not just a one-off data patch.
   - Store rule metadata in the existing rule table if available.
   - Include: rule name, source/account conditions, description aliases, active years, target action, suggested from/to accounts, budget impact, and review requirement.
4. Implement recognition metadata on candidates.
   - `classification = transfer_candidate`
   - `transaction_type = transfer`
   - `status = transfer_candidate` or `needs_review` depending on existing conventions.
   - no expense category; `proposed_category_id = null`
   - `budget_impact = neutral`
   - notes should carry `transfer.pair_status`, `from_account_name`, `to_account_name`, and `counterparty_missing` when unmatched.
5. Matching heuristic for own-account pairs.
   - Date tolerance: usually ±3 days.
   - Amount equal/similar with opposite direction.
   - Description aliases: source bank, target bank, TWINT, Swisslos, Übertrag/Uebertrag, own-account language, family/account-owner names.
   - On match, link both candidates and mark pair status `matched`.
   - On no match, keep candidate visible as unmatched transfer review; do not demote to expense/income.
6. UI requirements.
   - Use a clearly named `Interne Transfers` tab.
   - Display date, from account, suggested target account, description, amount, and matching status.
   - Provide actions: confirm as transfer, change target account, return to review, ignore.
   - Do not show transfer candidates in normal expense, income, category-review, or budget-spending queues.
7. Runtime correction.
   - Take a SQLite backup before changing runtime data.
   - Apply the rule via service code, not ad-hoc SQL when possible.
   - Write audit events for each corrected candidate.
   - Report only counts and yes/no statuses; never report amounts.
8. Verification checklist.
   - `python -m compileall -q src tests`
   - backend pytest
   - frontend Vitest
   - frontend build
   - source/build secret scans
   - `git diff --check`
   - browser sanity for review/internal transfers, effective expenses, budget status, and cash accounts
   - commit, push, remote hash verification

## Pitfalls

- Do not classify a misleading own-account movement as spending just because the merchant text looks like a purchase.
- Do not require the amount in the reusable rule unless the user explicitly asks; future transfers may vary.
- Do not auto-confirm unmatched transfer candidates. They should stay review-required with a clear counterparty-missing hint.
- Do not let transfer candidates enter normal expense batch confirm. That is how budget pollution sneaks in wearing a fake moustache.
- Existing tests that asserted transfers appear in the default candidate list may need updating: default review lists should exclude transfers; the internal-transfer tab should include them.
