EP-012B — Financial Reports RTM
EPIC: EP-012B Financial Reports Parent EPIC: EP-012 Analytics, Reporting & Night Audit Reconciled: 2026-05-25 Wave-1 RTM — authored under
PLAYBOOK.mdper-EPIC scale-out (BF-001 pilot template). Source of truth for "% passing":hmp-docs/docs/platform-registry/2026-05-16-13-flow-demo-readiness.mduntil re-measure;hmp-analytics-service/CLAUDE.mdis the codebase ground-truth.
Status legend
| Status | Meaning |
|---|---|
Built | Code exists + tests exist + tests pass |
Built-Untested | Code exists, no automated test coverage (or test exists but is a stub) |
Partial | Code exists for some acceptance criteria, gaps for others |
Stub | Code is a placeholder / TODO / returns mock data |
Not Started | No code |
Summary
| Metric | Count |
|---|---|
| Total stories (EP-012B body) | 11 |
| Catalog rows (EP-012 parent, financial-reports slice) | 11 (US-012-002/003/005/006/009/010/011/012/015/016/018/020 + EP-012B's 3 -01/02/03) |
| Catalog claims "Built" | 4 |
| Catalog claims "Partial" | 6 |
| Catalog claims "Not Started" | 1 |
| Verified-Built | 5 (US-012-002, US-012-003 v2-catalog, US-012-009-housekeeping, US-012-010-fnb, US-012-011-aging-receivables) |
| Built-Untested | 1 (US-012-015 — Plan 6 formatters all have spec coverage but no flow-test for PDF letterhead) |
| Partial | 3 (US-012-009-pnl, US-012B-02, US-012B-03) |
| Stub | 1 (US-012-010-balance-sheet-and-cash-flow — no controller, no chartOfAccounts consumer) |
| Not Started | 1 (US-012B-01 — Channel Commission report — has MV but no ReportDefinition) |
| Drift findings | 12 (D-001..D-012; 4 P0 / 6 P1 / 2 P2) |
| [SME-PROPOSED] refinements | 21 |
US-012-002 — Daily Operational Reports (Arrivals / Departures / In-House / No-Show / VIP)
Catalog status: Built (P0, 3 points, parent EP-012 US-012-002) Verified status: Built
Code locations:
hmp-analytics-service/src/reporting/reports/arrival.definition.ts—arrival-report(mv → MvDailyArrivals)hmp-analytics-service/src/reporting/reports/departure.definition.ts—departure-report(mv → MvDailyDepartures)hmp-analytics-service/src/reporting/reports/no-show-reservation.definition.ts—no-show-reservation-reporthmp-analytics-service/src/reporting/reports/cancelled-reservation.definition.tshmp-analytics-service/src/reporting/reports/in-house.definition.ts— live-proxy → frontdesk/reporting-feeds/frontdesk/in-house(Plan 3 upgrade)hmp-analytics-service/src/reporting/reports/high-balance.definition.ts— VIP/high-balance slicehmp-analytics-service/src/reporting/reports/manager-flash-report.definition.tshmp-analytics-service/src/reporting/controller/reports-v2.controller.ts—GET /reports/v2/catalog,POST /reports/v2/:id/run,GET /reports/v2/:id/runshmp-web-dashboard/src/app/(dashboard)/reports/v2/page.tsx— catalog listhmp-web-dashboard/src/app/(dashboard)/reports/v2/[reportId]/page.tsx— runner / viewer
Unit + integration tests:
hmp-analytics-service/src/reporting/reports/arrival.definition.spec.ts,departure.definition.spec.ts,no-show-reservation.definition.spec.ts,cancelled-reservation.definition.spec.ts,in-house.definition.spec.ts,high-balance.definition.spec.ts,manager-flash-report.definition.spec.ts— one spec per definitionhmp-analytics-service/src/reporting/controller/reports-v2.controller.spec.tshmp-analytics-service/src/reporting/execution/*.spec.ts— execution pipeline (rbac → audit → resolver → redact → comparator → drill → format)
Flow tests:
hmp-infrastructure/testing/flow-tests/src/flows/flow-10-reports.spec.ts:: "v2 catalog — S1..S8" — 8 reports probed (revenue-daily, revenue-monthly, occupancy, in-house, einvoice, source-wise, room-type, ageing); empty MV rows acceptable; tenant-isolation S10. Per the 2026-05-16 demo doc this flow is rewritten for v2 (legacy/api/reports/revenue/dailyretired in Plan 1). (2026-05-16-13-flow-demo-readiness.md)hmp-infrastructure/testing/flow-tests/src/flows/reports-export.flow.spec.ts:: "catalogue / generate / history" — currently soft-skip scenarios; left as tripwires.
Real-backend smokes: [CITED-BUT-NOT-FOUND] — no e2e/real-backend/smoke-reports*.real.spec.ts enumerated in source map; verify.
E2E (Playwright): [CITED-BUT-NOT-FOUND] — generic /reports/v2/[reportId] page handles them; no per-report spec.
Use cases referencing this story: none direct; covered by EP-012-NEW-OPERATIONAL-REPORTS surface.
SME notes:
- Operational daily reports were fully migrated to the v2 catalog (Plan 3, 2026-04-30); the legacy
GET /reports/arrivals|departures|in-house|no-shows|fnb|housekeeping|aging-receivablesendpoints cited in the EP-012B EPIC body §4.1 table are retired. The EPIC body is stale. in-house-reportwas upgraded from MV → live-proxy in Plan 3 because in-house state mutates intra-day (check-ins/check-outs); the MV would have been minutes stale.high-balance.definition.tscarries the VIP slice (high-folio-balance highlight) — EPIC body conflates "VIP list" + "high-balance" as one operational concern, but the report definition is single-purpose.- Pinned widget at
hmp-web-dashboard/src/components/modules/reports/v2/pinned-reports-widget.tsxsurfaces a user's saved view list on the home page — fast-path to daily report consumption.
Drift findings:
D-001 — [P0] [EPIC-body-vs-code] EP-012B EPIC body §4.1 lists the legacy GET /reports/arrivals|departures|in-house|no-shows|housekeeping|fnb|aging-receivables controller surface as the current implementation. That controller was deleted in Plan 1 (2026-04-30) and replaced by /reports/v2/:id/run catalog-driven routes. A new engineer reading the EPIC will look for the wrong files. Status table in §4.1 also out-of-date by 7+ Plans (Plans 0/1/2/2.1/2.3/3/5/6/7/8/9 all landed after the doc was authored 2026-03-27).
- [SME-PROPOSED] Replace §4.1 table with the v2 catalog table from
hmp-analytics-service/CLAUDE.md"Plan 3 catalog (36 total)" + Plan 2.3 catalog (27 new = 63 total). - [SME-PROPOSED] Mark every legacy controller row "RETIRED Plan 1 2026-04-30; replaced by
POST /api/reports/v2/<id>/run" so the rewrite is discoverable.
US-012-003 — Revenue Reports by Source & Segment (catalog: US-012-003 P0; EPIC body: US-012-03)
Catalog status: Partial (P0, 5 points)
Verified status: Built — pre-Plan-3 review was stale; v2 catalog now ships revenue-daily/-monthly/source-wise/room-type/market-segment/country-wise definitions
Code locations:
hmp-analytics-service/src/reporting/reports/revenue-daily.definition.ts:16-95—revenue-daily-report(mv → MvDailyOccupancy + MvDailyKpi joined by date)hmp-analytics-service/src/reporting/reports/revenue-monthly.definition.tshmp-analytics-service/src/reporting/reports/source-wise-revenue.definition.ts— mv → MvRevenueBySourcehmp-analytics-service/src/reporting/reports/room-type-revenue.definition.ts— mv → MvRoomTypeRevenue (W2.D.13, 2026-05-06)hmp-analytics-service/src/reporting/reports/market-segment.definition.ts— mv → MvRevenueBySegment (Plan 2)hmp-analytics-service/src/reporting/reports/country-wise-statistics.definition.ts— mv → MvCountryStats (Plan 2)hmp-analytics-service/src/reporting/comparator/— LY / Budget / Forecast enrichers (Plan 5)hmp-web-dashboard/src/app/(dashboard)/reports/v2/[reportId]/page.tsx— runner
Unit + integration tests:
revenue-daily.definition.spec.ts,revenue-monthly.definition.spec.ts,source-wise-revenue.definition.spec.ts,room-type-revenue.definition.spec.ts,market-segment.definition.spec.ts,country-wise-statistics.definition.spec.tshmp-analytics-service/src/reporting/comparator/*.spec.ts— LyEnricher, BudgetEnricher, ForecastEnricher (Plan 5/3.2)
Flow tests:
flow-10-reports.spec.ts:: S1revenue-daily-report, S2revenue-monthly-report, S6source-wise-revenue-report, S7room-type-revenue-report— body shape + tenant-isolation; empty MVs accepted (2026-05-16-13-flow-demo-readiness.md)
Real-backend smokes: [CITED-BUT-NOT-FOUND] — verify
Use cases referencing this story: none direct.
SME notes:
- The legacy
reviews/EP-012/US-012-003-review.mdverdict "PASS_WITH_NOTES, GAP-1 no revenue-by-room-type" is stale by ~7 weeks:room-type-revenue-reportshipped W2.D.13 on 2026-05-06 andMvRoomTypeRevenueis populated byFolioConsumer.onChargePosted. Refresh the review. - Plan 5 Comparator wires LY (vs Last Year), Budget, and Forecast slots into every comparative-enabled definition. Revenue-daily currently does NOT carry
comparative: {ly,budget,forecast}in its definition despite the spec demanding YoY (AC-EP-012B-01 paraphrase). Mass-enabling comparative on the finance/analytics-category reports is a one-line change per definition (comparative.ly = {…}) — likely Plan 3.5 scope. - GST-inclusive vs GST-exclusive revenue split (review GAP-1) is still missing — folio writes
taxAmountto MvDailySalesRegister but revenue reports reporttotalRoomRevenueas a single figure. Indian finance teams need the split per HSN bucket for monthly reconciliation. MvRevenueBySegmentsource field is reservationsource(Direct/MMT/Goibibo/Booking/Walk-in/Corporate) — there is no separatemarketSegment(FIT/Group/Corporate/Government/Crew) taxonomy. The EPIC body §2 conflates "source" and "market segment" — they are different in industry practice.
Drift findings:
D-002 — [P1] [Story-incomplete + India-hospitality-critical] Revenue reports surface totalRoomRevenue without a GST-inclusive vs GST-exclusive vs net-of-tax breakdown. Finance teams must reconcile by HSN bucket (996311 room, 996331 restaurant, 998114 laundry, 999719 spa) for GSTR-3B; today they re-pivot in Excel.
- [SME-PROPOSED] Add AC: "Revenue measures SHALL be available in three modes:
revenueGross(incl tax),revenueNet(excl tax),taxAmount. Default display = gross; toggleable in the runner UI; CSV/XLSX export carries all three columns." - [SME-PROPOSED] Add AC: "Per-HSN-bucket revenue breakdown SHALL be available as a comparative slot or sub-table on
revenue-daily-reportandrevenue-monthly-report, reading fromMvDailySalesRegister.taxAmountjoined withMvEInvoiceRegister.hsnBreakdown."
D-003 — [P1] [Taxonomy-conflation] EPIC body §2 + AC-EP-012B-01 use "source" and "market segment" interchangeably, but the code keeps them in separate MVs (MvRevenueBySource vs MvRevenueBySegment) with different semantics. source = origin channel (OTA codename), segment = sales classification (FIT/Group/Corporate/Government/Crew/Crew/Allotment). New engineers patch the wrong MV and produce double-counted revenue.
- [SME-PROPOSED] Refine US-012-003 to split into US-012-003a (Revenue by Source/Channel) and US-012-003b (Revenue by Market Segment). Document the canonical taxonomy in
01-epic-catalog.md+ EPIC body §3.1.
D-004 — [P0] [Story-incomplete + missing-AC] Plan 5 Comparator ships LY / Budget / Forecast slots and the dashboard renderer already understands ComparativeCell, but the v2 revenue definitions do not actually enable comparative. AC-EP-012B-01 paraphrase ("STLY comparison") is therefore implementable-in-zero-lines yet not enabled.
- [SME-PROPOSED] Add AC: "
revenue-daily-reportandrevenue-monthly-reportSHALL setcomparative: { ly: {…}, budget: { metric: 'revenue' }, forecast: { metric: 'revenue' } }. The runner UI surfaces aCompare to: [LY|Budget|Forecast|None]toggle. Empty-state cells MUST carry honest reason codes (NO_LY_DATA,NO_BUDGET_UPLOADED,WARMING_UPperhmp-analytics-service/CLAUDE.md)."
US-012-009 — Financial P&L Statement (catalog: US-012-011 Financial Reports P&L+Balance-Sheet P1; EPIC body: US-012-09)
Catalog status: Partial (P1, 8 points — catalog row US-012-011 covers BOTH P&L and Balance Sheet)
Verified status: Partial — revenue side maps from folio + POS; expense side requires chartOfAccounts mapping consumer (not built)
Code locations:
hmp-analytics-service/src/reporting/reports/sale-register.definition.ts— folio + POS aggregationhmp-analytics-service/src/reporting/reports/folio-register.definition.ts— live-proxy → frontdeskhmp-analytics-service/src/reporting/reports/expense.definition.ts— mv → MvDailyExpense (populated by ExpenseConsumer onexpense.recordedfrom property-service Plan 11)hmp-analytics-service/src/reporting/reports/payment-collection.definition.ts— mv → MvPaymentCollectionhmp-property-servicePlan 11 expense module — producesexpense.recordedevents[CITED-BUT-NOT-FOUND]— nopnl.definition.tsexists; no chart-of-accounts mapper insrc/reporting/reports/shared/[CITED-BUT-NOT-FOUND]—hmp-property-servicePropertyConfig.chartOfAccountsJSON field (EPIC body §10 claims "already exists as Sprint 5 TenantConfig deliverable" — needs verification)
Unit + integration tests:
sale-register.definition.spec.ts,folio-register.definition.spec.ts,expense.definition.spec.ts,payment-collection.definition.spec.ts- No
pnl.definition.spec.ts
Flow tests: none
Use cases referencing this story: none
SME notes:
- The story claims "Revenue mapped from folio charges and POS transactions" — this is true, via
MvDailySalesRegister+MvRevenueBySource. The blocker is the expense side:MvDailyExpenseis populated but there is nochartOfAccountsmapping translation from raw expensecategory→ P&L line (Payroll / Utilities / Marketing / Engineering / OOE). - The "12 expense categories" example in the EPIC body §7 gherkin is a placeholder, not codified.
- "GOP / GOP %" computation is correct (Total Revenue − Total Expenses) but requires both sides; today's report would show negative GOP because expenses fall through as 0.
- Indian hospitality typically uses USALI (Uniform System of Accounts for the Lodging Industry) chart-of-accounts. The platform should ship USALI defaults out-of-box; clones per tenant.
- The expense ingestion pipeline (property-service Plan 11) is upstream — Wave-1 RTM should call this dependency out.
Drift findings:
D-005 — [P0] [Story-incomplete + dependency-unowned] No pnl.definition.ts exists. The EPIC body promises P&L for R4 (Q3 2026) but the chart-of-accounts mapper (the load-bearing dependency) is not in any current Plan. The §7 gherkin will never pass.
- [SME-PROPOSED] Author
ReportDefinitionpnl-reportinhmp-analytics-service/src/reporting/reports/pnl.definition.ts. Backing: aggregate query overMvDailySalesRegister(revenue side) +MvDailyExpense(expense side) +ChartOfAccountsMapper(translation layer). Definition emits 12 USALI-default categories: Room Revenue, F&B Revenue, Ancillary Revenue, Total Revenue, Room Expenses, F&B COGS, Payroll, Utilities, Marketing, Engineering, Admin & General, OOE, Total Expenses, GOP, GOP %. - [SME-PROPOSED] Add AC: "Tenants without a
chartOfAccountsconfig SHALL receive the USALI default mapping (12 categories) on first GET; the runner UI surfaces a 'Customize CoA' link to property-service/settings/finance/chart-of-accounts." - [SME-PROPOSED] Add AC: "If
chartOfAccountsis empty ANDMvDailyExpenserows exist withcategoryvalues not in any mapping, the report SHALL fail closed with aCHART_OF_ACCOUNTS_INCOMPLETEerror code listing the orphaned categories — never silently emit GOP from partial expenses."
US-012-010 — Balance Sheet & Cash Flow Reports (catalog: US-012-011 covers; EPIC body: US-012-10)
Catalog status: Not Started (P1, 13 points — split from US-012-011 in EPIC body)
Verified status: Stub — no controller, no ReportDefinition, no chart-of-accounts mapping, no manual-expense form
Code locations: NONE FOUND
Unit + integration tests: NONE FOUND
Flow tests: NONE FOUND
Use cases referencing this story: none
SME notes:
- Balance Sheet (Assets / Liabilities / Equity) and Cash Flow Statement (Operating / Investing / Financing) require manual expense + asset + liability input OR Tally/Zoho Books integration (EP-020).
- For Indian hospitality, owner-CA quarterly review is the primary audience; "managed accountant" persona uploads B/S via CSV today.
- The "Not Started" classification is honest.
Drift findings:
D-006 — [P2] [Out-of-scope-confirmed] Story body promises Balance Sheet + Cash Flow but explicitly defers the integration prerequisite (Tally / Zoho Books = EP-020 Out of Scope). The story is correctly Not Started but the EPIC body should make the dependency on EP-020 explicit so PMs don't schedule it ahead.
- [SME-PROPOSED] Add a new "Dependencies" row in EPIC body §8 wiring this story to EP-020 with status
Blocked-by-EP-020and atargetRelease: R5+callout.
US-012-011 — Aging Receivables Report (catalog: US-012-012 P1; EPIC body: US-012-11)
Catalog status: Partial (P1, 3 points)
Verified status: Built — ageing-report shipped Plan 1; ar-aging-detail Plan 2.3 live-proxy; city-ledger-report shipped Plan 3
Code locations:
hmp-analytics-service/src/reporting/reports/ageing.definition.ts—ageing-reportmv-backed buckets (current / 30 / 60 / 90+)hmp-analytics-service/src/reporting/reports/ar-aging-detail.definition.ts—ar-aging-detail(Plan 2.3, live-proxy → frontdesk)hmp-analytics-service/src/reporting/reports/city-ledger.definition.ts—city-ledger-reporthmp-frontdesk-service/src/folios/— owns city-ledger source-of-truth
Unit + integration tests:
ageing.definition.spec.ts,ar-aging-detail.definition.spec.ts,city-ledger.definition.spec.ts
Flow tests:
flow-10-reports.spec.ts:: S8ageing-report(2026-05-16-13-flow-demo-readiness.md)
Use cases referencing this story: none
SME notes:
- Aging buckets (current / 1-30 / 31-60 / 61-90 / 90+ days) match Indian accounting convention.
- City ledger is the FIT-vs-corporate distinction: corporate / TA bookings post to a ledger account per company; the city-ledger-report and ageing-report should join via
companyId. - The Plan 2.3 split (
ageing-reportMV-backed for trend,ar-aging-detaillive-proxy for current state) is the right pattern — MVs are eventually-consistent and AR teams need real-time. - Story does not specify the "ageing buckets" boundary policy. Indian RBI overdue classification kicks in at 90+ days for some segments; should be configurable per property.
Drift findings: (none — feature complete; review status "Partial" is stale)
- [SME-PROPOSED] Re-run
reviews/EP-012/US-012-012-review.mdagainst the 3-definition v2 surface; update verdict toBuilt. - [SME-PROPOSED] Add AC: "Ageing bucket boundaries SHALL be configurable per property via
PropertyConfig.ageingBuckets(default[0,30,60,90]); the report query honors the config."
US-012-012 — Housekeeping Productivity Report (catalog: US-012-009 P1; EPIC body: US-012-12)
Catalog status: Built (P1, 3 points)
Verified status: Built — Plan 2.1 added MvHkStaffProductivity; Plan 2.3 added the definition
Code locations:
hmp-analytics-service/src/reporting/reports/housekeeping-productivity.definition.ts— mv → MvHkStaffProductivityhmp-analytics-service/src/reporting/reports/discrepancy-report.definition.ts— live-proxy → housekeepinghmp-analytics-service/src/reporting/reports/lost-and-found-register.definition.ts— live-proxyhmp-analytics-service/src/reporting/reports/room-status-detail.definition.ts— live-proxyhmp-analytics-service/src/consumers/housekeeping.consumer.ts— extended Plan 2.1 to populateMvHkStaffProductivitywhenassignedTopresent
Unit + integration tests:
housekeeping-productivity.definition.spec.ts,discrepancy-report.definition.spec.ts,lost-and-found-register.definition.spec.ts,room-status-detail.definition.spec.ts
Flow tests: none specific
Use cases referencing this story: none
SME notes:
- 4 housekeeping definitions in the v2 catalog covers the EPIC body's "housekeeping productivity report" line item adequately.
MvHkStaffProductivityincludesinspectionScore+reworkCount— quality dimensions the EPIC body §3.1 doesn't enumerate.
Drift findings:
D-007 — [P2] [Story-incomplete] Housekeeping productivity report shipped with quality metrics (inspectionScore, reworkCount) the story body does not mention; ops will use them anyway, but DAU/MAU on those columns will be invisible to product.
- [SME-PROPOSED] Add AC: "Productivity report SHALL include
inspectionScore(0–100) andreworkCount(count of rooms re-cleaned same day) alongsidetasksCompletedandavgCleanTimeMin. Sort default =inspectionScore desc."
US-012-013 — F&B Revenue & Menu Analysis (catalog: US-012-010 P1; EPIC body: US-012-13)
Catalog status: Built (P1, 3 points)
Verified status: Built — pos-sales-register, item-wise-sales, server-productivity, kot-register, void-register, discount-register all shipped Plans 1/3/2.3
Code locations:
hmp-analytics-service/src/reporting/reports/pos-sales-register.definition.ts— mv → MvPosRevenuehmp-analytics-service/src/reporting/reports/item-wise-sales.definition.ts— mv → MvPosRevenue.itemWiseBreakdown JSONhmp-analytics-service/src/reporting/reports/server-productivity.definition.ts— mv → MvPosRevenue.serverWiseBreakdown JSONhmp-analytics-service/src/reporting/reports/kot-register.definition.ts— live-proxy → poshmp-analytics-service/src/reporting/reports/void-register.definition.ts,discount-register.definition.tshmp-analytics-service/src/consumers/pos.consumer.ts— populatesMvPosRevenue(incl. itemWise/serverWise JSON, avgKotTimeMin, kotCount AP-T10-006)
Unit + integration tests:
pos-sales-register.definition.spec.ts,item-wise-sales.definition.spec.ts,server-productivity.definition.spec.ts,kot-register.definition.spec.ts,void-register.definition.spec.ts,discount-register.definition.spec.ts
Flow tests: none specific
Use cases referencing this story: none
SME notes:
- 6 definitions in the v2 catalog covers the F&B revenue + menu surface comprehensively.
- COGS / Contribution Margin (US-012B-02) is the gap; this story is only about revenue + KOT throughput.
Drift findings: (none)
US-012-015 — Data Export (CSV, PDF, Excel) (catalog: US-012-006 P1; EPIC body: US-012-15)
Catalog status: Partial (P1, 3 points — catalog row 006) Verified status: Built-Untested — Plan 6 export pipeline shipped 5 formatters all with spec coverage; no flow-test asserting hotel-letterhead PDF or Tally-importable XLSX
Code locations:
hmp-analytics-service/src/reporting/format/csv.formatter.ts— RFC 4180, CRLF, schema-driven column orderhmp-analytics-service/src/reporting/format/xlsx.formatter.ts— exceljs, bold + light-grey header, col width 16/14, sheet 31 char caphmp-analytics-service/src/reporting/format/pdf.formatter.ts— Puppeteer + puppeteer-cluster (maxConcurrency=3) + Handlebars templates;SKIP_PUPPETEER=trueopt-out for test/CIhmp-analytics-service/src/reporting/format/xml.formatter.ts— xmlbuilder2; gated tofrro-c-formonlyhmp-analytics-service/src/reporting/format/json.formatter.tshmp-analytics-service/src/reporting/format/templates/— Handlebars templates;<def.id>.hbsthengeneric-tabular.hbsfallbackhmp-analytics-service/src/reporting/exports/report-export.service.ts— sync ≤500 rows; queued via BullMQ otherwise; Redis-backed job state (keyreport-exports:{tenantId}:{jobId}, TTL 24h); S3 presigned URL whenAWS_REPORT_EXPORTS_BUCKETset, else base64 data-URLhmp-analytics-service/src/reporting/exports/report-exports.controller.ts—POST /reports/export/:type,GET /reports/exports/:jobId,GET /reports/exportshmp-analytics-service/src/reporting/exports/storage/—S3ExportStorage(Plan 6, @Optional)hmp-web-dashboard/src/app/(dashboard)/reports/v2/[reportId]/page.tsx— export dropdown
Unit + integration tests:
csv.formatter.spec.ts,xlsx.formatter.spec.ts,pdf.formatter.spec.ts,xml.formatter.spec.ts,json.formatter.spec.ts,formatter-registry.spec.tsreport-export.service.spec.ts,report-exports.controller.spec.ts
Flow tests:
reports-export.flow.spec.ts— currently soft-skip (legacy/api/reports/export/...candidates). Tripwire stays in place; flips whenPOST /api/reports/v2/:id/run?format=pdfis asserted.
Real-backend smokes: [CITED-BUT-NOT-FOUND]
Use cases referencing this story: none
SME notes:
- The legacy
reviews/EP-012/US-012-006-review.mdverdict "FAIL — no CSV, no multi-sheet XLSX, no PDF letterhead, no async export" is completely stale. All those gaps closed in Plan 6 (2026-04-30):- CSV: shipped, RFC 4180, schema-driven, CRLF
- XLSX: shipped, exceljs, bold header, sheet name cap
- PDF: shipped, Puppeteer + Handlebars, requires Chromium in Docker (
apk add chromium nss freetype) - Async: shipped, BullMQ-queued, Redis state, S3 presigned URL output
- Audit: every export writes
ReportRunwith userId/filters/format/IP/UA/duration/rowCount (5-year retention)
- PDF templates per-report are the open work — today only
generic-tabular.hbsexists for most definitions. Hotel-letterhead branding (logo, GSTIN, footer "Generated by HMP on YYYY-MM-DD") is not wired into the Handlebars template loader. - No
<def.id>.hbstemplates exist for the 63 definitions today; everything falls through togeneric-tabular.hbs. Branded PDFs require either per-report templates OR a brandable base. - UTF-8 BOM in CSV (per review GAP-10 for Hindi/Devanagari Excel compat) — verify in spec, not in scope of the formatter today.
Drift findings:
D-008 — [P0] [Review-vs-code] reviews/EP-012/US-012-006-review.md verdict FAIL is stale; v2 export pipeline (Plan 6, 2026-04-30) closed all 12 review-GAPs. New reviewers reading the file alone would re-open closed work.
- [SME-PROPOSED] Refresh the review to verdict
PASS_WITH_NOTES: the surface is shipped; hotel-letterhead per-report templates + UTF-8 BOM are the residual work.
D-009 — [P1] [Story-incomplete] No per-report <def.id>.hbs templates exist; all 63 definitions fall through to generic-tabular.hbs. AC-EP-012B-05 ("PDF export includes hotel letterhead, report title, date, page numbers, properly formatted INR amounts") is not satisfied today.
- [SME-PROPOSED] Add AC: "PDF formatter SHALL load a
<def.id>.hbstemplate when present; otherwise fall back tobranded-tabular.hbs(a new branded base that pullsproperty.logoUrl,property.gstin,property.name, generation timestamp in IST, page x of N footer, repeated table headers across pages).generic-tabular.hbsis deleted." - [SME-PROPOSED] Add AC: "CSV formatter SHALL emit a UTF-8 BOM (
\xEF\xBB\xBF) by default; configurable viaBOM=falsequery param for downstream consumers (Tally) that reject BOM."
US-012B-01 — Channel Commission Report (EPIC body new ID; no catalog row)
Catalog status: Not Started (P1, 8 points — per EPIC body §6 table)
Verified status: Partial — commission-report and commission-statement shipped Plan 3 (2026-04-30); EPIC body §4.1 + §6 table both wrong
Code locations:
hmp-analytics-service/src/reporting/reports/commission.definition.ts—commission-report(mv → MvCommission, Plan 3)hmp-analytics-service/src/reporting/reports/commission-statement.definition.ts—commission-statement(mv → MvCommission, Plan 3)hmp-analytics-service/src/reporting/reports/channel-production.definition.ts—channel-production(mv → MvChannelPerformance, comparative.ly, Plan 2.3)hmp-analytics-service/src/reporting/reports/ota-commission-statement-detail.definition.ts—ota-commission-statement-detail(Plan 2.3)hmp-analytics-service/src/consumers/channel.consumer.ts— populatesMvChannelPerformance+MvCommission(Plan 2)hmp-analytics-service/src/consumers/reservation.consumer.ts— also populatesMvCommission(Plan 2)
Unit + integration tests:
commission.definition.spec.ts,commission-statement.definition.spec.ts,channel-production.definition.spec.ts,ota-commission-statement-detail.definition.spec.ts
Flow tests: none specific
Use cases referencing this story: none
SME notes:
- The Wave-1 verify-first finding here is significant: the EPIC body §4.1 says "Channel Commission report not built" and §5 GAP-4 lists it as a Priority P1 gap, but 4 commission-flavor definitions are live in the v2 catalog as of Plan 3 (2026-04-30) and Plan 2.3 (2026-04-30).
commission-reportreturns per-channel commission amount/booking count/revenue;commission-statementis the OTA-payment-side view;channel-productionis the lift/efficiency comparative view;ota-commission-statement-detailis a Plan 2.3 detail-line drill-down.- The "Net Revenue after commission" computation in the EPIC body §7 gherkin is
gross − commissionAmount— straightforward and supported byMvCommission.commissionAmount. - "Best channel by net revenue" summary is one extra reduction in the runner UI — not currently wired.
Drift findings:
D-010 — [P0] [Catalog-vs-code + EPIC-vs-code] EPIC body §4.1, §5 GAP-4, §6 user-story-table all claim "Channel Commission Report Not Started, P1, 8 points". Reality: 4 commission/channel-production definitions are SHIPPED. A new Sprint planning will schedule 8 points of duplicate work.
- [SME-PROPOSED] Bump catalog status to Built; rewrite §4.1 row "Channel Commission" → "Built (4 v2 definitions: commission-report, commission-statement, channel-production, ota-commission-statement-detail)".
- [SME-PROPOSED] Add AC: "
commission-reportSHALL includenetRevenue = grossRevenue − commissionAmountas a computed column and surface a 'Best channel by net revenue' summary card at the top of the runner page."
US-012B-02 — F&B Cost Analysis (COGS + Contribution Margin) (EPIC body new ID; no catalog row)
Catalog status: Partial (P2, 8 points — per EPIC body §6 table) Verified status: Partial — revenue + KOT throughput shipped (US-012-013); COGS depends on Inventory module which is not built (EPIC body §5 GAP-5; AP-T10-006 says POS Inventory Phase = Partial)
Code locations:
hmp-analytics-service/src/reporting/reports/pos-sales-register.definition.ts— revenue side coveredhmp-analytics-service/src/reporting/reports/item-wise-sales.definition.ts— per-item revenue covered[CITED-BUT-NOT-FOUND]—pos-cogs.definition.ts(no F&B COGS report exists)[CITED-BUT-NOT-FOUND]—hmp-pos-serviceinventory module (Plan 11-ish? unclear)
Unit + integration tests: revenue side covered above; no COGS spec
Flow tests: none
Use cases referencing this story: none
SME notes:
- COGS = sum of (item_recipe_ingredient_cost × quantity_sold) — requires a Recipe entity per menu item AND an Ingredient inventory with cost-per-unit. The inventory module is the prerequisite; without it, COGS is just a manual percentage estimate (typically 28–32% for F&B in Indian hotels).
- Contribution Margin = (Revenue − COGS) / Revenue × 100. Story body uses this term but does not define the formula.
- F&B Manager persona is the primary user — daily review of contribution margin by menu category drives the dynamic pricing of dishes (esp. high-cost imported items).
Drift findings:
D-011 — [P1] [Story-incomplete + dependency-unowned] Inventory module ownership is unclear — the EPIC body §5 GAP-5 says "F&B COGS requires inventory integration (not yet built)" but no Plan / EPIC / ticket carries the work. Story will indefinitely be Partial.
- [SME-PROPOSED] Author a new EPIC
EP-007B Inventory & Recipe Managementcovering: Ingredient master with cost-per-unit, Recipe linkage to MenuItem, daily stock movement events (inventory.stock.consumed),pos-cogs-reportdefinition reading from a newMvDailyCogsMV. - [SME-PROPOSED] Add AC: "F&B Cost Analysis report SHALL be
Blocked-by-EP-007B. Until then, the dashboard surfaces a placeholder COGS card with the message 'Inventory module pending — upload manual COGS via Settings → Finance → F&B Cost Override' and accepts a flat per-category percentage override (default 30%)."
US-012B-03 — PDF Export Formatting (Hotel Letterhead) (EPIC body new ID; no catalog row)
Catalog status: Partial (P2, 5 points — per EPIC body §6 table)
Verified status: Partial — PdfFormatter shipped Plan 6; no <def.id>.hbs per-report templates exist; only generic-tabular.hbs
Code locations:
hmp-analytics-service/src/reporting/format/pdf.formatter.ts— Plan 6 Puppeteer + Handlebarshmp-analytics-service/src/reporting/format/templates/—generic-tabular.hbsonly today
Unit + integration tests: pdf.formatter.spec.ts (with SKIP_PUPPETEER=true in CI)
Flow tests: none specific
Use cases referencing this story: none
SME notes:
- See D-009 above — this story is a direct duplicate of D-009.
- Hotel-letterhead branding requires per-property logo/GSTIN/name pulled from property-service; the PDF template loader does not yet do that lookup.
- Plan 9 (Onboarding seed) could ship a per-property
branding.jsonblob fetched once per report run; cache-friendly.
Drift findings: (carries D-009)
- [SME-PROPOSED] Merge this story into US-012-015 (D-009 SME-PROPOSED already covers the work).
- [SME-PROPOSED] If left separate: add AC "PDF formatter SHALL fetch
(logoUrl, gstin, name, address, phone, emailDomain)fromPropertyServiceClient.getProperty(propertyId)(5-min cache), pass to Handlebars context as{{property}}, and thebranded-tabular.hbstemplate surfaces these in a header band."
Cross-cutting: Save Views + Pinned Reports + Scheduling + Comparator (no catalog row — was Plan 3.4 + Plan 5 + Plan 7)
Catalog status: not catalogued Verified status: Built — Plan 3.4 shipped Save Views; Plan 5 shipped Comparator (LY/Budget/Forecast); Plan 7 shipped Scheduler + Distribution; dashboard pinned-reports widget shipped
Code locations:
hmp-analytics-service/src/reporting/saved-views/—POST/GET/GET:id/PATCH/DELETE/POST:id/scheduleat/api/reports/v2/saved-viewshmp-analytics-service/src/reporting/comparator/—ReportComparator,LyEnricher,BudgetEnricher,ForecastEnricher,comparative-cell.helper.tshmp-analytics-service/src/reporting/scheduler/report-scheduler.service.ts— BullMQ recurring jobshmp-analytics-service/src/reporting/distribution/—EmailChannel,WhatsappChannel,S3Channel,SftpChannel,DistributionService,NotificationHttpClienthmp-analytics-service/src/reporting/distribution-lists/— 5 endpoints CRUDhmp-web-dashboard/src/app/(dashboard)/reports/v2/saved/page.tsx— Saved Views listhmp-web-dashboard/src/components/modules/reports/v2/save-view-modal.tsx— save dialoghmp-web-dashboard/src/components/modules/reports/v2/pinned-reports-widget.tsx— home-page widgethmp-web-dashboard/src/lib/api/saved-views.ts— TS clienthmp-web-dashboard/src/app/(dashboard)/keynova.tsx— embeds pinned widget on home
Unit + integration tests:
saved-views/*.spec.ts(RBAC: ADMIN+ for shared=true)comparator/*.spec.ts(LY, Budget, Forecast — empty-state reasons)scheduler/*.spec.tsdistribution/*.spec.ts(email/whatsapp/s3/sftp + UnsupportedChannelError)save-view-modal.test.tsx,pinned-reports-widget.test.tsx,saved-view-dialogs.test.tsx
Flow tests:
hmp-infrastructure/testing/flow-tests/src/flows/saved-views-schedules.flow.spec.ts:: 8 scenarios — create private + list + patch pinnedToHome + filter pinned + create shared (admin) + convert to schedule + list schedules + delete
Use cases referencing this story: none
SME notes:
- This is a major shipped feature surface with no catalog row — classic Property #1 audit pattern (orphan code). Save Views + Pinned widget + Comparator + Scheduler are ~6 weeks of work the catalog doesn't reflect.
- Plan 3.4 (Save Views), Plan 5 (Comparator), Plan 7 (Scheduler/Distribution) were authored 2026-04-30 / 2026-05-01 — predate the EPIC body's last-updated date (2026-03-27) by ~5 weeks but the body was never refreshed.
- The pinned-reports-widget is the primary daily-driver UI element on the home page; product analytics on it should be tracked from launch.
Drift findings:
D-012 — [P0] [Orphaned-code + missing-story] Save Views (CRUD + pinned + shared), Comparator (LY/Budget/Forecast with honest empty-states), Scheduler (BullMQ recurring jobs), Distribution (email/whatsapp/s3/sftp), and the home-page pinned-reports widget are all SHIPPED but have no catalog story. No PM can plan day-2 features (e.g. shared-view-permissions, schedule-failure-retry-alert) without a story to attach them to.
- [SME-PROPOSED] Author 4 new stories on the EP-012B sidecar:
US-012B-04 Saved Views with Pin/Share(P1, 5pt) — Plan 3.4US-012B-05 Comparative Cells (vs LY / Budget / Forecast)(P1, 8pt) — Plan 5 + Plan 3.2US-012B-06 Scheduled Report Distribution (Email/WhatsApp/S3/SFTP)(P1, 8pt) — Plan 7US-012B-07 Pinned Reports Home Widget(P2, 3pt) — UI follow-on
- [SME-PROPOSED] Each new story carries an AC for end-to-end flow-test coverage;
saved-views-schedules.flow.spec.tsalready satisfies most of US-012B-04 + US-012B-06.
Closing notes (carry-forwards to sidecar)
- Numbering drift between catalog (US-012-NNN under EP-012 parent) and EPIC body (US-012-NN / US-012B-NN under sub-epic) — repeats the BF-001 D-007 pattern. PLAYBOOK calls this out as a P0 reconciliation. Adopt the catalog numbering as canonical; the EPIC body's
US-012-XXandUS-012B-XXIDs should map toUS-012-NNNrows in the catalog (or be promoted to new catalog rows for US-012B-01/02/03 + the 4 proposed in D-012). - EPIC body §4.1 + §4.2 status tables are ~7 weeks stale — Plans 0/1/2/2.1/2.3/3/5/6/7/8/9 all landed after the doc's last-updated date. The "Partial" / "Not Started" verdicts on Channel Commission, P&L expense side, PDF export, and others are misleading.
- Plan 3 v2 catalog (36 reports) + Plan 2.3 (27 more = 63 total) + Plan 5 Comparator + Plan 7 Distribution = a fundamental architecture pivot away from per-report controller endpoints toward a definition-registry + execution-pipeline model. The EPIC body still describes the pre-pivot world.
- 5 verified-already-shipped finds in this RTM (D-001, D-008, D-010 + parts of US-012-003 / US-012-013) apply the [[feedback_verify-engineering-debt-claims-in-code]] memory: every "Partial" / "Not Started" claim in the EPIC body was re-checked against
hmp-analytics-service/CLAUDE.mdand code paths before drafting.