Case Study · Personal Project

Budget G.P.A.

A personal finance app that grades your financial health like a report card: letter grades and a 4.0 GPA across five weighted pillars. I took the corporate budget scorecard methodology I built professionally and rebuilt it for individuals: automated bank sync, AI-classified transactions, and a single number that tells you how you’re actually doing. Built solo, end to end.

5 Scoring Pillars
35+ App Screens
12 Edge Functions
4.0 GPA Scale

The Problem

Budgeting apps show you data. Almost none of them render a verdict.

At work I grade enterprise cloud subscriptions with a weighted scorecard, and the effect is immediate: a letter grade ends the debate about whether things are fine. Personal finance apps have the opposite problem: endless charts, categories, and balances, but no answer to the only question people actually have: “am I doing okay?”

Budget G.P.A. makes the grade the product. Every month you get a report card: a letter grade, a GPA on the familiar 4.0 scale, and the top three fixes that would raise it. The competition (YNAB at $109/yr, Monarch at $99.99) competes on feature breadth; this app competes on a single legible number, at $39.99/yr.

Scale

Metric Value
Scoring Pillars5, weighted 35 / 25 / 15 / 15 / 10
App Screens35+ (Expo Router file-based routes)
Supabase Edge Functions12 (Plaid, classification, alerts, webhooks, account deletion)
Database Migrations26, with row-level security on every table
State Stores14 Zustand stores
Default Categories13 free (6 needs, 4 wants, 3 savings); unlimited on Pro
PlatformsiOS and Android from one codebase
Marketing Sitebudgetgpa.com, S3 + CloudFront

Six Systems

A scoring engine at the center, and everything else (sync, classification, money management, alerts, and monetization) feeding it honest data.

The Report Card
Five weighted pillars → one grade
  • Budget Adherence (35%), Financial Safety (25%), Spending Stability (15%), Needs vs. Wants (15%), Forecast Accuracy (10%)
  • GPA interpolates within each letter grade’s band: an A− is 3.7–3.9, never bleeding into the next grade
  • Provisional grades: pillars without enough data are excluded and weights renormalize, shown as “Provisional: N of 5”
  • Top Fixes card ranks the three actions that would recover the most weighted points
  • Report Card Day: a once-a-month reveal with grade deltas, streaks, and confetti when a goal lands
Bank Sync & AI Classification
Plaid + Claude API, three-tier trust
  • Plaid link, sync, and webhooks handled server-side in shared Edge Function code
  • Claude API classifies transactions: user rules auto-apply, AI results are suggestions to review, never silent writes
  • A third transaction state, “transfer,” keeps own-money movement out of both income and spending
  • Review Transactions screen with bulk accept and one-tap promotion (a deposited check can become income)
  • Subscription auto-detection from recurring charge patterns
Money Hub
Debts, income, subscriptions, one-time spend
  • Debt tracking with amortized payoff projections, avalanche/snowball ordering, and a “what would an extra $100 do?” payoff impact sheet
  • Multi-source income editor; scoring prefers real logged income over profile estimates
  • One-time planned expenses excused from discipline pillars, so a planned car repair shouldn’t tank your grade
  • Savings goals with target dates; contributions count as saving, not spending, and over-contributing never penalizes
Alerts & Nudges
Server-evaluated, client-rendered
  • Pacing, overspend, and emergency-fund milestone alerts evaluated in an Edge Function
  • Push notifications and an email digest, each independently toggleable
  • Alert logic mirrors the scoring engine’s exclusions: transfers and excused one-time spend never trigger false alarms
  • Spike auto-suggestions notice unusual planned-looking expenses and offer one-tap marking
Monetization
RevenueCat, one entitlement, honest tiers
  • $3.99/mo, $39.99/yr, or $99.99 lifetime, capped at 500 lifetime slots per year via a row-locked Supabase counter
  • Two promotional lifetime paths at $74.99: Founder for early adopters and EDU for .edu addresses, each mapping to the same single Pro entitlement
  • Free tier is genuinely useful: 13 categories, full report card, savings goals, debt tracking
  • Pro unlocks bank sync, AI classification, scoring settings, one-time tags, and unlimited categories
  • Entitlement state is server-owned: written only by the RevenueCat webhook, protected by a database trigger
Trust Infrastructure
The parts nobody sees
  • Row-level security on every table; Plaid tokens encrypted; webhook signatures verified
  • Biometric/PIN app lock via the device’s local authentication
  • Full in-app account deletion that unwinds Plaid billing before removing data
  • Sentry crash reporting on production builds
  • Branded email-confirmation and password-reset web pages, with show/hide on every password field

Architecture

One Expo codebase for both platforms, a Supabase backend that owns every sensitive operation, and third-party services kept strictly server-side.

 Expo / React Native App
TypeScript · Expo Router · 14 Zustand stores · scoring engine runs on-device · iOS + Android
↓ Supabase client (RLS-scoped) · EAS builds
 Supabase: Postgres + Edge Functions
26 migrations · RLS on every table · 12 Deno Edge Functions · server-owned entitlements · alert evaluation
↓ server-side only; no third-party keys ever ship in the app
 Plaid
Bank sync, balances, webhooks
 Claude API
Transaction classification
 RevenueCat
Purchases, entitlement webhook
 Sentry
Crash reporting

Tech Stack

LayerTechnology
AppExpo / React Native, TypeScript, Expo Router, Zustand, Gifted Charts
BackendSupabase: Postgres with row-level security, Deno Edge Functions
IntegrationsPlaid (bank sync), Claude API (classification), RevenueCat (IAP), Sentry (crashes)
Auth & SecuritySupabase Auth, biometric/PIN app lock, encrypted secure storage
Build & ReleaseEAS builds, App Store + Google Play
Marketing SiteStatic HTML on S3 + CloudFront + Route 53 at budgetgpa.com

Key Technical Decisions

A scoring product lives or dies on whether people trust the grade. Most of these decisions exist to protect that trust.

The first weight set (45% adherence) over-rewarded people who were good at predicting their own spending, regardless of whether their finances were actually healthy. The rebalance to 35/25/15/15/10 raised Financial Safety and Needs vs. Wants (the pillars that measure real financial position) and demoted pure planning accuracy.

  • Proration that matches reality: fixed costs like rent hit on the 1st and are never prorated mid-month; variable categories are paced, with penalties damped early in the month when one grocery run looks like a blowout.
  • One-sided savings scoring: over-contributing to a savings goal never lowers a grade. The failure mode of a naive “distance from budget” score is punishing people for saving too much.
  • GPA band integrity: the 4.0 GPA interpolates within each letter’s band and never crosses into the next grade’s territory, so the letter and the number can never disagree.
  • The philosophy in one line, stated in onboarding: spend as close to your budget as possible without going over.

Testing against my own live bank data broke the app in a way synthetic data never had: credit card payments, moves between accounts, and card-payment “thank you” credits inflated both income and spending. A grade computed from inflated numbers is worse than no grade.

The fix was structural, not a filter: transactions have a third state, transfer, alongside income and expense. Own-money movement is excluded from income, from spending, and from every pillar. Sync marks likely transfers automatically; the review screen shows them as “Transfer: not counted” with a one-tap promotion for the exceptions (a deposited check or a gift really is income).

Critically, the same exclusions were then propagated server-side into alert evaluation and subscription detection; otherwise the app would refuse to count a transfer in your grade while happily sending you an overspend alert about it.

A brand-new user has no spending history, no income data, and no forecast to check. Early versions quietly scored missing pillars as perfect, which meant every new user started with an inflated grade that could only fall. The first week of using the app felt like getting worse.

  • Pillars without enough data are excluded and the remaining weights renormalize; the grade reflects only what’s actually measurable
  • The report card says so explicitly: “Provisional: 3 of 5 pillars”
  • Pillars with no data persist as null in history, never as a fake 100
  • Report Card Day recomputes the completed month before celebrating it, and streak logic only counts completed months

Onboarding shows a sample report card instead, so the first real grade is a moment of information, not deflation.

A security review of my own code found that the paid flag was client-writable; any user with the anon key could mark themselves Pro. Worse than lost revenue: Pro includes AI classification, so a bypass would burn metered API spend on my key.

  • The flag is now server-owned: a database trigger rejects client writes; only the RevenueCat webhook (signature-verified) can set it
  • Lifetime slots are race-safe: the 500/year counter decrements under a Postgres row lock, so two simultaneous purchases can’t both claim slot 500
  • Plaid billing is unwound properly: account deletion calls Plaid’s item removal before deleting the token, in the right order; the original code nulled the token first, orphaning the item so Plaid would bill for it forever

All three were found by running structured reviews against my own finished code, the same discipline I’d apply to someone else’s.

Transaction classification has three trust tiers, and only the first writes without asking:

  • User rules auto-apply: if you told the app Kroger is Groceries, that’s a fact, not a guess
  • Claude’s classifications are suggestions: queued in Review Transactions with bulk accept, never silently written into your grade
  • Everything else stays uncategorized, and is counted in the grade with a visible “+$X uncategorized” disclosure, because letting unclassified spend vanish from the score was effectively grade inflation

Debt payments get special handling: auto-labeled, linked to their debt, and treated as spending, excluded from the AI pipeline entirely so a loan payment can’t get misfiled as dining out.

Every stack choice optimizes for one person shipping and operating the whole product:

  • Expo / React Native: one TypeScript codebase for iOS and Android, EAS handles signing and builds, and the scoring engine is a pure TypeScript module that runs on-device and is unit-testable in isolation
  • Supabase: Postgres with row-level security replaces a whole backend authorization layer; Edge Functions host every operation that touches a secret
  • No third-party key ever ships in the app: Plaid, Claude, and RevenueCat server credentials live exclusively in Edge Function secrets
  • RevenueCat over DIY receipts: refunds, grace periods, and cross-platform restore are a minefield with no upside to hand-rolling at this scale

Trade-off accepted: vendor dependence on Supabase and RevenueCat. For a solo product, operational simplicity beats theoretical portability, and both have clean exits (it’s Postgres underneath; the purchase model is three products and one entitlement).

Features at a Glance

 Scoring & Insight
  • Monthly letter grade + 4.0-scale GPA across five weighted pillars
  • Per-pillar score detail with humanized explanations of every deduction
  • Top Fixes: the three actions that would raise your grade the most
  • Trend charts with curved gradient lines and grade history
  • Report Card Day monthly reveal with deltas, streaks, and shareable card
  • Configurable scoring (Pro): variance windows, investment counting in emergency fund
 Money Management
  • Automated bank sync via Plaid, with manual accounts alongside
  • AI transaction classification with rules, suggestions, and bulk review
  • Savings goals with target dates, pace tracking, and completion celebration
  • Debt payoff projections with avalanche/snowball and payoff-impact modeling
  • Subscription auto-detection with a dedicated management screen
  • One-time planned expenses excused from discipline scoring (Pro)
 Experience & Safety
  • Push alerts and email digest for pacing, overspend, and milestones
  • Biometric / PIN app lock
  • 10 color themes with light and dark modes
  • Guided onboarding with sample report card and philosophy explainer
  • In-app account deletion that fully unwinds Plaid and RevenueCat state
  • Data export, in-app guide, and FAQ

What I Learned

What Worked Well
  • The grade-as-product thesis held: one legible number beats twenty charts, and every design decision could be tested against “does this make the grade more trustworthy?”
  • Testing against my own live bank data found entire bug classes synthetic data never would; the transfer problem alone reshaped the data model
  • Running structured multi-angle reviews against my own finished code caught a paywall bypass, a billing leak, and a dozen scoring seams before any user saw them
  • Keeping the scoring engine a pure, unit-testable TypeScript module made every rebalance safe to ship
  • Row-level security as the authorization model: the client can only ever see its own rows, no backend middleware to get wrong
  • Making the free tier genuinely useful (13 categories, full report card) instead of crippled; conversion comes from bank sync and AI, not from frustration
Challenges
  • Real bank data is adversarial: card payments, internal transfers, and refund credits all masquerade as income or spending, and every one inflates the grade if unhandled
  • Scoring policy is product policy: deciding whether a planned one-time expense should hurt your grade is a design question wearing a math costume
  • Client, server alerts, and subscription detection each re-implement “what counts as spending”; keeping their exclusion rules mirrored was a persistent source of seams
  • A misconfigured TypeScript exclude silently skipped type-checking the entire app for weeks, masking ten real errors, including UI that never rendered
  • Postgres 17 RLS interacts subtly with soft-deletes: a deleted_at IS NULL predicate in a SELECT policy silently breaks UPDATEs on those rows
Would Do Differently
  • Test with live bank data from week one; the transfer state, income promotion, and proration logic all came from late contact with reality
  • Define the scoring exclusion rules once, in one shared module consumed by client and Edge Functions, instead of mirroring them by discipline
  • Treat entitlements as server-owned from the first commit; retrofitting the trigger and webhook was avoidable work
  • Budget more time for auth-flow edge cases: email confirmation and password reset on mobile produced more beta-blocking bugs than any feature

The through-line with my professional work is exact: a score people trust is worth more than a dashboard people ignore. The corporate scorecard works because a VP can glance at a C+ and know where to look. Budget G.P.A. bets the same is true for a person and their own money, and that the hard part isn’t the grading math, it’s making sure the inputs deserve to be graded.

 Visit budgetgpa.com
 Back to Projects