Expense Categorizer
Classify each transaction in a bank/credit-card statement as Income, Saving, or Expenditure (with a sub-category), then report totals and a savings rate. Categorization is keyword/merchant-based: it looks at the transaction description and matches it against editable keyword lists — it does not require an LLM call per transaction, so it's fast and deterministic even on statements with hundreds of rows.
When to use this
Trigger on requests to analyze, categorize, or summarize personal transactions — whether from an uploaded file or pasted as text. Don't wait for the user to say "use expense-categorizer"; if they hand you a statement and ask what they spent or saved, this is the tool.
Workflow
Get the input. If the user attached/named a file, use it directly. If they pasted transactions as text instead, save them to a temporary CSV first with columns
date,description,debit,credit(ordate,description,amountwith signed values) before running the script — don't try to categorize by reading the pasted text yourself, the script's keyword matching is the source of truth and keeps results consistent.Run the script:
python scripts/categorize.py <input_file> --out <output_file>.csvIt handles
.csv,.xlsx/.xlsdirectly via pandas, and.pdfby extracting tables withpdfplumber(install withpip install pdfplumberif missing). It auto-detects the description column and either a debit/credit pair or a single signed amount column — if it can't find them, it raises an error naming the columns it did find, so you can tell the user what to check or rename.Many bank-generated PDF statements are password-protected. If the user gives you a password, pass it with
--password <pw>— don't write it to any file or log, just pass it as the CLI argument for that one run.Report the results. The script prints a summary (total income, total expenditure, total saving, savings rate, and a per-category breakdown) and writes the full categorized transaction list to the output CSV. Present the summary to the user directly, and mention where the categorized CSV was saved. Highlight anything that looks off (e.g. a large chunk landed in "Other" — see step 4).
Handle misses. Any expenditure that doesn't match a keyword falls into "Other". If a meaningful share of transactions end up there, look at the actual descriptions and suggest specific keyword additions to the user rather than guessing — then add them to
SAVING_KEYWORDS,INCOME_KEYWORDS, orEXPENDITURE_CATEGORIESinscripts/categorize.py(and mirror the change inreferences/categories.mdfor readability) so future runs pick them up. These lists are meant to evolve with the user's own bank's wording — don't hesitate to edit them.Categorization logic (for reference — already implemented in the script, only relevant if you need to explain or adjust behavior):
- A transaction matching a saving keyword (SIP, FD, RD, mutual fund, transfer to own savings/investment account, etc.) is Saving, regardless of whether it's a debit or credit.
- Any other credit (money in) is Income.
- Any other debit (money out) is Expenditure, sub-categorized by
matching
EXPENDITURE_CATEGORIESkeywords, defaulting to "Other".
Notes
- Statement formats vary a lot bank to bank. The column auto-detection covers common naming ("Narration", "Particulars", "Debit", "Withdrawal Amt", etc.) but if a statement uses unusual headers, rename the relevant columns in the source file (or tell the script author) rather than fighting the detector.
- PDF extraction quality depends on the statement's layout. If
pdfplumberfinds no tables, the PDF is likely scanned/image-based and needs OCR before this skill can help — tell the user that rather than guessing at numbers. - This categorizes based on description text, not amount thresholds — a ₹50,000 transfer and a ₹50 one are classified the same way if the description matches the same keyword.