MF360
A DIY investor platform for Indian mutual funds: what you own, what it costs, where you duplicate, what is deteriorating — answered from messy statements with methodology you can read before you trust.
“If it is not right, do not do it; if it is not true, do not say it.” — Marcus Aurelius, Meditations 12.17
Two sentences that read like this project’s operating manual. Rankings, affiliates, and ads are permanently excluded because selling and advising cannot share a roof — that is the “not right” half. Every formula lives in a versioned methodology document before it lives in code, and corrections are rows rather than edits — that is the “not true” half: never state a number you cannot re-derive.
Two ways to read this page
- Understand the system: The spark → The struggle → The decisions → How it works → Where it stands → Reflection.
- Build your own: Constraints → Architecture → The correction rule → The tutorial series → Exercises at the end.
The spark
Most investors cannot answer six basic questions: what do I own across all folios, what is it really costing me, where am I duplicated, what is deteriorating, what deserves review, what am I doing well? Brokerage statements answer none of them — a CAS file is a flat list of transactions, not a portfolio. Spreadsheets answer them once, then rot, because no spreadsheet survives a scheme merger.
So MF360 started from distrust: distrust of statements as answers, of one-off math, of any number without a method behind it. The project is method-first in the literal sense — formulas are written into versioned methodology documents before they are implemented, and 24 golden portfolios stand guard over every change.
The struggle
Real statements are hostile. CAS files arrive in (at least) two formats, names don’t match the master list, NAV days go missing, schemes merge and rename mid-history. The ingestion pipeline answered with four mapping strategies carrying confidence scores instead of hope, a corrections pipeline where fixes are rows (old facts are never overwritten), and a NAV heartbeat that notices gaps. Messy input wasn’t a phase to get through; it is the permanent condition, and the architecture finally admits it.
The 5-test drift, still open. Verification found tests calling MethodologyVersion.parse while the implementation exposes from_string — a rename that skipped its tests. Five failures, one root cause, sitting in the backlog below. I’m leaving it visible here because it is exactly the rot that methodology versioning exists to prevent, caught by the machinery doing its job. It gets fixed before any new feature.
The frontend is a scaffold. The API serves real answers; the Next.js side renders the shape of the product, not the product. Saying so plainly matters more than screenshots of it: this is a backend-and-methodology project wearing a frontend wireframe.
Money failed at intersections. Unit tests alone missed the bugs that matter — a merger during a SIP with a missing NAV day. That specific shape of failure is why the golden suite (GP-01…GP-24) exists: lump sums, SIPs, mergers, renames, duplicate uploads, out-of-order data, each with expected holdings, lots, opportunities, and alerts.
The decisions
Frozen entities + value objects for money and identity
- Problem
- Financial identity data (PAN) and money values must be valid everywhere, without checks scattered across handlers.
- Options considered
- Validate inline at each use
- Value objects with validation in constructors
- Database constraints only
- Chosen
- Frozen dataclasses + validating value objects (PAN regex + masking, Decimal percentages, ISIN/AMFI formats).
- Why
- Invalid states become unrepresentable: a PAN object always holds a valid PAN, and its string form returns the masked version so logs can't leak it.
- Trade-offs
- More types to learn; conversion at every boundary. Plain strings with validators would do for throwaway scripts — never for identity or money.
- Result
- 8 PAN tests, 10 crypto tests; leakage structurally impossible, not just discouraged.
Golden portfolios as the money test suite
- Problem
- Financial calculations need regression protection that unit tests of isolated functions can't give.
- Options considered
- Unit tests only
- Golden end-to-end scenarios (GP-01…GP-24)
- Manual spreadsheet checks
- Chosen
- 24 golden scenarios: lump sums, SIPs, mergers, renames, duplicate uploads, out-of-order data, missing NAVs — each with expected holdings, lots, opportunities, and alerts.
- Why
- Money bugs hide in interactions, and only full-scenario fixtures catch them.
- Trade-offs
- Fixtures are heavy to author and brittle to methodology changes — hence methodology versioning, so old goldens stay pinned to old rules.
- Result
- The suite that would catch a formula regression before any user does.
Five constraints hold regardless of mood: money math reproducible (versions + goldens); PAN never in plaintext outside AES-256-GCM (masked display everywhere else); the pipeline survives bad data (validators, dead letters, corrections as rows); no execution, rankings, or affiliates — permanently; everything runs local via Compose.
How it works
flowchart LR
subgraph In["Inputs"]
CAS["CAS statements"]
NAV["AMFI NAV feed"]
SEBI["SEBI / AMC data"]
end
subgraph Pipe["Ingestion (Bronze > Silver)"]
P["Parse > map > validate > correct"]
end
subgraph Core["Domain + engines"]
E["Cost, overlap, tax,
performance engines"]
end
subgraph Out["Answers"]
API["REST + read-only MCP"]
UI["Next.js (scaffold)"]
end
CAS --> P --> E --> API --> UI
NAV --> P
SEBI --> PRaw statements enter; validated facts accumulate; engines answer the six questions. Corrections are rows, not edits.
Follow Ravi’s upload: CAS parse (forgiving) → strict validation → scheme mapping with confidences → corrections as new rows, never edits → NAV heartbeat fills gaps → cost, overlap, tax, and performance engines read settled facts → REST answers, and a read-only agent server offers the same to AI clients. Temporal modeling runs through all of it — schemes resolve as-of any date, multi-hop merger chains followed, circular mappings raising instead of looping forever.
flowchart TB CAS["CAS statements"] NAV["AMFI NAV feed"] CAS --> P["Parse > map > validate"] NAV --> P P --> L["Normalized transactions (immutable)"] COR["Corrections as new rows"] --> L L --> T["Tax lots (FIFO)"] T --> H["Holdings, as of any date"] H --> E["Cost / overlap / tax / performance engines"] MV["Methodology version"] -.-> E G["Goldens GP-01..GP-24"] -.-> E E --> A["Answers: REST + read-only MCP"]
Read top to bottom in 30 seconds: messy input settles into immutable facts; lots derive holdings; engines answer — pinned to a methodology version, guarded by golden portfolios. Nothing downstream is ever edited, only re-derived.
| Step | Matches on | Confidence |
|---|---|---|
| 1. AMFI code | Exact code from the statement | 1.0 |
| 2. ISIN | Security identifier | 0.98 |
| 3. Normalised name | Exact match after cleanup | 0.95 |
| 4. Fuzzy (difflib) | Similarity ratio, above threshold | score |
From scheme_mapper.py: first match wins, confidence recorded per row. No match raises — the caller queues it for human review instead of guessing.
Where it stands
| Verified | 94 passed / 5 failed with one isolated cause (methodology-version drift); 7 linear migrations; 102 docs + 11 ADRs; suites for unit/integration/api/golden/security present in source |
| Observed | The doc/docs split (read-only master vs living blueprint) surviving real development; task reports drifting from task status |
| Intended | Zero-failure suite once the drift is fixed; frontend beyond scaffold |
| Unknown | Behavior on a decade of real CAS history; multi-user load |
| Check | Result | Date |
|---|---|---|
| Unit tests (excl. infra) | 94 passed, 5 failed* | 2026-09-05 |
| Test suites present | unit/integration/api/golden/security | from source |
| Doc pages | 102 + 11 ADRs | from source |
| Migrations | 7, linear chain | from source |
| Golden scenarios | GP-01…GP-24 specified | from source |
*The 5 failures share one root cause: tests call MethodologyVersion.parse while the implementation exposes from_string — a test/source API drift, reported as maintainer backlog, not tutorial content.
How help was used, and where judgment stayed
This repo shows its agent workflow in the open: docs/tasks/ holds generated task prompts beside their completion reports, produced under scope guards (one module per task, binary acceptance, hard stop). Delegated: scaffolding, ingestion stages, boilerplate. Kept by humans: the methodology documents (formulas are opinions about money — no agent signs those), the guardrails, the goldens’ expected values. Verification is the suite itself, and the suite just proved its worth by catching the methodology drift. One process debt, named in the tutorials: completion reports exist for tasks still marked READY — trackers rot the moment updating them feels optional.
Reflection
What worked. Method-first development: formulas reviewed as prose before they became code. Frozen value objects making leakage unrepresentable. Goldens catching interaction bugs unit tests can’t see.
What didn’t. The methodology rename that skipped its tests (open backlog). Client-side anything for money (moved server-side as a standing rule). The scaffold frontend masquerading — no longer; it’s labeled.
What surprised me. That the hardest part wasn’t math but identity across time: which scheme, as of which date, through which merger. Temporal modeling ate more design hours than every engine combined.
What I’d change. Fix the 5-test drift first — no new features until green. Then: real CAS history volume test, frontend past scaffold, and a tracker with an owner instead of a template.
Project timeline
- Phase 0Foundation tasks
Scaffolding, Compose, FastAPI/Next.js skeleton, CI, tax/CPI groundwork (TASK_001–004).
- ActiveDomain + ingestion + fund master
Frozen entities, tax engine, CAS parsing with corrections, NAV pipeline, 102 doc pages.
- 2026-09-05Independently verified
94/99 unit tests passing locally; 5 failures isolated to one API drift; routers/models/pipeline read end to end.
Build your own
Reproduce: Compose up, migrate, seed, run the goldens — green except the five known drift failures, which you should see fail exactly as described (a failing test you understand beats a passing suite you don’t). Modify: add one validator to ingestion and watch a bad CAS row land in the dead letter queue. Extend: author golden portfolio GP-25 for a case you fear — merger during a SIP is taken; find your own intersection. Re-design: swap one engine’s methodology version and feel what “pinned goldens” buys you. The five-part series follows Ravi from upload to reviewed portfolio — start with Part 1: method and monolith.
Go deeper
The closest sibling is the MF360 Academy — the education layer on top of this methodology: versioned syllabi and deterministic study paths instead of versioned formulas. Read them as a pair: one versions money math, the other versions learning content. For the opposite philosophy — agents with gates instead of deterministic math — the Autonomous Data Engineer shows what happens when the machine is allowed to act, and the Mastery platform shows the no-agent extreme.