Case Study
A₹tha: A Finance App Where the AI Isn't Allowed to Do Math
Solo-built in 4 days. Every rupee figure is computed in TypeScript. Groq writes the sentence that explains it. The rule never broke.
Built with:Next.js 16 · React 19 · TypeScript · Supabase · Groq (Llama 3.3 70B) · Tailwind v4 · Playwright
Outcome
Shipped solo: 3-step onboarding, a causal-attribution verdict engine, and a 5-action Decision Lab. Zero TypeScript errors. Zero Groq JSON parse failures across 27 eval cases.
The Constraint I Set Before Writing Any Code
Most personal-finance apps have the same failure mode. They hand the user's income and expenses to an LLM and ask it to reason about their financial health. The LLM does something reasonable. Sometimes it's right. Sometimes it invents a savings rate, rounds an opportunity cost, or confidently describes a shortfall that doesn't match the numbers sitting three lines above it in the same prompt. This isn't a hypothetical failure mode. It's the same mechanism that got an airline held liable in 2024 for a fare policy its chatbot invented on the spot.1
Once that happens, you don't have a finance product. You have a slot machine with better copy.
DECISION
The constraint I set before writing a single component: math first, AI last. Every rupee figure a user sees (goal probability, causal contribution percentage, opportunity cost of a purchase) comes from deterministic TypeScript running against real Indian market data. Groq only receives numbers that already exist and writes the sentence that explains them. It cannot change the numbers.
This cost me time I didn't have. Writing and testing the weighted scoring function for causal attribution took an evening I'd budgeted for onboarding UI. I kept the rule anyway, because the alternative (an LLM that occasionally invents a percentage in a product about someone's money) isn't a tradeoff. It's a disqualifier.
What the Product Is
A₹tha builds a Financial Digital Twin per user: a structured model of their income, expenses, assets, and goal, seeded through a three-step onboarding and persisted in Supabase. Against that twin, the product answers one question no generic finance app answers: which specific factor is blocking your goal, and by how much?
The causal-attribution engine is the core. causalAttribution() checks a user's
twin against five thresholds: savings rate below 20%2, EMI burden above 30%
of income3, lifestyle inflation outrunning income growth, an emergency fund
under three months of expenses4, and equity holdings under three times
monthly income.
Each triggered factor gets a raw weighted score. Those scores normalise into
percentages that sum to 100, sorted by contribution. If nothing triggers, it falls
back to a single honest output: goal timeline too short.
INSIGHT
The percentages Groq sees in the analyze-twin prompt are the output of that
function. Groq writes the verdict sentence and a confidence score around them.
It cannot change them. "Lifestyle inflation drives 38% of your shortfall" is a
computed number, not a generated one.
The Decision Lab runs five actions against the same twin:
Simulate projects net-worth paths for an MBA, a home purchase, or a job
switch using sipFutureValue() and compoundGrowth() against the same market
return and inflation tables as the main verdict. Groq writes the assumption note
and key risks. It never touches the path numbers.
Spend-check answers "should I buy this?" with two numbers the LLM doesn't
see until they're already computed: emi_ceiling_breach and a 10-year
opportunity cost at Nifty 50's real CAGR.5 For an ₹80,000 phone bought today,
that opportunity cost lands around ₹1.7 lakh. The route recomputes it after
every Groq call to make sure the model hasn't rounded it into something friendlier.
Credit-card match has no Groq involvement at all. It scores the card catalogue against the twin's income, EMI load, and spend categories in pure TypeScript and returns the top three.
Chat is the one surface that runs in text mode: temp 0.4, capped at 400 tokens, grounded in the same twin data as everything else.
The Architecture Decision That Made This Work
Two structural choices did most of the work.
Six tables (market returns, city inflation, property appreciation, term-insurance premiums, tax slabs, and a credit-card catalogue) are seeded once via a service-role script and locked read-only through RLS for every authenticated user. The app can query Nifty 50's 10-year CAGR from mfapi.in or RBI's city-level CPI. It can never mutate them.
That boundary is what makes the causal-attribution math trustworthy. The same twin, run twice, produces the same percentage. Nothing in the read path can drift.
Every structured API route (analyze-twin, simulate, spend-check) passes
Groq a block of pre-computed values and asks it to narrate, not calculate.
The system prompt for spend-check explicitly tells Groq that
emi_ceiling_breach and opportunity_cost_10yr are already computed and to
pass them through unchanged.
The eval harness exists precisely to catch the cases where Groq ignores this instruction. Across 27 test cases, it didn't.
What I Scoped Out and Why
NOTE
A₹tha's onboarding is self-reported. The user types their income and expenses. That is the right call for a time-boxed solo build. It is the wrong call for a product where the accuracy of the twin determines the credibility of the verdict. Account Aggregator integration (real bank-statement data under explicit consent) is the obvious v2. I cut it because standing up a licensed AA partner integration in four days isn't a tradeoff. It's a fantasy.
Everything else I cut was an effort call, not a capability call.
| Solution | Reach | Impact | Effort | Score |
|---|---|---|---|---|
| Deterministic causal-attribution engine (not LLM-estimated)top | 100% | 0.46 | 0.35 | 1.31 |
| 3-step onboarding + persona quick-fill | 100% | 0.30 | 0.25 | 1.20 |
| Decision Lab (simulate, spend-check, credit-card, chat) | 80% | 0.40 | 0.55 | 0.58 |
| Groq eval harness (27 cases across 3 personas) | 60% | 0.25 | 0.30 | 0.50 |
| Playwright E2E + mocked API test suite | 50% | 0.20 | 0.35 | 0.29 |
| Account Aggregator integration (real bank data) | 70% | 0.35 | 2.00 | 0.12 |
Internal scoring for what got built in the time box. Effort = engineering complexity (lower = harder). Score = reach × impact ÷ effort.
AA scored highest on potential impact and lowest on time-adjusted score. I parked
it and moved on. The city list on Step 1 is hardcoded to 20 entries because
querying property_appreciation directly adds a round trip I didn't need in
onboarding. Both are one change away. Neither was the right thing to fix in the
time I had.
Proving the AI Layer Behaves
A math-first architecture only holds if the narrative layer is actually reliable.
"Trust me, I checked the prompts" isn't enough for a finance product. So I built
scripts/groq-eval.ts before I called the build done.
It runs nine prompt variants (the verdict call, three simulate scenarios, two spend-check items, and three open-ended chat questions) against three fixed personas (graduate, mid-career, senior). 27 total cases. It checks that every JSON-mode response parses cleanly and every response returns inside a 5-second budget.
The 0 parse failures number is the one I'd defend. It means Groq respected the output schema on every call. It does not mean the narrative content is always good. That's a different question, one that needs real users, not a harness.
Hypothesis
A causal-attribution percentage, like 'EMI burden explains 34% of your shortfall,' will feel more actionable to a 22–32 year-old user than a generic health score or advice paragraph.
What we found
Untested with real users. No one outside the build has used the live product yet. This is the bet the entire architecture is built around, not a result I've measured.
Implication
The next real test is qualitative: put the dashboard in front of 10 people in the target age band and watch whether they read the causal bars before the probability number, or skip past them entirely.
How the Build Actually Went
- Day 1 · Jun 13Reference data before product screens
Scaffolded Next.js and Supabase auth, then built the data layer: 16 market-return instruments from mfapi.in, a credit-card catalogue, city inflation from RBI CPI data, and property appreciation from NHB RESIDEX. The twin math can't run without real data underneath it, so I didn't touch a product screen until the reference tables were clean.
- Day 2 · Jun 14The whole product, under deadline
Built onboarding steps 2–3, financial-math.ts and the causal-attribution engine, spend-check and credit-card-match routes, Decision Lab scenarios, the demo and temp-session system, the first test suite, and the eval harness, all in one day. That's what a clean Day 1 scope buys you.
- Day 3 · Jun 15Design system
Moved source into src/, rewrote the type scale onto Hanken Grotesk + Instrument Serif, replaced placeholder styles with a warm ivory / sage-green token system. The product was working before it looked right, which is the correct order.
- Day 4 · Jun 16Hardening
Fixed demo-twin propagation bugs, documented the architecture contracts in AGENTS.md, ran two passes of Vercel's React performance guidelines. Called it done.
What I'd Do Differently
I'd write the schema and API contracts on Day 1, not mid-build. I wrote the field-name contracts (which columns exist, what they're called, what the canonical aliases are) as I discovered I needed them. Early code and later code disagreed on naming until I froze the contract and back-filled the rule. A ten-minute decision on Day 1 would have removed a full sprint of churn.
The second gap is the demo account. The first person who opens the demo dashboard
triggers a live Groq call instead of an instant cached read, because I didn't
pre-seed the twin_analyses cache for the demo user. It's a one-line seed script
change. I noticed it on Day 4 and ran out of time. That's the kind of thing that
matters for an early demo and barely matters once a product is in production, a
useful reminder of where to spend time in each context.
What I don't know yet is whether the core bet lands. Whether a 26-year-old in Bengaluru, looking at a dashboard that says "lifestyle inflation drives 38% of your shortfall," finds that more useful than "save more." That's the only question that matters, and it needs real people, not an eval harness. The next session is qualitative.