expense-log
Purpose
Parse bank CSV or SMS exports into a local SQLite ledger, auto-categorise transactions using keyword rules, and generate monthly spend reports — all without any cloud service. Financial data never leaves your machine.
Supported input formats:
- Generic bank CSV (date, description, amount, balance)
- SMS export from Android (
.xmlor.txtfrom SMS Backup & Restore) - Custom format adapters configurable via
~/.expense-log/adapters.json
Runbook
Pre-flight — verify
python3andsqlite3are available.
EnsureINPUT_DIRpoints to the folder where your bank exports are dropped.
EnsureREPORT_DIRis writable for HTML/Markdown report output.
On first run,scripts/import-csv.shcreates~/.expense-log/ledger.db.Import — run
scripts/import-csv.sh [--file <path>|--dir <INPUT_DIR>]:- Auto-detects the file type (CSV or SMS XML)
- Deduplicates rows using a hash of
(date, description, amount) - Inserts new transactions into the
transactionstable withcategory = NULL - Prints an import summary:
N new / M duplicates skipped
Categorise — run
scripts/categorize.sh:- Reads
~/.expense-log/rules.json(keyword → category mapping) - Updates
categoryfor all uncategorised rows viaLIKEmatching ondescription - Prints any transactions still uncategorised (review manually)
- You may add rules interactively or by editing
rules.jsondirectly
- Reads
Report — run
scripts/report.sh [--month YYYY-MM]:- Defaults to the current calendar month
- Groups spend by category and prints a ranked table
- Flags the highest spend category with ⚠️ if it exceeds 40% of total
- Outputs a Markdown file to
REPORT_DIR/expense_<YYYY-MM>.md
Trend —
scripts/report.sh --trend 3prints a 3-month comparison table side by side, showing category-level month-over-month delta.
Stop conditions
- Abort if
python3is missing — SQLite operations use the stdlibsqlite3module. - Never delete or modify source bank files in
INPUT_DIR. - Do not execute any write to
ledger.dbwithout first taking a backup copy to~/.expense-log/backups/ledger_<timestamp>.db. - Do not transmit financial data to any external service or API.
- Abort for
nullclawvariant — no shell execution.
Output format
Transaction row (transactions table)
id | date | description | amount | category
1 | 2026-04-10 | SWIGGY ORDER #8823421 | -450.00 | food_delivery
2 | 2026-04-12 | SALARY CREDIT | 75000 | income
Monthly report (stdout + Markdown)
💰 expense-log — April 2026
Category | Spent (₹) | % of total | vs Mar
------------------|-----------|-----------|---------
Food & Delivery | 8,450 | 28.2% | +12% ⚠️
Rent | 15,000 | 50.0% | —
Transport | 2,100 | 7.0% | -5%
Entertainment | 1,800 | 6.0% | +40%
Uncategorised | 1,650 | 5.5% | review
Total spend : 29,000 ₹
Income (estimated): 75,000 ₹
Net savings : 46,000 ₹ (61.3%)
Example invocations
scripts/import-csv.sh --file apr_statement.csv— import April statementscripts/categorize.sh— auto-categorise uncategorised rowsscripts/report.sh --month 2026-04— generate April expense reportscripts/report.sh --trend 3— show 3-month category trend- "Import my latest bank CSV and show this month's spend by category."
- "How much did I spend on food last month?"