Side Project

FinClarity

Your money is somewhere. FinClarity shows you where. Upload your bank statements to get one clear monthly picture.

Next.js · TypeScript · Supabase · Claude API Live demo → GitHub →

The Idea

I track money across three banks, two wallets, and a spreadsheet that I keep abandoning. FinClarity started as a fix for that, and as a chance to build something end to end. The Singapore market doesn't have a great answer to this either — SGFinDex exists, but bank participation is low and getting fewer, so most people are stuck tracking manually or not at all.

Why this problem: I would imagine myself as someone who is pretty financially savvy. But my own spreadsheet habit turned out to be a better signal than any market report. I did not know where my money went or how much savings I had across different plaforms. This was an easy way for me to do so without having to connect accounts.

The Pain Points

These are pain points I heard from friends, and also felt myself. They drove the design decisions that followed.

"I check three banking apps and still don't know my full picture. Some of the cashback are stuck on other rewards platforms"

"Connecting accounts officially is practcally non-existent."

"I tried tracking in Excel and whatever spend tracker apps I could find. I gave up after a week."

"I have accounts in a few different currencies. Nothing handles that well."

Customer Journey

Here is a typical user journey mapped against a persona so the pain points earlier tie to something relatable. The journey map also shows how FinClarity responds to each pain point, and the emotional arc of the experience.

Persona

Justin, 27 — juggles money across several banks and e-wallets, no single view of where it's all going

Scenario

Every month, Justin's money moves across accounts he rarely checks together. He just wants one clear picture without opening all of them separately.

Awareness Consideration Discovery Trust Test Habit Loop
Touchpoint Own banking apps Spreadsheet FinClarity demo page Upload modal → Dashboard Monthly reminder email
User Actions Checks balances across separate apps, tallies spend in his head Logs transactions manually in Excel and Spend Tracker Explores a sample dashboard before signing up Uploads one password-protected statement, then reads the plain-language summary Returns each month; watches his streak grow
Emotion 😕Overwhelmed 😒Resigned 🤔Curious 😮→😊Nervous, then relieved 🙂Satisfied
Pain Point No single view of total spending Manual entry is tedious; gives up within weeks Wary of yet another finance app Hesitant to share financial documents
FinClarity Response Demo dashboard with sample data, no signup required to look around No bank login; password used once in memory, then discarded. Narrative leads with wins, not a guilt trip. Streak rewards consistency, not spend or savings amount
The bet: if the Trust Test column doesn't feel safer than handing over a bank login, nothing to its right matters.

Scoping It

Every cut here needed an actual reason, not just "ran out of time."

Cut, and why

  • Live bank API / Plaid integration — legal complexity, MAS licensing risk
  • Investment recommendations — requires a regulated financial advice licence
  • Saving statement passwords — SG bank passwords are often NRIC/DOB; storing them is a security and PDPA risk, not a convenience worth the exposure
  • XP / levels tied to money — rejected on values grounds: don't signal that more money means higher status
  • Social / comparison features — not core to proving the insight loop works

Kept, and why

  • Statement upload as the only input — automatic extraction, zero manual entry
  • Multi-currency as first-class, not a bolt-on
  • Progress-first narrative framing over a raw net-worth number
  • A monthly, not daily, check-in loop — frequent enough to build a habit without nagging
Caught myself: I called one feature a "watchdog" when it was really retrospective analysis. Naming things correctly early saves confusion later.

Requirements

The PRD turned that scope into criteria specific enough that someone else could check them without asking me what I meant. A few examples:

  • App attempts to open the PDF immediately; a password prompt only appears if the file is password-protected
  • On success, a small celebratory animation confirms the statement was processed and the password discarded
  • When a month was genuinely difficult, the narrative acknowledges the truth first, then pivots to what's actionable
  • Sample dashboard data is clearly labelled as demo data, never mistaken for the user's real finances

Technical Approach

One call mattered more than the rest: parsing PDFs with pdfjs-dist in memory, rather than sending raw statements through Claude's Files API.

Vercel (Next.js App)
Next.js Frontend
  • Upload UI — drag-and-drop, inline password prompt
  • Dashboard, breakdown views
  • Demo page — sample data
Next.js API Routes
  • POST /api/statements/upload — full pipeline
  • GET /api/reports, /api/transactions
  • Cron: /api/cron/reminders — daily
pdfjs-dist Legacy build — in-memory parse + decrypt, password discarded after use
Extraction & Narrative Logic
  • PII-excluded extraction prompt
  • Transfer detection — amount/date match
  • Narrative, observations, nudges
Claude API claude-haiku-4-5
Authenticated writes only — anonymous sessions never persist
Cron-triggered reminder emails
Supabase (Postgres)
  • 6 tables — users, statements, transactions, monthly_reports, check_ins, exchange_rates
  • Auth — anonymous + email sessions
  • RLS off — accepted for hackathon scope
Resend (Email API)
  • Daily check-in reminder emails
  • Requires verified sending domain
Why: the Files API doesn't handle password-protected PDFs. pdfjs-dist decrypts in memory instead — it costs Claude's layout understanding, but bank statements are just line-by-line text, so that trade was fine.

Building It

Built in stages, each one sitting on something that already worked before I added the next layer.

Foundation
Database schema (6 tables, cascade deletes), Next.js/TypeScript/Tailwind scaffold, public GitHub repo with secrets excluded.
Statement upload pipeline
Drag-and-drop PDF upload, inline password handling, duplicate detection via file hash, animated processing state.
Claude extraction & narrative
Real LLM extraction with PII exclusion by design, transfer detection, and plain-language monthly narrative generation.
Dashboard
Narrative summary, four summary cards, collapsible observations panel, and forward-looking nudges — all wired to live data.
Demo & first-run experience
Static demo dashboard with clearly labelled sample data and guided tooltips, so first-time visitors see the value before uploading anything.
Detailed spending breakdown
Four views (category, account, transaction type, time), foreign-currency display inline, card-nickname editor.
Privacy-first anonymous mode
Users can upload and see results before creating an account; processed entirely in memory, nothing written to the database until they choose to save.
Deploy & ship
Live on Vercel with a daily reminder cron job, then submitted to Devpost with a project write-up and screenshots.

Acceptance criteria

  • Detected internal transfers are auto-excluded from spending totals and surfaced separately under "Savings & Transfers"
  • Mismatched statement date ranges trim analysis to the overlapping window and flag what was excluded
  • Foreign currency transactions show both the SGD equivalent and original currency inline, with an "as of [date]" exchange-rate note
  • Streak counter is based purely on check-in behaviour — never on spending or savings level

Risks encountered

  • OCBC merchant-code formatting caused "Other" to dominate spending categories — extraction prompt needs a dedicated handling pass
  • No login page for returning users — they could sign up and upload but not sign back in, caught late in the build
  • pdfjs-dist v5 is ESM-only and threw a DOMMatrix is not defined error in Node — resolved by switching to the legacy build
  • Reminder emails depend on Resend domain verification — untested end-to-end, could fail silently
Trade-off: shipping fast means some risks get accepted, not solved. The goal is knowing which ones.

Screenshots

Captured straight from the live demo.

Guided walkthrough of the FinClarity demo, with tooltips introducing the monthly summary and stat cards

Guided walkthrough

Scrolling through the FinClarity demo dashboard from the monthly summary down to next month's heads-up cards

Demo dashboard

Navigating the FinClarity app through Breakdown, Budget, Upload History, and Settings, then switching to dark mode

App navigation & settings

Iterating Post-Launch

What came after the first ship:

  • Login page for returning users, closing the gap flagged at deploy
  • Account creation now saves session data to the database instead of only living in browser storage
  • Conditional "top card" insight tile — only appears once a user has 2+ distinct cards to compare
  • Monthly budget tracker, added after realizing the narrative alone didn't answer "am I on track?"
  • Animated spend-vs-save stat strip for a faster read of the month

Reflection

The real surprise wasn't any single feature — it was that the thing became real instead of staying an idea in my head.

Next time: ask "where will this break in production?" earlier. The pdfjs and Supabase quirks only showed up once real users hit them.
Next time: own the debugging more instead of just describing symptoms. By the later steps I was already diagnosing things myself — like realizing the transactions table was empty because extraction was failing before insert.