Live Link: https://ak-project-caace.web.app
BankDigitizer unlocks password-protected Indian bank PDFs, extracts every transaction, categorizes them automatically, and presents them in a filterable, exportable dashboard — fully serverless.
🧩 Problem · 💡 Solution · ✨ Features · 🏗 Architecture · 🔄 Flowcharts · 🚀 Getting Started · 📁 Structure · 🗺 Roadmap
Upload a password-protected bank PDF → get clean, categorized, searchable transactions in seconds.
Every Indian bank delivers your account statement as a password-protected PDF — a format that's completely impossible to search, filter, or use in any budgeting tool.
# What you get from your bank:
statement_jan_2024.pdf ← password locked, unreadable by any tool
# What you actually need:
{ date, description, debit, credit, balance, category }[]
← structured, searchable, filterable, exportable| Pain Point | Impact |
|---|---|
| 🔐 Password-protected PDFs | No tool can read them without manual intervention |
| 🏦 Different format per bank | SBI, HDFC, ICICI all have unique column layouts |
| 📅 Inconsistent date formats | DD/MM/YYYY vs DD-MMM-YYYY vs DD-MM-YYYY |
| 🏷️ No categorization | Raw descriptions like "UPI-ZOMATO-123456" are unreadable |
| 📤 No export options | Stuck inside the bank's portal forever |
| 🔄 Duplicate uploads | Re-uploading the same statement creates duplicate records |
Bank Statement Digitizer is a full-stack serverless application that handles the entire pipeline from encrypted PDF to clean data:
You upload a PDF + password
↓
Cloud Function unlocks it in-memory (password never stored)
↓
Layout-aware parser extracts every transaction row
↓
Auto-categorization: Salary / Food / Rent / Transport / ATM...
↓
SHA-256 deduplication — re-uploads are silently safe
↓
Firestore stores clean structured data, scoped to your account
↓
Filter · Search · Analyze · Export to Excel
No third-party OCR. No external APIs. Entirely within your Firebase project.
|
pdfjs-dist decrypts bank PDFs entirely in-memory inside the Cloud Function. The password travels over TLS and is immediately discarded — never logged, never stored. |
Dedicated layout-aware parsers for SBI, HDFC, ICICI, and Axis Bank. Auto-detects which bank from the PDF text — no manual selection needed. |
|
10 categories assigned automatically by keyword matching on the description: Salary, Food, Rent, Utility, Shopping, Transport, ATM, Investment, Health, Transfer. |
Each transaction's Firestore document ID is a SHA-256 hash of userId + date + description + amount. Re-uploading the same statement is completely safe — no duplicates. |
|
Filter by date range, debit/credit type, category, amount range, and description search. All filters sync to URL — shareable and refresh-safe. |
monthly income vs expense line chart, top merchants, and auto-generated financial insights — all from real transaction data. |
|
Download all currently filtered transactions as a formatted |
System preference detected on first load. Toggle persisted in localStorage. Full dark-first design with |
┌──────────────────────────────────────────────────────────────────────────┐
│ CLIENT BROWSER │
│ │
│ /login /signup /dashboard /upload /transactions │
│ /statements /analytics /profile │
│ │
│ ┌───────────────────────────────────────────────────────────────────┐ │
│ │ React 18 + Vite + Tailwind v3 + react-router-dom v7 │ │
│ │ │ │
│ │ Pages Components Hooks │ │
│ │ • Dashboard • Navbar • useAuth │ │
│ │ • Upload • FilterBar • useUpload │ │
│ │ • Transactions • FilterChips • useTransactions │ │
│ │ • Statements • TransactionTable • useFilterParams │ │
│ │ • Analytics • TransactionModal • useTheme │ │
│ │ • Profile • ExportButton │ │
│ │ • Login/Signup • StatementSelector │ │
│ └───────────────────────────────────────────────────────────────────┘ │
└─────────────────────────┬────────────────────────────────────────────────┘
│
Firebase SDK v10
httpsCallable · uploadBytesResumable
onAuthStateChanged · Firestore SDK
│
┌─────────────────────────▼────────────────────────────────────────────────┐
│ FIREBASE CLOUD FUNCTIONS (Node 18) │
│ │
│ ┌─────────────────────┐ ┌──────────────────────────────────────┐ │
│ │ processStatement() │ │ listTransactions() │ │
│ │ │ │ │ │
│ │ ① Auth check │ │ ① Auth check │ │
│ │ ② Storage download │ │ ② Firestore query │ │
│ │ ③ unlockPdf() │ │ userId + statementId + date │ │
│ │ ④ detectScanned() │ │ type — server-side │ │
│ │ ⑤ detectBank() │ │ ③ Amount / search / category │ │
│ │ ⑥ parser.parse() │ │ — in-memory filter │ │
│ │ ⑦ normalize() │ │ ④ Paginate → return │ │
│ │ ⑧ categorize() │ └──────────────────────────────────────┘ │
│ │ ⑨ hash + batch write│ │
│ └─────────────────────┘ ┌──────────────────────────────────────┐ │
│ │ listStatements() │ │
│ Structured logging: │ userId filter + uploadedAt sort │ │
│ uploadId · userId(masked) └──────────────────────────────────────┘ │
│ bank · txCount · errorType · durationMs │
└──────────────┬───────────────────────────────┬───────────────────────────┘
│ │
┌──────────▼──────────┐ ┌──────────▼──────────┐
│ Firebase Storage │ │ Cloud Firestore │
│ │ │ │
│ statements/ │ │ /users/{uid} │
│ {uid}/ │ │ /statements/{id} │
│ {uploadId}.pdf │ │ /transactions/{hash}│
└─────────────────────┘ └──────────────────────┘
What happens when a user uploads a bank PDF
flowchart TD
A([👤 User]) --> B[Drop PDF + Enter Password]
B --> C{Valid PDF?\nMax 20MB?}
C -- ❌ No --> D[Show Validation Error]
D --> B
C -- ✅ Yes --> E[uploadBytesResumable\nFirebase Storage]
E --> F[Progress Bar 0 → 100%]
F --> G[httpsCallable\nprocessStatement]
G --> H[bucket.file.download\nGet PDF Buffer]
H --> I[unlockPdf\npdfjs-dist decrypt]
I --> J{Password\nCorrect?}
J -- ❌ No --> J1[Throw WRONG_PASSWORD]
J -- ✅ Yes --> K[detectScanned\nCheck text density]
K --> L{All pages\nscanned?}
L -- ⚠️ Yes --> L1[Throw SCANNED_PDF]
L -- ✅ No --> M[extractAllRows\nY-coord grouping]
M --> N[detectBank\nKeyword match]
N --> O{Bank\nDetected?}
O -- ❌ No --> O1[Throw UNSUPPORTED_BANK]
O -- ✅ Yes --> P[parser.parse\nSBI / HDFC / ICICI / Axis]
P --> Q[normalize\nValidate + clean rows]
Q --> R[categorizeAll\n10 keyword categories]
R --> S[hashTransaction\nSHA-256 dedup ID]
S --> T[writeStatement\nCreate statement doc]
T --> U[writeTransactions\nBatch 500 / commit]
U --> V[✅ Success Toast\nbank + txCount]
style A fill:#6366f1,color:#fff
style V fill:#22c55e,color:#fff
style J1 fill:#ef4444,color:#fff
style L1 fill:#ef4444,color:#fff
style O1 fill:#ef4444,color:#fff
style D fill:#ef4444,color:#fff
How raw PDF bytes become clean structured transactions
flowchart LR
A([PDF Buffer]) --> B[unlockPdf\npdfjs getDocument]
B --> C[detectScanned\nchars per page]
C --> D[extractFullText\nall pages concat]
D --> E{detectBank\nkeyword rules}
E --> E1[SBI\nState Bank of India]
E --> E2[HDFC\nHDFC BANK]
E --> E3[ICICI\nICICI Bank]
E --> E4[AXIS\nAxis Bank]
E1 & E2 & E3 & E4 --> F[extractAllRows\ngetTextContent per page]
F --> G[groupItemsIntoRows\nY-coord ±3px tolerance\nSort by X]
G --> H[parser.parse\nFind header row\nSkip summary rows\nParse amounts]
H --> I[normalizeTransactions\nValidate date YYYY-MM-DD\nClamp negatives\nFilter zero amounts]
I --> J[categorizeAll\nkeyword → category]
J --> K[hashTransaction\nSHA-256 16-char ID]
K --> L([Clean Transaction Array])
style A fill:#3b82f6,color:#fff
style L fill:#22c55e,color:#fff
How secure login and route protection works
sequenceDiagram
actor User
participant UI as ⚛️ React Frontend
participant Auth as 🔐 Firebase Auth
participant DB as 🗄 Firestore
participant Route as 🛡 ProtectedRoute
User->>UI: Visit /dashboard
UI->>Route: Check auth state
Route->>Auth: onAuthStateChanged()
Auth-->>Route: user = null (not logged in)
Route-->>UI: Navigate to /login
User->>UI: Enter email + password
UI->>Auth: signInWithEmailAndPassword()
Auth-->>UI: UserCredential + JWT
UI->>UI: Store user in state
UI-->>User: Redirect to /dashboard
Note over UI,DB: On Signup only
UI->>DB: setDoc /users/{uid}
DB-->>UI: User doc created
User->>UI: Visit any protected route
UI->>Route: Check useAuth()
Route->>Auth: onAuthStateChanged()
Auth-->>Route: user = UserObject
Route-->>UI: Render children ✅
How the transaction filter system works end-to-end
flowchart TD
A[User Sets Filters\ndate · type · category\nsearch · amount · statement] --> B[useFilterParams\nSync to URL params]
B --> C[useTransactions\nBuild params object]
C --> D[httpsCallable\nlistTransactions]
D --> E{Firestore\nServer-side}
E --> E1[.where userId == uid]
E --> E2[.where statementId]
E --> E3[.where date >= from]
E --> E4[.where date <= to]
E --> E5[.where type == debit/credit]
E --> E6[.orderBy date desc]
E1 & E2 & E3 & E4 & E5 & E6 --> F{In-memory\nFilters}
F --> F1[amount min/max]
F --> F2[description search]
F --> F3[category match]
F1 & F2 & F3 --> G[Paginate\nslice offset to offset+size]
G --> H[Return\ndata · total · page · totalPages]
H --> I[TransactionTable\nRender rows + skeleton]
H --> J[FilterChips\nShow active filters]
H --> K[ExportButton\nfetch all 1000 → xlsx]
style A fill:#6366f1,color:#fff
Complete journey of data through the BankDigitizer system
flowchart LR
subgraph Browser ["⚛️ React Frontend"]
U[User\nPDF + Password] --> UP[Upload Hook\nuseUpload]
UP --> FE[Filter Hook\nuseFilterParams]
end
subgraph Functions ["☁️ Cloud Functions"]
UP --> PS[processStatement\nAuth → Decrypt → Parse]
FE --> LT[listTransactions\nQuery → Filter → Paginate]
end
subgraph Storage ["🗄 Firebase Storage"]
PS <--> ST[statements/\nuserid/uploadId.pdf]
end
subgraph Firestore ["🔥 Firestore"]
PS --> WS[(statements\ncollection)]
PS --> WT[(transactions\ncollection)]
LT <--> WT
LT <--> WS
end
subgraph UI ["📊 Dashboard"]
LT --> TT[TransactionTable]
LT --> AN[Analytics Charts]
LT --> EX[Excel Export]
end
Every bank's raw rows get normalized to this single shape:
{
"transactionId": "a1b2c3d4e5f6a7b8",
"statementId": "firestore-auto-id",
"userId": "firebase-auth-uid",
"date": "2024-01-15",
"description": "UPI-ZOMATO-Food delivery",
"debit": 450.00,
"credit": 0,
"balance": 38050.00,
"type": "debit",
"category": "Food",
"createdAt": "Firestore Timestamp"
}| Bank | Source Format | Example | Normalized |
|---|---|---|---|
| SBI | DD/MM/YYYY | 15/01/2024 | 2024-01-15 |
| HDFC | DD-MMM-YYYY | 15-Jan-2024 | 2024-01-15 |
| HDFC | DD/MM/YY | 15/01/24 | 2024-01-15 |
| ICICI | DD-MMM-YYYY | 15-Jan-2024 | 2024-01-15 |
| Axis | DD-MM-YYYY | 15-01-2024 | 2024-01-15 |
firestore-root/
│
├── users/{uid}
│ ├── uid : string
│ ├── email : string
│ └── createdAt : timestamp
│
├── statements/{statementId}
│ ├── statementId : string (= doc ID)
│ ├── userId : string
│ ├── bankName : SBI | HDFC | ICICI | AXIS
│ ├── fileName : string
│ ├── storagePath : string
│ ├── transactionCount : number
│ ├── uploadedAt : timestamp
│ └── status : pending | processing | done
│
└── transactions/{hash} ← SHA-256(userId+date+description+amount)
├── transactionId : string (= doc ID)
├── statementId : string
├── userId : string
├── date : string (YYYY-MM-DD)
├── description : string
├── debit : number
├── credit : number
├── balance : number
├── type : debit | credit
├── category : Salary|Food|Rent|Utility|Shopping|
│ Transport|ATM|Investment|Health|Transfer|Other
└── createdAt : timestamp
transactions: userId ASC + date DESC
transactions: userId ASC + type ASC + date DESC
transactions: userId ASC + statementId ASC + date DESC
statements: userId ASC + uploadedAt DESC
✅ /users/{uid} → read/write only if auth.uid == uid
✅ /statements/{id} → read/write only if .userId == auth.uid
✅ /transactions/{hash} → read/write only if .userId == auth.uid
❌ Cross-user access → denied at rule level, not app level
❌ Unauthenticated → denied
✅ statements/{uid}/** → read/write only if auth.uid == uid
❌ Other user paths → denied
Browser ──(HTTPS/TLS)──▶ Cloud Function RAM ──▶ pdfjs-dist ──▶ discarded
❌ Never written to Cloud Logging
❌ Never stored in Firestore
❌ Never saved to Firebase Storage
❌ Never in function response body
| Technology | Version | Purpose |
|---|---|---|
| React.js | 18.x | Component-based UI with hooks |
| Vite | 5.x | Fast HMR dev server + bundler |
| Tailwind CSS | 3.x | Utility-first dark-first styling |
| react-router-dom | v7 | SPA routing + ProtectedRoute |
| lucide-react | 0.383 | Tree-shakeable SVG icon set |
| xlsx (SheetJS) | — | Client-side .xlsx generation |
| recharts | — | Bar + line charts for Analytics |
| Technology | Version | Purpose |
|---|---|---|
| Firebase Cloud Functions | v2 | Serverless Node 18 backend |
| Cloud Firestore | — | Per-user document database |
| Firebase Storage | — | GCS-backed PDF file storage |
| Firebase Auth | — | Email/password authentication |
| pdfjs-dist (legacy) | 3.11 | In-memory PDF decrypt + extract |
| crypto (built-in) | — | SHA-256 deduplication hashing |
| Technology | Purpose |
|---|---|
| Vitest | Unit tests for all 4 bank parsers |
| Fixture files | Row dump fixtures per bank format |
| CLI test scripts | testPdf · testParser · testUnlock |
bank-statement-digitizer/
│
├── 📂 Admin/ # React 18 frontend
│ └── 📂 src/
│ │
│ ├── 📂 pages/
│ │ ├── Login.jsx # Email/password auth form
│ │ ├── Signup.jsx # Account creation + Firestore user doc
│ │ ├── Dashboard.jsx # Summary stats + recent 6 transactions
│ │ ├── Upload.jsx # Drag-drop + password + progress bar
│ │ ├── Transactions.jsx # Table + all filters + export button
│ │ ├── Statements.jsx # All statements + bank badge + View btn
│ │ ├── Analytics.jsx # Category bar + monthly line + insights
│ │ └── Profile.jsx # Email · joined date · change password
│ │
│ ├── 📂 components/
│ │ ├── Navbar.jsx # DM Mono design · clock · dark/light
│ │ ├── ProtectedRoute.jsx # Auth guard → redirect /login
│ │ ├── FilterBar.jsx # Date/amount/type/category/search
│ │ ├── FilterChips.jsx # Active filter pills with X dismiss
│ │ ├── TransactionTable.jsx # Sticky header · skeleton · pagination
│ │ ├── TransactionModal.jsx # Row click → detail modal + copy btn
│ │ ├── StatementSelector.jsx # Dropdown filter by statement
│ │ ├── ExportButton.jsx # Fetch all filtered → .xlsx download
│ │ ├── Toast.jsx # Success / error / info notifications
│ │ └── ProgressBar.jsx # Upload → Process → Done stepper
│ │
│ ├── 📂 hooks/
│ │ ├── useAuth.js # onAuthStateChanged wrapper
│ │ ├── useUpload.js # Storage upload + function call
│ │ ├── useTransactions.js # listTransactions + validation
│ │ ├── useFilterParams.js # URL ↔ filter state sync
│ │ └── useTheme.js # Dark/light toggle + localStorage
│ │
│ ├── 📂 routes/
│ │ └── AppRoutes.jsx # All routes with ProtectedRoute
│ │
│ └── firebase.js # SDK init: auth · db · storage · fns
│
├── 📂 functions/ # Firebase Cloud Functions
│ │
│ ├── 📂 parsers/
│ │ ├── index.js # parseStatement() dispatcher
│ │ ├── sbi.js # DD/MM/YYYY · Dr/Cr suffix
│ │ ├── hdfc.js # DD-MMM-YYYY · 7-col layout
│ │ ├── icici.js # S.No detection · DD-MMM-YYYY
│ │ └── axis.js # DD-MM-YYYY · Tran Date col
│ │
│ ├── 📂 utils/
│ │ ├── unlockPdf.js # pdfjs decrypt + typed errors
│ │ ├── extractRows.js # Y-coord grouping + raw dump
│ │ ├── detectBank.js # Keyword rules → bank name
│ │ ├── detectScanned.js # Text density per page check
│ │ ├── normalizeTransactions.js # Validate + clean parser output
│ │ ├── categorize.js # 10-category keyword matcher
│ │ ├── hashTransaction.js # SHA-256 dedup ID generator
│ │ └── logger.js # Structured JSON Cloud Logging
│ │
│ ├── 📂 firestore/
│ │ ├── writeStatement.js # Create statement doc → return ID
│ │ ├── writeTransactions.js # Batch write 500/commit
│ │ ├── listTransactions.js # Query + in-mem filters + paginate
│ │ └── listStatements.js # userId filter + date sort
│ │
│ ├── 📂 tests/
│ │ ├── 📂 fixtures/ # PDF row dump fixtures
│ │ │ ├── sbi.fixture.js
│ │ │ ├── hdfc.fixture.js
│ │ │ ├── icici.fixture.js
│ │ │ └── axis.fixture.js
│ │ ├── sbi.test.js # 11 unit tests
│ │ ├── hdfc.test.js # 7 unit tests
│ │ ├── icici.test.js # 8 unit tests
│ │ ├── axis.test.js # 7 unit tests
│ │ └── parseStatement.smoke.test.js
│ │
│ ├── index.js # Exports all callables
│ ├── testPdf.js # CLI: raw text per page
│ ├── testParser.js # CLI: full parse preview
│ ├── testUnlock.js # CLI: error path testing
│ └── vitest.config.js
│
├── 📂 docs/
│ ├── schema.md
│ ├── firestore-schema.md
│ └── samples.md
│
├── 📂 samples/ # Test PDFs — NOT committed
│ └── .gitignore # *.pdf
│
├── firestore.rules
├── firestore.indexes.json
├── storage.rules
├── firebase.json
├── .firebaserc
├── .prettierrc
├── .eslintrc.js
└── README.md
| Code | Triggered By | User-Facing Message |
|---|---|---|
WRONG_PASSWORD |
pdfjs PasswordException | "Incorrect password. Please check and try again." |
CORRUPT_PDF |
pdfjs InvalidPDFException | "This file is not a valid PDF or is corrupted." |
EMPTY_PDF |
pdf.numPages === 0 | "PDF opened but contains no pages." |
UNSUPPORTED_BANK |
detectBank → UNKNOWN | "Supported: SBI, HDFC, ICICI, Axis Bank." |
SCANNED_PDF |
All pages image-only | "Scanned PDFs not supported in v1 — use net banking PDF." |
STORAGE_DOWNLOAD |
bucket.download() fails | "Could not find the uploaded PDF in storage." |
node --version # must be 18+
npm --version # must be 9+
npm install -g firebase-tools
firebase --versionStep 1 — Clone
git clone https://github.com/Akshatsrii/bank-statement-digitizer.git
cd bank-statement-digitizerStep 2 — Install dependencies
cd functions && npm install && cd ..
cd Admin && npm install && cd ..Step 3 — Firebase config
Open Admin/src/firebase.js and paste your config from:
Firebase Console → Project Settings → Your Apps → Web App
const firebaseConfig = {
apiKey: "AIza...",
authDomain: "your-project.firebaseapp.com",
projectId: "your-project-id",
storageBucket: "your-project.appspot.com",
messagingSenderId: "123456789",
appId: "1:123...:web:abc...",
};Enable email/password auth: Firebase Console → Authentication → Sign-in method → Email/Password → Enable
Step 4 — Deploy Firestore rules + indexes
firebase login
firebase use --add
firebase deploy --only firestore
firebase deploy --only firestore:indexes
firebase deploy --only storageStep 5 — Run locally
# Terminal 1
firebase emulators:start
# → Emulator UI: http://localhost:4000
# Terminal 2
cd Admin && npm run dev
# → App: http://localhost:5173Step 6 — Deploy backend
firebase deploy --only functionscd functions
npm test
# Runs 33 unit tests: sbi · hdfc · icici · axis parsersnpm test -- --coverageSBI_PASSWORD=pass HDFC_PASSWORD=pass ICICI_PASSWORD=pass npm testcd functions
# Print raw PDF text per page
node testPdf.js ../samples/SBI.pdf
# Full parse with table preview
node testParser.js ../samples/SBI.pdf YOUR_PASSWORD
# Inspect raw PDF item layout (debug new bank)
node testParser.js ../samples/HDFC.pdf YOUR_PASSWORD --inspect
# Test error handling paths
node testUnlock.js ../samples/SBI.pdf YOUR_PASSWORD╔═════════════════════════════════════════╗ ╔═════════════════════════════════════════╗
║ Phase 1 — Foundation ✅ DONE ║ ║ Phase 2 — Core Pipeline ✅ DONE ║
╠═════════════════════════════════════════╣ ╠═════════════════════════════════════════╣
║ ✅ Repo + Firebase init ║ ║ ✅ unlockPdf + typed errors ║
║ ✅ React/Vite/Tailwind scaffold ║ ║ ✅ extractRows Y-coord grouping ║
║ ✅ Firestore schema + rules ║ ║ ✅ detectBank keyword fingerprint ║
║ ✅ pdfjs-dist prototype ║ ║ ✅ detectScanned page density check ║
╚═════════════════════════════════════════╝ ╚═════════════════════════════════════════╝
╔═════════════════════════════════════════╗ ╔═════════════════════════════════════════╗
║ Phase 3 — Parsers ✅ DONE ║ ║ Phase 4 — Firestore ✅ DONE ║
╠═════════════════════════════════════════╣ ╠═════════════════════════════════════════╣
║ ✅ SBI parser (DD/MM/YYYY) ║ ║ ✅ writeStatement doc ║
║ ✅ HDFC parser (DD-MMM-YYYY) ║ ║ ✅ writeTransactions 500-batch ║
║ ✅ ICICI parser (S.No detection) ║ ║ ✅ SHA-256 deduplication ║
║ ✅ Axis parser (DD-MM-YYYY) ║ ║ ✅ listTransactions + filters ║
╚═════════════════════════════════════════╝ ╚═════════════════════════════════════════╝
╔═════════════════════════════════════════╗ ╔═════════════════════════════════════════╗
║ Phase 5 — UI ✅ DONE ║ ║ Phase 6 — Auth + Polish ✅ DONE ║
╠═════════════════════════════════════════╣ ╠═════════════════════════════════════════╣
║ ✅ Upload page drag-drop + progress ║ ║ ✅ Firebase Auth email/password ║
║ ✅ Transactions table + skeleton ║ ║ ✅ Login · Signup · ProtectedRoute ║
║ ✅ Filters + URL sync + chips ║ ║ ✅ Dashboard · Analytics · Profile ║
║ ✅ Excel export + summary sheet ║ ║ ✅ Dark/light mode · Transaction modal ║
╚═════════════════════════════════════════╝ ╚═════════════════════════════════════════╝
╔══════════════════════════════════════╗ ╔══════════════════════════════════════╗
║ v1.0.0 — MVP ✅ RELEASED ║ ║ v2.0.0 — Intelligence 🔲 ║
╠══════════════════════════════════════╣ ╠══════════════════════════════════════╣
║ ✅ 4-bank PDF parser engine ║ ║ 🔲 OCR support (Tesseract.js) ║
║ ✅ Firebase Auth + Firestore ║ ║ 🔲 AI categorization (Gemini API) ║
║ ✅ Analytics dashboard ║ ║ 🔲 Budget goals + alerts ║
║ ✅ Excel export ║ ║ 🔲 Google OAuth sign-in ║
║ ✅ 33 unit tests ║ ║ 🔲 Bank of Baroda + Kotak parsers ║
╚══════════════════════════════════════╝ ╚══════════════════════════════════════╝
╔══════════════════════════════════════╗ ╔══════════════════════════════════════╗
║ v3.0.0 — Scale 🔲 ║ ║ v4.0.0 — Mobile 🔲 ║
╠══════════════════════════════════════╣ ╠══════════════════════════════════════╣
║ 🔲 Multi-account support ║ ║ 🔲 React Native app ║
║ 🔲 Webhook: auto-scan on email ║ ║ 🔲 Camera PDF scan ║
║ 🔲 Google Sheets export ║ ║ 🔲 VS Code extension ║
╚══════════════════════════════════════╝ ╚══════════════════════════════════════╝
| Issue | Workaround / Status |
|---|---|
| Scanned PDFs (image-only) | Warns user — OCR planned for v2 |
| HDFC credit card statements | Only savings account format supported in v1 |
| Axis NRI statement format | Non-standard column layout — v2 |
| Statements with 1000+ rows | First parse is slow — batch writes optimize storage |
| Multi-page statements with unusual fonts | Use --inspect flag in testParser.js to debug |