A command-line tool to analyze and categorize monthly credit card expenses from multiple bank statements.
- Parse transaction files from multiple banks (Amex, CIBC, Scotia)
- Categorize transactions using keyword matching and LLM-based classification
- Generate PDF reports with spending summaries and 12-month trends
- Email reports via AWS SES
- Python 3.14+
- uv - Python package manager
- LM Studio - For running local LLM models (required for categorization)
# Clone the repository
git clone <repository-url>
cd expense-report
# Install dependencies
uv syncCopy the example environment file and fill in your values:
cp .env.example .envRequired environment variables:
| Variable | Description | Required |
|---|---|---|
LLM_BASE_URL |
LLM API endpoint (default: http://localhost:1234/v1) |
Optional |
LLM_API_KEY |
LLM API key (default: lm-studio) |
Optional |
GOOGLE_MAPS_API_KEY |
Google Maps API key for location-aware categorization | Optional |
SES_SMTP_USERNAME |
AWS SES SMTP username | For email only |
SES_SMTP_PASSWORD |
AWS SES SMTP password | For email only |
SES_FROM_EMAIL |
Sender email address (verified in SES) | For email only |
REPORT_RECIPIENTS |
Comma-separated list of recipient emails | For email only |
Tip: To use OpenAI instead of LM Studio, set:
LLM_BASE_URL=https://api.openai.com/v1 LLM_API_KEY=sk-your-openai-api-key
- Download and install LM Studio
- Download a model (recommended:
openai/gpt-oss-20b) - Start the local server:
- Go to the Developer tab
- Click Start Server (runs on
http://localhost:1234by default)
- The CLI will automatically connect to LM Studio for LLM-based categorization
Note: If
GOOGLE_MAPS_API_KEYis set, the LLM can use Google Maps MCP tools to look up merchant locations for more accurate categorization of ambiguous transactions.
docs/
├── sources/ # Raw transaction files from banks, append new data here (Bronze Layer)
│ ├── amex.csv
│ ├── cibc.csv
│ └── scotia.csv
├── intermediate/ # Parsed and categorized files (Silver Layer)
│ ├── transactions_YYYY-MM.csv
│ └── categorized_YYYY-MM.csv
├── output/ # Definitive monthly aggregates (Gold Layer)
│ └── monthly_YYYY-MM.csv
└── reports/ # Generated PDF reports
└── report_YYYY-MM.pdf
After downloading transaction files from your bank accounts:
- Download the CSV export from each bank's website
- Append new transactions to the existing source files:
# For Amex (skip header row from new file)
tail -n +2 ~/Downloads/new_amex_export.csv >> docs/sources/amex.csv
# For CIBC
cat ~/Downloads/new_cibc_export.csv >> docs/sources/cibc.csv
# For Scotia
tail -n +2 ~/Downloads/new_scotia_export.csv >> docs/sources/scotia.csvTip: Keep the original downloaded files as backups before appending.
All commands require a month parameter in YYYY-MM format:
Extract transactions from source files for a specific month:
uv run expense-report parse -m 2026-01Apply keyword and LLM-based categorization:
uv run expense-report categorize -m 2026-01Filter transactions and create definitive monthly aggregates (Gold Layer):
uv run expense-report aggregate -m 2026-01Create a PDF report with spending breakdown and trends:
uv run expense-report render-pdf -m 2026-01Email the report to configured recipients:
uv run expense-report send-email -m 2026-01
# Test without sending
uv run expense-report send-email -m 2026-01 --dryrunEven though we use AWS SES for sending emails, any SMTP server can be used by setting the environment variables.
Run all steps in sequence (parse → categorize → monthly-data → report → email):
uv run expense-report run -m 2026-01
# With email dry-run
uv run expense-report run -m 2026-01 --dryrunRun this workflow at the beginning of each month for the previous month's data:
- Download statements from your bank accounts (Amex, CIBC, Scotia)
- Append transactions to source files (see Adding New Transactions)
- Make sure LM Studio is running with a loaded model
- Run the full workflow:
uv run expense-report run -m YYYY-MM
- Review the report at
docs/reports/report_YYYY-MM.pdf
Edit src/expense-report/resources/categories.json to modify:
- Category names and descriptions
- Keyword matching rules
- Amount-based filtering for specific rules
uv run pytestMIT