Work / Chitragupt / Wiki / Decisions
2026-07-12-per-txn-ay-bucketing-for-expense-ledger
Decisioncanonicalverified 2026-07-12
DECISION.2026-07-12.PER-TXN-AY-BUCKETING-FOR-EXPENSE-LEDGERDecision
Ledger entries derive ay from their per-transaction date
(txn_date), not from the enclosing document's ctx.ay. Envelope
rows without a transaction date (Form 16, 26AS, AIS) keep ctx.ay.
Why
The recompute audit surfaced a silent data-corruption bug: a bank
statement spanning February–May 2026 filed every row under one AY.
The March rows should sit in AY 2026-27; the April/May rows in AY
2027-28. Both the expense pillar (per-AY KPI hero) and the tax pillar
(Chapter VI-A caps, HRA, §80D) read where("ay","==",args.ay) — so
half the user's transactions silently vanished from whichever AY the
mapper picked for the document.
The pre-fix behaviour was equivalent to trusting whoever named the statement (e.g. "January to December 2026 statement") over the statute. Indian FY runs 1 Apr → 31 Mar, so any statement whose window crosses Mar/Apr is at risk; the frequency of long-span statements makes this common.
Impact
_lib.ts::makeEntrycallsderiveAyFromDate(txnDate)and uses the derived AY on the ledger row.ctx.ayremains the fallback for envelope rows (notxnDate).- Bank-statement mapper is the primary beneficiary; MF CAS SIP rows
and broker trade logs follow the same path when they carry
txnDate. - Tests updated:
expense-pipeline.test.tsnow expects the correct statute-derived AY (12 May 2026 → AY 2027-28) instead of the document's ctx.ay. - Downstream engines (pillar-expense, pillar-tax,
pillar-portfolio) automatically pick up the correct AY because
their queries filter
where("ay","==",args.ay)— no engine change required.
Status
Active. Landed as part of the Phase 3 recompute-correctness sweep.
Sources
- Phase 3 recompute-correctness commit (Phase 3 sweep).
apps/functions/src/triggers/firestore/ledger-mappers/_lib.ts::makeEntry.- Related concept: user-identity-entry.
Every project of mine is written down like this.
Read the résumé