Stop losing receipts. Start understanding your money.
SpendSnap turns any receipt photo, UPI screenshot, or bank PDF into a searchable, categorized expense record β automatically.
| Input | β | Output |
|---|---|---|
| π· Receipt photo | OCR + regex parsing | { merchant, amount, date, category, source_type } |
| πΌοΈ UPI/bank screenshot | Filename heuristic + OCR | Labelled source_type: "screenshot" |
| π PDF bank statement | pdfplumber β Vision fallback | Full transaction history |
| π¬ Bank SMS text | Regex + normalisation | { merchant, amount, date, category, source_type: "sms" } |
Current Phase: Month 1 core pipeline β input β OCR/PDF extraction β parsed JSON β database.
spendsnap/
βββ Run_SpendSnap_Dashboard.bat # One-click Windows launcher
β
βββ backend/ # Python Β· FastAPI
β βββ main.py # API routes: upload, list, update, delete
β βββ ocr.py # Vision API + Mock OCR + PDF extraction + date parser
β βββ models.py # Receipt & Expense ORM (SQLAlchemy)
β βββ schemas.py # Pydantic request/response schemas
β βββ database.py # Session management (SQLite β Postgres-ready)
β βββ config.py # pydantic-settings + .env
β βββ index.html # Browser dashboard (served at /)
β βββ test_pipeline.py # Integration test suite
β βββ requirements.txt
β βββ sms_parsers/ # Bank SMS normalisation (HDFC, ICICI, SBI, Paytm)
β βββ data/
β βββ merchants.json # Indian merchant β category lookup (Phase 3)
β
βββ mobile/ # TypeScript Β· React Native Β· Expo SDK 56
βββ App.tsx # Camera upload, dark-mode feed, edit modal
βββ index.ts # Expo entry point
βββ app.json # Expo app config
βββ package.json
Data flow:
[Camera / Gallery / PDF]
β
βΌ
POST /api/upload
β
ββββββ΄βββββββββββββββββββββββββββββ
β Image? PDF? β
β Google Vision pdfplumber β
β (mock fallback) (Vision fallback for scanned)
ββββββ¬βββββββββββββββββββββββββββββ
β raw_text
βΌ
Regex Parser
merchant Β· amount Β· transaction_date (OCR-extracted)
β
βΌ
SQLite / Postgres
Receipt + Expense rows
(source_type Β· user_id stub Β· ai fields ready)
Double-click: Run_SpendSnap_Dashboard.bat
This will:
- Start the FastAPI backend in a minimised background window
- Open
http://127.0.0.1:8000in your default browser - Keep a control window open β press any key to stop the server
π‘ Test without real receipts: Upload any image named
swiggy,starbucks,fuel,amazon, orcanteento trigger realistic mock receipt templates. Name itphonepe_screenshot_...to test screenshot source detection.
cd backend
python -m venv venv
.\venv\Scripts\activate # Windows
# source venv/bin/activate # macOS / Linux
pip install -r requirements.txt
uvicorn main:app --reload --host 127.0.0.1 --port 8000Interactive API docs: http://127.0.0.1:8000/docs
cd mobile
npm install
npm run start| Key | Action |
|---|---|
a |
Open Android emulator |
i |
Open iOS simulator |
| Scan QR | Open in Expo Go on physical device |
cd backend
.\venv\Scripts\python test_pipeline.pyTests covered:
- β Health check (OCR mode, DB type, upload limit)
- β Oversized file rejection (HTTP 413)
- β Unsupported file type rejection (HTTP 415)
- β
Swiggy receipt upload β merchant, amount,
transaction_date,source_type - β
Screenshot
source_typedetection from filename - β Expense list ordering
- β
Expense update (including
source_typeandcategory) - β Expense delete + verify removal
Create backend/.env to override any default:
# ββ Database βββββββββββββββββββββββββββββββββββββββββββββββββ
# Default: local SQLite. Switch to Postgres for production.
DATABASE_URL=postgresql://user:password@localhost:5432/spendsnap
# ββ OCR ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# Omit to use Mock OCR (no credentials needed for local dev).
GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account.json
# ββ Uploads ββββββββββββββββββββββββββββββββββββββββββββββββββ
MAX_UPLOAD_SIZE_MB=10
# ββ CORS βββββββββββββββββββββββββββββββββββββββββββββββββββββ
# Add your production domain when deploying.
# Default allows localhost:8081 (Expo) and localhost:3000.
CORS_ORIGINS=["https://app.yourdomain.com","http://localhost:8081"]| Method | Endpoint | Description |
|---|---|---|
GET |
/ |
Browser dashboard UI |
GET |
/api/health |
Server status, OCR mode, upload limit |
POST |
/api/upload |
Upload image or PDF β returns parsed expense |
POST |
/api/expenses/sms |
Parse bank SMS text β returns parsed expense |
GET |
/api/expenses |
All expenses, ordered by receipt date |
PUT |
/api/expenses/{id} |
Correct any field (merchant, amount, category, source_type) |
DELETE |
/api/expenses/{id} |
Remove expense + deletes image file from disk |
Upload response shape:
{
"receipt": {
"id": 1,
"file_path": "backend/uploads/uuid.png",
"raw_text": "Swiggy Delivery Receipt\n...",
"created_at": "2026-06-20T13:42:00Z"
},
"expense": {
"id": 1,
"merchant": "Bundl Technologies Private Ltd",
"amount": 349.0,
"category": "Uncategorized",
"transaction_date": "2026-06-20T13:42:00",
"source_type": "photo",
"ai_suggested_category": null,
"category_confidence": null,
"user_id": null,
"created_at": "2026-06-21T10:00:00Z"
}
}Accepted file types: .jpg, .jpeg, .png, .webp, .pdf
Max upload size: 10 MB (configurable via MAX_UPLOAD_SIZE_MB)
| Column | Type | Description |
|---|---|---|
id |
int | Primary key |
receipt_id |
int FK | Linked receipt row |
merchant |
string | Extracted from first OCR line |
amount |
float | Largest/labelled total on receipt |
transaction_date |
datetime? | Date extracted from OCR text (not upload time) |
category |
string | "Uncategorized" until Phase 3 |
source_type |
string | photo | screenshot | pdf | sms |
ai_suggested_category |
string? | Populated by Claude in Phase 3 |
category_confidence |
float? | AI confidence score (0.0β1.0) |
user_id |
string? | Auth stub β wired up in Phase 2 |
created_at |
datetime | Upload timestamp |
| Phase | Weeks | Status | Milestone |
|---|---|---|---|
| 1 β Core Pipeline | 1β3 | β Done | Image/PDF β OCR β DB with full audit fields |
| 2 β Mobile Shell | 3β5 | π In progress | Camera picker, upload UX, user auth |
| 3 β Smart Features | 5β10 | π Planned | Claude AI categorisation, subscription detection, weekly summaries |
| 4 β Warranty Vault | 10β14 | π Planned | Product parsing, warranty tracking, push notifications |
Next up (Phase 2):
- Upload progress bar + offline queue for slow connections
- Auth via Supabase β wire
user_idto expense rows - Supabase Storage for receipt images (replace local
file_path) - Bank SMS normalisation layer (
sms_parsers/hdfc.py,sms_parsers/icici.py, β¦)
| Layer | Technology | Reason |
|---|---|---|
| Mobile | React Native + Expo SDK 56 | Single codebase, camera + file picker, TypeScript |
| Backend | FastAPI (Python) | Python-native OCR/AI libs, async I/O |
| OCR | Google Cloud Vision | Best Hindi/regional text and messy fonts |
| pdfplumber + PyMuPDF | Machine-readable first; renders scanned pages as fallback | |
| Database | SQLite β Postgres-ready | Zero-config dev, production-grade swap |
| ORM | SQLAlchemy 2.0 | Type-safe queries |
| Validation | Pydantic v2 | Schema enforcement, .env settings |
- Fork β branch off
main - Follow the phase order β don't build Phase 3 features before Phase 2 is solid
- Test with at least 5 real receipt images before opening a PR
- When you encounter a new Indian merchant name, add it to
backend/data/merchants.json
MIT Β· Built with β and too many Indian restaurant bills.