Skip to main content

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.md per-EPIC scale-out (BF-001 pilot template). Source of truth for "% passing": hmp-docs/docs/platform-registry/2026-05-16-13-flow-demo-readiness.md until re-measure; hmp-analytics-service/CLAUDE.md is the codebase ground-truth.

Status legend​

StatusMeaning
BuiltCode exists + tests exist + tests pass
Built-UntestedCode exists, no automated test coverage (or test exists but is a stub)
PartialCode exists for some acceptance criteria, gaps for others
StubCode is a placeholder / TODO / returns mock data
Not StartedNo code

Summary​

MetricCount
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-Built5 (US-012-002, US-012-003 v2-catalog, US-012-009-housekeeping, US-012-010-fnb, US-012-011-aging-receivables)
Built-Untested1 (US-012-015 — Plan 6 formatters all have spec coverage but no flow-test for PDF letterhead)
Partial3 (US-012-009-pnl, US-012B-02, US-012B-03)
Stub1 (US-012-010-balance-sheet-and-cash-flow — no controller, no chartOfAccounts consumer)
Not Started1 (US-012B-01 — Channel Commission report — has MV but no ReportDefinition)
Drift findings12 (D-001..D-012; 4 P0 / 6 P1 / 2 P2)
[SME-PROPOSED] refinements21

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-report
  • hmp-analytics-service/src/reporting/reports/cancelled-reservation.definition.ts
  • hmp-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 slice
  • hmp-analytics-service/src/reporting/reports/manager-flash-report.definition.ts
  • hmp-analytics-service/src/reporting/controller/reports-v2.controller.ts — GET /reports/v2/catalog, POST /reports/v2/:id/run, GET /reports/v2/:id/runs
  • hmp-web-dashboard/src/app/(dashboard)/reports/v2/page.tsx — catalog list
  • hmp-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 definition
  • hmp-analytics-service/src/reporting/controller/reports-v2.controller.spec.ts
  • hmp-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/daily retired 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-receivables endpoints cited in the EP-012B EPIC body §4.1 table are retired. The EPIC body is stale.
  • in-house-report was 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.ts carries 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.tsx surfaces 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.ts
  • hmp-analytics-service/src/reporting/reports/source-wise-revenue.definition.ts — mv → MvRevenueBySource
  • hmp-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.ts
  • hmp-analytics-service/src/reporting/comparator/*.spec.ts — LyEnricher, BudgetEnricher, ForecastEnricher (Plan 5/3.2)

Flow tests:

  • flow-10-reports.spec.ts :: S1 revenue-daily-report, S2 revenue-monthly-report, S6 source-wise-revenue-report, S7 room-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.md verdict "PASS_WITH_NOTES, GAP-1 no revenue-by-room-type" is stale by ~7 weeks: room-type-revenue-report shipped W2.D.13 on 2026-05-06 and MvRoomTypeRevenue is populated by FolioConsumer.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 taxAmount to MvDailySalesRegister but revenue reports report totalRoomRevenue as a single figure. Indian finance teams need the split per HSN bucket for monthly reconciliation.
  • MvRevenueBySegment source field is reservation source (Direct/MMT/Goibibo/Booking/Walk-in/Corporate) — there is no separate marketSegment (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-report and revenue-monthly-report, reading from MvDailySalesRegister.taxAmount joined with MvEInvoiceRegister.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-report and revenue-monthly-report SHALL set comparative: { ly: {…}, budget: { metric: 'revenue' }, forecast: { metric: 'revenue' } }. The runner UI surfaces a Compare to: [LY|Budget|Forecast|None] toggle. Empty-state cells MUST carry honest reason codes (NO_LY_DATA, NO_BUDGET_UPLOADED, WARMING_UP per hmp-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 aggregation
  • hmp-analytics-service/src/reporting/reports/folio-register.definition.ts — live-proxy → frontdesk
  • hmp-analytics-service/src/reporting/reports/expense.definition.ts — mv → MvDailyExpense (populated by ExpenseConsumer on expense.recorded from property-service Plan 11)
  • hmp-analytics-service/src/reporting/reports/payment-collection.definition.ts — mv → MvPaymentCollection
  • hmp-property-service Plan 11 expense module — produces expense.recorded events
  • [CITED-BUT-NOT-FOUND] — no pnl.definition.ts exists; no chart-of-accounts mapper in src/reporting/reports/shared/
  • [CITED-BUT-NOT-FOUND] — hmp-property-service PropertyConfig.chartOfAccounts JSON 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: MvDailyExpense is populated but there is no chartOfAccounts mapping translation from raw expense category → 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 ReportDefinition pnl-report in hmp-analytics-service/src/reporting/reports/pnl.definition.ts. Backing: aggregate query over MvDailySalesRegister (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 chartOfAccounts config 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 chartOfAccounts is empty AND MvDailyExpense rows exist with category values not in any mapping, the report SHALL fail closed with a CHART_OF_ACCOUNTS_INCOMPLETE error 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-020 and a targetRelease: 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-report mv-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-report
  • hmp-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 :: S8 ageing-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-report MV-backed for trend, ar-aging-detail live-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.md against the 3-definition v2 surface; update verdict to Built.
  • [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 → MvHkStaffProductivity
  • hmp-analytics-service/src/reporting/reports/discrepancy-report.definition.ts — live-proxy → housekeeping
  • hmp-analytics-service/src/reporting/reports/lost-and-found-register.definition.ts — live-proxy
  • hmp-analytics-service/src/reporting/reports/room-status-detail.definition.ts — live-proxy
  • hmp-analytics-service/src/consumers/housekeeping.consumer.ts — extended Plan 2.1 to populate MvHkStaffProductivity when assignedTo present

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.
  • MvHkStaffProductivity includes inspectionScore + 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) and reworkCount (count of rooms re-cleaned same day) alongside tasksCompleted and avgCleanTimeMin. 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 → MvPosRevenue
  • hmp-analytics-service/src/reporting/reports/item-wise-sales.definition.ts — mv → MvPosRevenue.itemWiseBreakdown JSON
  • hmp-analytics-service/src/reporting/reports/server-productivity.definition.ts — mv → MvPosRevenue.serverWiseBreakdown JSON
  • hmp-analytics-service/src/reporting/reports/kot-register.definition.ts — live-proxy → pos
  • hmp-analytics-service/src/reporting/reports/void-register.definition.ts, discount-register.definition.ts
  • hmp-analytics-service/src/consumers/pos.consumer.ts — populates MvPosRevenue (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 order
  • hmp-analytics-service/src/reporting/format/xlsx.formatter.ts — exceljs, bold + light-grey header, col width 16/14, sheet 31 char cap
  • hmp-analytics-service/src/reporting/format/pdf.formatter.ts — Puppeteer + puppeteer-cluster (maxConcurrency=3) + Handlebars templates; SKIP_PUPPETEER=true opt-out for test/CI
  • hmp-analytics-service/src/reporting/format/xml.formatter.ts — xmlbuilder2; gated to frro-c-form only
  • hmp-analytics-service/src/reporting/format/json.formatter.ts
  • hmp-analytics-service/src/reporting/format/templates/ — Handlebars templates; <def.id>.hbs then generic-tabular.hbs fallback
  • hmp-analytics-service/src/reporting/exports/report-export.service.ts — sync ≤500 rows; queued via BullMQ otherwise; Redis-backed job state (key report-exports:{tenantId}:{jobId}, TTL 24h); S3 presigned URL when AWS_REPORT_EXPORTS_BUCKET set, else base64 data-URL
  • hmp-analytics-service/src/reporting/exports/report-exports.controller.ts — POST /reports/export/:type, GET /reports/exports/:jobId, GET /reports/exports
  • hmp-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.ts
  • report-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 when POST /api/reports/v2/:id/run?format=pdf is 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.md verdict "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 ReportRun with userId/filters/format/IP/UA/duration/rowCount (5-year retention)
  • PDF templates per-report are the open work — today only generic-tabular.hbs exists 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>.hbs templates exist for the 63 definitions today; everything falls through to generic-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>.hbs template when present; otherwise fall back to branded-tabular.hbs (a new branded base that pulls property.logoUrl, property.gstin, property.name, generation timestamp in IST, page x of N footer, repeated table headers across pages). generic-tabular.hbs is deleted."
  • [SME-PROPOSED] Add AC: "CSV formatter SHALL emit a UTF-8 BOM (\xEF\xBB\xBF) by default; configurable via BOM=false query 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 — populates MvChannelPerformance + MvCommission (Plan 2)
  • hmp-analytics-service/src/consumers/reservation.consumer.ts — also populates MvCommission (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-report returns per-channel commission amount/booking count/revenue; commission-statement is the OTA-payment-side view; channel-production is the lift/efficiency comparative view; ota-commission-statement-detail is 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 by MvCommission.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-report SHALL include netRevenue = grossRevenue − commissionAmount as 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 covered
  • hmp-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-service inventory 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 Management covering: Ingredient master with cost-per-unit, Recipe linkage to MenuItem, daily stock movement events (inventory.stock.consumed), pos-cogs-report definition reading from a new MvDailyCogs MV.
  • [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 + Handlebars
  • hmp-analytics-service/src/reporting/format/templates/ — generic-tabular.hbs only 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.json blob 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) from PropertyServiceClient.getProperty(propertyId) (5-min cache), pass to Handlebars context as {{property}}, and the branded-tabular.hbs template 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/schedule at /api/reports/v2/saved-views
  • hmp-analytics-service/src/reporting/comparator/ — ReportComparator, LyEnricher, BudgetEnricher, ForecastEnricher, comparative-cell.helper.ts
  • hmp-analytics-service/src/reporting/scheduler/report-scheduler.service.ts — BullMQ recurring jobs
  • hmp-analytics-service/src/reporting/distribution/ — EmailChannel, WhatsappChannel, S3Channel, SftpChannel, DistributionService, NotificationHttpClient
  • hmp-analytics-service/src/reporting/distribution-lists/ — 5 endpoints CRUD
  • hmp-web-dashboard/src/app/(dashboard)/reports/v2/saved/page.tsx — Saved Views list
  • hmp-web-dashboard/src/components/modules/reports/v2/save-view-modal.tsx — save dialog
  • hmp-web-dashboard/src/components/modules/reports/v2/pinned-reports-widget.tsx — home-page widget
  • hmp-web-dashboard/src/lib/api/saved-views.ts — TS client
  • hmp-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.ts
  • distribution/*.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.4
    • US-012B-05 Comparative Cells (vs LY / Budget / Forecast) (P1, 8pt) — Plan 5 + Plan 3.2
    • US-012B-06 Scheduled Report Distribution (Email/WhatsApp/S3/SFTP) (P1, 8pt) — Plan 7
    • US-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.ts already satisfies most of US-012B-04 + US-012B-06.

Closing notes (carry-forwards to sidecar)​

  1. 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-XX and US-012B-XX IDs should map to US-012-NNN rows in the catalog (or be promoted to new catalog rows for US-012B-01/02/03 + the 4 proposed in D-012).
  2. 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.
  3. 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.
  4. 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.md and code paths before drafting.