Skip to content
Ritesh FirodiyaGet in touch

Work / Chitragupt / Wiki / Decisions

2026-08-10-debt-mf-capital-gains-three-regime-split

Decisioncanonicalverified 2026-08-10

DECISION.2026-08-10.DEBT-MF-CAPITAL-GAINS-THREE-REGIME-SPLIT

Debt mutual fund capital-gains three-regime split (Finance Act 2023 + Finance Act 2024)

Decision

Implemented for real via broker-cg's "Non Equity" section (verified against self.ritesh's real Zerodha export with per-row Entry/Exit dates); left as a documented no-op for mf-cas (CAMS/KFintech CAS) because no real CAS document in the corpus proves it prints per-lot dates or a debt-specific breakdown, so no fixture-backed extraction was added there — do not fabricate.

Files:

  • packages/shared/src/schemas/document.ts — BrokerCgFieldsSchema +4 debt fields (debt_stcg, debt_ltcg_indexed, debt_ltcg_flat, debt_ltcg_slab).
  • packages/parser/src/broker-cg/debt-cg.ts (new) — pure per-row classifier: purchase >= 2023-04-01 → ltcg_slab always; else purchase < 2023-04-01 + sale < 2024-07-23 → ltcg_indexed if held > 1095 days else stcg; else (sale >= 2024-07-23) → ltcg_flat if held > 730 days else stcg.
  • packages/parser/src/broker-cg/parser.ts — wires the "Non Equity" section into the classifier.
  • packages/parser/src/broker-cg/index.ts — re-exports the new module.
  • packages/parser/src/broker-cg/debt-cg.test.ts (new, 11 tests) — boundary coverage for both statutory cutoffs.
  • packages/parser/src/broker-cg/fixtures/index.ts + fixtures.test.ts — 4 new fixtures, one built from real corpus data (self.ritesh's actual fy2025-26-q1-q4-equity.xlsx "Non Equity" rows), 3 synthetic covering the other regimes/boundaries.
  • apps/functions/src/triggers/firestore/ledger-mappers/broker-cg.ts — emits debt_mf_stcg_realised / debt_mf_ltcg_indexed_realised / debt_mf_ltcg_flat_realised / debt_mf_ltcg_slab_realised, the same entry_types the mf-cas mapper already had as dead passthrough.
  • apps/functions/src/triggers/firestore/ledger-mappers/mf-cas.ts — comment fix only, correcting a false claim that the parser already splits by acquisition/transfer date.
  • apps/functions/src/__tests__/broker-cg-template.test.ts and mapper-contract.test.ts — new coverage.

Downstream (capital-gains-engine.ts, portfolio-review-engine.ts, income-summary.ts's ENTRY_TYPE_TO_BUCKET) already correctly consumed these four entry_types — they were wired and waiting; only the emission side was dead. Full typecheck/lint/test green across parser, shared, and chitragupt-functions workspaces (parser 163 tests, shared 196, functions 561 + 1 pre-existing unrelated skip).

Why

The debt-MF capital-gains three-regime split (Finance Act 2023 + Finance Act 2024) was dead code: mf-cas's ledger-mapper read four fields (debt_stcg_realised, debt_ltcg_indexed_realised, debt_ltcg_flat_realised, debt_ltcg_slab_realised) that neither MfCasFieldsSchema nor extractMfCasFields ever defined or populated, so the mapper's passthrough always evaluated to zero. Investigation of self.ritesh's real uploaded corpus (~/git/personal/documents/docs/self_ritesh/tax/*/stocks/zerodha/) found the real, extractable source of this data: Zerodha's Console export ("Tradewise Exits" sheet) started shipping a dedicated "Non Equity" section around FY2025-26 with genuine per-row Entry Date / Exit Date columns for debt-oriented instruments (Gold ETF, Liquid ETF) — exactly the lot-level data needed to classify each disposal into the correct regime. No real CAS (CAMS/KFintech "Consolidated Account Statement") document exists in the corpus, and the existing mf-cas-template.test.ts file already documents this gap ("the user's corpus has no CAMS / KFintech CAS PDFs to fixture against yet") — so per the task's explicit instruction not to fabricate data, mf-cas was left as an honestly-commented no-op rather than guessing at label wording never verified against a real document.

Impact

  • pillar-portfolio — Portfolio Review's debt-MF STCG/LTCG-indexed/LTCG-flat/LTCG-slab tax buckets (portfolio-review-engine.ts) now receive real, non-zero data for any user who uploads a Zerodha "Tradewise Exits" export containing a "Non Equity" section, instead of always reading zero.
  • capital_gains/{ay} documents gain real realised_debt_mf_*_paise values wherever a real debt-fund disposal exists in the source statement.
  • No schema drift introduced against .context/wiki/entities/* — this is a bug fix against already-canonical tax rules (Finance Act 2023 + Finance Act 2024), not a new product decision about rates or thresholds.
  • mf-cas (CAMS/KFintech CAS) remains unable to produce this split until a real CAS document proves what label/format it would use — flagged, not silently defaulted.

Status

Active.

Sources

  • ~/git/personal/documents/docs/self_ritesh/tax/fy2025-26/stocks/zerodha/fy2025-26-q1-q4-equity.xlsx — real Zerodha Console export, "Tradewise Exits from 2025-04-01 to 2026-03-31" sheet, "Non Equity" section (GOLDBEES + LIQUIDCASE rows with real Entry/Exit dates).
  • ~/git/personal/documents/docs/self_ritesh/tax/fy2023-24/stocks/zerodha/tax-pnl-q1-q4.xlsx and ~/git/personal/documents/docs/self_ritesh/tax/fy2024-25/stocks/zerodha/zerodha-profit-loss.xlsx — real "Taxpnl Statement for Mutual Funds" sheets confirming the industry convention of a 3-way debt bucket split ("Short Term profit Debt" / "Long Term profit Debt" / "Debt - Purchases post 2023-04-01").
  • apps/functions/src/__tests__/mf-cas-template.test.ts — existing code comment confirming no real CAS document exists in the corpus.
  • packages/parser/src/broker-cg/debt-cg.ts, packages/parser/src/broker-cg/parser.ts, apps/functions/src/triggers/firestore/ledger-mappers/broker-cg.ts, apps/functions/src/triggers/firestore/ledger-mappers/mf-cas.ts.

Every project of mine is written down like this.

Read the résumé