scripts/recategorize-plan.md
Rama main · réplica de lectura del repositorio original.
# Recategorize plan
Goal: every transaction carries the category of what was bought, every rule is one
the data supports, and new rows after each sync land the same way.
## Signals, strongest first (per non-manual row)
1. **Hold list** - rows the owner left for review (Prosper, RR Bowker): untouched.
2. **User rules** (`rules.source != 'llm'`) - the owner's intent. Applied as written.
3. **Order receipts** (`lib/orders.ts`) - Amazon order/shipment emails (Gmail via
`gogx`, read-only) or an Amazon "Your Orders" CSV in `data/`. A card charge
matches a receipt by exact total and a date window. The receipt's Amazon
category label ("Electronics", "Snack Foods") or item names decide the category.
4. **Descriptor rules** (`DESCRIPTOR_RULES` in `lib/recategorize.ts`) - code rules
on the raw bank descriptor for multi-category merchants and known facts:
Amazon Tips / Grocery Subscription -> Groceries, Kindle -> Subscriptions,
Amazon Media EU / Amazon.com Services payments (credits) -> Income (book
royalties), Apple.com/bill -> Subscriptions, Uber trips vs Uber One, Walmart+
vs Walmart, Wawa fuel vs store, Costco gas, student/auto loans -> Loans,
JPMorgan Chase Ext Trnsfr -> Loans (owner: a real payment out), cleaner
payments -> Home.
5. **Single-category LLM rules** that survive the audit.
6. **LLM** (fast lane, batches of 20, sequential) with name, merchant, Plaid
detailed, amount, account type. Writes category, a clean display merchant
when Plaid gave none, and confidence. Below 0.6 -> "Other" (To-Review).
7. Fallback: `mapPfc(primary, detailed)`.
Never written: `category_source='manual'` rows. Instead `flagManualPicks()` lists
manual picks whose category disagrees with signals 3-4 or with a strong Plaid
detailed category.
## Taxonomy additions (counts measured on the 2026-10-18 copy)
Electronics, Household, Clothing, Pets, Personal Care, Loans. Each is justified
by the dry-run counts in the report; prompts and `mapPfc` read `CATEGORIES`.
## Rules audit (`scripts/audit-rules.mts`)
- Delete LLM rules on multi-category merchants (Amazon, Apple, Walmart, Costco,
Uber, Target, Google, Wawa) - per-row signals replace them.
- Delete LLM rules whose substring is too broad (matches rows of 2+ categories
in the data, or a pattern under 4 chars).
- Delete LLM rules duplicated or contradicted by a user rule.
- Add LLM rules only for merchants whose rows (after the pass) are 100% one
category with 3+ rows.
- Manual rules are never deleted; suspicious ones are printed.
## Moving forward
`classifyNewTransactions({ llm })` after each sync: refresh receipts (best
effort), then run signals 3-7 on rows still `category_source='plaid'`, capped.
Never throws, never touches manual rows.
## Evidence
`scripts/recategorize-all.mts` (dry-run default, `--apply`): before/after per
category, 40 sample changes, flagged manual picks. `scripts/verify-recategorize.mts`
asserts key cases on a throwaway db.