A personal expense tracker built with Flask and SQLite. Upload your bank statement and Spendly automatically categorizes your transactions — using keyword rules first, then an OpenAI LLM as a fallback.
- Upload bank statements in
.xlsor.xlsxformat - Automatic transaction categorization (keyword-based + LLM fallback)
- Duplicate upload detection — re-uploading the same file is safe
- Dashboard with spend summary, category breakdown, and full transaction history
- Date range filtering
- Category management UI with one-click re-categorization
- User can edit categories according to them on UI.
Prerequisites: Python 3.12, a virtual environment tool, and an OpenAI API key.
# Clone and set up
git clone <repo-url>
cd ai-expense-tracker
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txtCreate a .env file in the project root:
OPENAI_API_KEY=sk-...
OPENAI_MODEL=gpt-4o-mini# Run the app
python app.pyOpen http://localhost:5003 and log in with:
| Password | |
|---|---|
demo@spendly.com |
demo123 |
Upload .xls / .xlsx
→ Parse narration into beneficiary + transaction note
→ Insert rows (duplicates silently skipped)
→ Keyword match against config/categories.json
→ LLM categorization for unmatched transactions
- Keyword matching — each category in
config/categories.jsonhas a list of keywords. If any keyword appears in the transaction note or beneficiary name, the category is assigned. - LLM fallback — uncategorized transactions are sent to OpenAI. Peer-to-peer transfers with transaction note "PAYMENT FROM PHONE" to a person's name or merchant are skipped automatically by LLM.
The parser expects standard Indian bank statement columns:
| Column | Description |
|---|---|
Date |
Transaction date |
Narration |
UPI narration or description |
Withdrawal Amt. |
Debit amount |
Deposit Amt. |
Credit amount |
UPI narrations (UPI-<beneficiary>@...-<note>) are automatically split into a beneficiary and a transaction note.
├── app.py # Flask app and routes
├── config/
│ └── categories.json # category → [keywords] mapping
├── database/
│ └── db.py # SQLite helpers (no ORM)
├── services/
│ ├── excel_parser.py # .xlsx and .xls parsing
│ └── categoriser.py # keyword + LLM categorization
├── templates/ # Jinja2 templates (extend base.html)
├── static/ # CSS and JS
├── data/
│ ├── app.db # SQLite database
│ └── uploads/ # Uploaded statement files
└── tests/
source .venv/bin/activate
pytest| Layer | Technology |
|---|---|
| Web framework | Flask |
| Database | SQLite (raw sqlite3) |
| Excel parsing | openpyxl, xlrd |
| LLM | OpenAI API |
| Auth | Werkzeug password hashing |
| Testing | pytest, pytest-flask |
Frontend inspired from CampusX YouTube channel