DLRSDLRS
Features

Finance & Statements — Vault, Parsing, Reports

Upload password-protected bank statements. Vault decrypts transiently, deterministic parser validates totals, AI categorizes. All beginner-friendly with security deep-dive.

Overview — Finance Intelligence

What Finance Module Does

Not a spreadsheet. From emails + bank PDFs it extracts: transactions, opening/closing balance, credits/debits, categories (Food, Software…), and subscriptions (Netflix, Adobe). Validates math, not just AI guess.

Located: /w/[workspace]/finance → Overview, Transactions, Subscriptions, Invoices. Handles sensitive data via SECRET_ENCRYPTION_KEY vault.

Bank Statement Pipeline (4 Steps)

1. Upload — Vault Hints

Finance → Import → choose PDF (SBI/HDFC/ICICI). Enter vault hints: PAN, DOB, phone last 4 (stored encrypted).

Route: POST /api/finance/import
Body: multipart/form-data { file, workspace_id }
Store: STORAGE_DIR/finance/<workspace>/<id>.pdf

2. In-Memory Unlock (Secure)

Vault: AES-256 decrypt hints → try patterns:
- DDMMYYYY, MMDDYYYY, DDMMYY
- PAN(5)+DOB(4), Name+DOB
- Phone last 4 + combinations
Each try in-memory, not logged. Stops at first success.
If none → show "Try password" modal.

🔒 Security: No Password Logging

Passwords tried transiently, never written to activity_log or job payload. Vault blob encrypted at rest with SECRET_ENCRYPTION_KEY.

3. Parse & Validate (Deterministic)

Parser: unpdf → text → table detection (regex for dates, amounts)
Rows: { date, description, amountMinor (int), currency, type: credit|debit }
Totals:
  openingMinor + sum(credits) - sum(debits) === closingMinor
  If mismatch → flag "Statement totals don't match" (red).
Integer math: ₹18,420.50 → 1842050 minor units (no float errors).

4. Categorize & Review

AI proposes category per row; low-confidence (<0.7) shown in Review modal.

  • Food, Travel, Shopping, Software, Subscriptions, Bills, Business, Salary, Transfers, ATM, Unknown
  • Heuristic + LLM: merchant “Swiggy” → Food, “Adobe” → Software/Subscription

Vault & Security Model

Encryption:
- Key: SECRET_ENCRYPTION_KEY (32-byte hex, generate via `node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"`)
- Algorithm: AES-256-GCM (in lib/security/vault.ts)
- Stored: oauth_tokens, pan, dob, phone fragments encrypted
- Decrypt: only in worker, in-memory, per request

Tables:
- vault_items { id, workspace_id, kind, encrypted_blob }
- Only workspace owner can read/write via DAL guard

Production Warning

Rotating SECRET_ENCRYPTION_KEY breaks existing vault rows (can't decrypt). Migrate by re-encrypting with new key, don't just change env.

Categorization & Deterministic Math

Why Deterministic > LLM for Totals

LLM never does arithmetic. Parser sums amountMinor ints, validates. LLM only labels merchant. This avoids “₹500 + ₹500 = ₹900” errors.
Example transaction:
{ date: "2025-12-01", description: "UPI-ADOBE", amountMinor: 167500, type: "debit" }
→ Category: Software (Adobe keyword)
→ Subscription? Expense 1675 monthly → detect → create subscriptions row

Subscriptions & Invoices

Detection from emails ("Your Netflix receipt") + transactions ("NETFLIX" monthly):
subscriptions {
  id, workspace_id, name: "Netflix", amountMinor, currency,
  frequency: monthly|yearly, next_expected_payment, payment_method,
  source: "gmail"|"transaction", status: active
}
UI: /finance/subscriptions → Upcoming payments: Adobe ₹1675 due 2 Sep

Reminder automation: 3 days before due → notify.

Dashboard & Reports

/w/[workspace]/finance shows:

  • Total spent this month: ₹18,420 vs last month (+12%)
  • By category: Food ₹4,200 (22%), Software ₹2,400…
  • By day chart (Recharts), by merchant, by account

All computed via SQL GROUP BY category on minor units, formatted with Intl.NumberFormat.

Database Schema

accounts { id, workspace_id, name, type: bank|wallet, openingMinor }
transactions { id, workspace_id, account_id, date, description, amountMinor, currency, type, category, confidence }
subscriptions { ... as above }
invoices { ... }
expenses { id, workspace_id, title, amountMinor, category, receipt_file_id }
audit: activity_log records every finance change

Beginner Tips

On this page