Work / Chitragupt / Wiki / Decisions
2026-07-09-phase-f-recompute-itr-observability
Decisioncanonicalverified 2026-07-09
DECISION.2026-07-09.PHASE-F-RECOMPUTE-ITR-OBSERVABILITYDecision
Close three follow-on items from the 2026-07-09 audit:
recomputeExpenseReview+recomputePortfolioReview— parity callables mirroring the existingrecomputeTaxReview. Both are idempotent, overwrite the review doc, and let the client force a refresh after a mapper fix or a manual re-categorise without waiting for the nextonDocumentConfirmedcascade.- ITR form auto-inference persisted on the tax review. The
orchestrator writes
itr_form: "ITR-1" | "ITR-2"on everytax_reviews/{ay}recompute. Discriminants:- Any capital gains (STCG / LTCG / VDA) → ITR-2.
- Any foreign income (salary or dividend) → ITR-2.
- Total income > ₹50L → ITR-2.
- Otherwise → ITR-1.
The client
inferItrFormhelper reads the persisted value when present and falls back to re-running the shared inferrer on older reviews that predate the field.
- Admin
unmapped_paiseobservability tile. The KPI recompute samples up to 200 recenttax_reviews, counts how many carryincome_summary.unmapped_paise > 0, and sums the total. Two new fields onadmin_kpis/current:unmapped_paise_totalandunmapped_users_count. Both go to zero when every mapper's emittedentry_typeis either inENTRY_TYPE_TO_BUCKETor on the audit-only whitelist. A non-zero signal here is a contract bug (a mapper is silently orphaning revenue-affecting rows) — fix at the mapper level. CI'smapper-contract.test.tsshould have caught it before deploy; this metric is defence-in-depth.
Why
Phase A–E landed the correctness spine of the document lifecycle. Three items from the original audit remained loose:
- Tax pillar had a
recomputeTaxReviewcallable but Expense and Portfolio did not — a mapper fix couldn't refresh those reviews without the caller re-confirming a doc. - ITR-1 vs ITR-2 was inferred client-side at CA-hire time only. If the classification was wrong (or drifted between the review and the hire), the CA-handoff packet would be built against the wrong form. Persisting the value on the review makes the tax engine the authority.
unmapped_paisebecame a clean silent-orphan signal in Phase C. Without an admin surface, a future mapper rename would ship silently until a user noticed their tax review under-counted.
Impact
Contracts (packages/shared/src/contracts):
expense.ts— newRecomputeExpenseReviewInputSchema/RecomputeExpenseReviewOutputSchema.portfolio.ts— newRecomputePortfolioReviewInputSchema/RecomputePortfolioReviewOutputSchema.
Callable registry auto-picked both up (144 → 146).
Backend (apps/functions/src):
expense/recompute.ts— new. 512 MiB / 300 s.portfolio/recompute.ts— new. 512 MiB / 120 s.index.ts— exports both.admin/recompute-kpis.ts— samples tax_reviews forunmapped_paise; writesunmapped_paise_total+unmapped_users_countontoadmin_kpis/current.
Rules (packages/shared/src/rules/v1):
income-summary.ts— newinferItrForm(summary)helper.ITR-1by default;ITR-2when CG / foreign / total > ₹50L.orchestrator.ts— stampsitr_formon the persisted review.
Schemas:
tax-review.ts— newitr_form: "ITR-1" | "ITR-2"optional field.admin-kpi.ts— newunmapped_paise_total+unmapped_users_countfields.- Client
store/tax-review.ts+utils/itr-form.ts— reads the persisteditr_formwhen present.
Tests:
yarn typecheck— 7 workspaces green.yarn test— 424 functions + 122 shared + 185 mobile = 731 tests pass (no regressions; shared tax-computer tests unchanged).yarn lint— 0 errors.yarn format:check— clean.
Status
Active.
Sources
- Fresh-perspective audit follow-on, 2026-07-09.
- Related decisions: 2026-07-09-dedup-content-hash-and-purge (Phase A), 2026-07-09-mapper-rollup-contract (Phase C — this observability tile watches the contract that phase introduced), 2026-07-09-recovery-and-compute-correctness (Phase D + E).
- Related concepts / entities: pillar-tax, pillar-expense, pillar-portfolio, upload-only.
Every project of mine is written down like this.
Read the résumé