Most retirement calculators are US-bound templates with a rupee symbol. DhanPlan treats Indian instruments — SIP, EPF, PPF, NPS, FIRE — as first-class columns, each with institutional-grade assumptions. It taught me that scope discipline and honest model math beat feature ceilings.
Every retirement calculator I tried asked me about my 401k. I don't have a 401k. Nobody in India has a 401k. What we have is EPF with a ₹2.5L interest-tax threshold, PPF with a 15-year lock-in, NPS with a 60/40 lumpsum-annuity split, and SIPs with step-ups — and not one mainstream calculator models any of it honestly. DhanPlan started as that irritation, and it became the clearest product lesson I've shipped: in a financial product, the model is the product.
The scope decision
We shipped four modules before adding anything else:
Four modules, each with institutional-grade assumptions. Scope discipline killed feature-creep in week one.
Barista FIRE: part-time work covers expenses, portfolio covers rest
Each uses: target = annual_expenses × 25 (4% SWR) adjusted for Indian inflation (6–7% long-term).
What made it ship
The scoping rule was: a feature must be defensible by two public data points. If we couldn't cite the instrument's mechanics from an official source, it didn't ship. This killed feature-creep in week one.
Tests are part of the product. Breakage here loses trust along with money — we wrote 28 reconciliation tests covering:
Test category
Count
Examples
Year-cap boundaries
6
EPF ₹2.5L interest threshold, PPF ₹1.5L contribution cap
Tax regimes
8
Old vs new regime for each instrument, LTCG grandfathering
Inflation scenarios
5
4%, 6%, 7%, 8%, variable year-by-year
Year-cap transitions
4
EPF interest taxability at ₹2.5L, NPS Tier I lumpsum tax
FIRE variants
5
Coast/Lean/Fat/Barista convergence tests
Each test is a known-input → expected-output assertion against the projection engine. CI runs them on every push.
What the product taught me
Model quality over field count. Four truthful modules out-rank twenty wish-list fields.
Tests are part of the product. A user who can't reconcile a number won't come back.
The boring math is the feature. Inflation surfacing per row is the retention wedge.
The inflation wedge (the feature that retained users)
We added a "real growth" column to every projection row: nominal_return - inflation. Users who saw their ₹1Cr corpus becoming ₹40L in today's money at 6% inflation over 20 years — that column drove 3× more return visits than the nominal projection alone. Mental accounting hates hidden inflation; surfacing it builds trust.
The projection engine (how the math actually runs)
The engine is a pure TypeScript library — no framework dependencies, runs in browser or Node. Core abstraction:
Each module (SIP, EPF, PPF, NPS, FIRE) implements Instrument with its own rules — caps, tax regimes, withdrawal rules. The orchestrator just calls project() on each and merges by year.
The CI pipeline (trust but verify)
# .github/workflows/test.yml
name: Projection Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: '20' }
- run: npm ci
- run: npm test -- --coverage
- name: Check reconciliation
run: |
# EPF year-cap boundary
node -e "
const { EPFEngine } = require('./dist');
const out = new EPFEngine().project({basic: 15000, da: 5000}, 30);
const final = out[out.length-1];
// Interest cap at ₹2.5L/yr should trigger
console.assert(final.interestAccrued <= 250000 * 30);
"
Coverage threshold: 90% on projection logic, 100% on tax math.
What I'd do differently
Let the modules export a plan (CSV/JSON) before inviting login walls — export early, gate later.
Carve the inflation story harder. Users mentally undercount inflation; surfacing each row's real-growth number is the retention wedge.
Build the tax comparator earlier. Old vs new regime comparison per instrument was the #1 support query; should've been a first-class view.