Reports Accuracy + Completeness Validation — Findings/Fix List
LIVE-CONFIRMED ON TEST (2026-07-11, property 96a0fdb2, via POST /api/reports/v2//run)
Not false positives — verified against the deployed env:
- HTTP 500 on EVERY run (P-A confirmed): city-ledger-report, in-house-report, high-balance-report, meal-plan-report, night-audit-report, room-status-report, police-register, frro-c-form, folio-register. (9 reports fully broken in production RIGHT NOW.)
- Returns 0 rows (P-C empty confirmed): tax-audit-trail, booking-enquiry-report, rate-parity-audit.
- Controls render fine: dsr (3 rows), occupancy-report (11), manager-flash-report (1) — so the run-endpoint + property data are healthy; the broken ones are genuinely broken.
- (The full P-A/P-B set is ~19 reports; only a sample was live-run. Every live-proxy report shares the identical mapper defect, so the same 500 is expected across the set once its required param is missing.)
SME (domain accuracy) + BA (completeness) review of all 75 analytics reports. Live-reconciliation harness deferred to a follow-up wave (per operator decision). Severity: CRITICAL (broken/legally wrong) · MATERIAL (wrong number) · MINOR/GAP (incomplete).
Status legend: [CODE-GROUNDED] verifiable in code now · [UNVERIFIED] domain judgment / cross-repo, needs live check.
Wave 1 — Statutory/Tax (7) + Core Money (10) — DONE
CRITICAL — structurally broken (report is empty or 500s on every run)
- tax-audit-trail [CODE-GROUNDED] — queries analytics
EventStorefor event types NO consumer writes (only banquet/channel/tenant-import write there); 2 of 4 target event types (folio.tax.adjusted,pos.order.voided) don't exist in the platform at all. → a CERT-In/GST audit report that structurally always returns 0 rows (false "no tax events"). Same bug class as #7588 (einvoice), not applied here. - night-audit [CODE-GROUNDED] —
liveQueryMappernever sendspropertyId; the frontdesk endpoint hard-requires it → every run 500s. - police-register [CODE-GROUNDED] —
liveQueryMapperomitspropertyId+date(frontdesk requires both) → every standard run 400s. (AB#5213 mapper bug class.) - frro-c-form [CODE-GROUNDED] — (a) same missing
propertyId/date→ 400s; (b) the C-Form XML formatter reads field names (passportOrIdNumber/checkinDate/expectedDeparture) that DON'T match the report schema (passportNumber/checkInDate/no-departure) → every filed C-Form ships with blank passport + check-in/out dates; (c) schema is structurally incomplete vs the immigration.gov.in Form C (missing DOB, visa validity, arrival-in-India, purpose). - gst-report [CODE-GROUNDED] — GSTR-3B half queries
EventStorefor deprecated event names (likely always empty) AND ignores the period filter entirely (unbounded historical scan) while the e-invoice half in the SAME report IS period-scoped → the two halves answer different questions; B2B/B2C split iscompanyId-based, disagreeing with the correct GSTIN-based GSTR-1 → GSTR-1↔3B reconciliation breaks; composite rows double-count tax if summed.
CRITICAL/MATERIAL — wrong number (accuracy)
- revenue-daily [UNVERIFIED→needs live] — room revenue posted 100% to the ARRIVAL DATE at booking time, not spread per occupied night (booking-basis vs occupancy-basis error) → wrong for every future/un-audited date; no-show revenue never reversed (overstates ADR/RevPAR); occupancy denominator uses raw
totalRoomsnot sellable (excl OOO); ÷0 → 0 not null. - revenue-monthly [UNVERIFIED→needs live] — inherits the above PLUS ADR/RevPAR/occupancy computed as an AVERAGE-OF-DAILY-RATIOS instead of re-aggregated
Σrev/Σrooms(mathematically wrong when room counts vary); occupancy reads a night-audit-only column so future days average in 0%.
MAJOR-GAP — completeness (declared columns render blank — schema↔feed field mismatch)
- folio-register [CODE-GROUNDED] — declared
folioNo/roomNo/folioStatus/roomCharges/otherCharges/totalAmount/paidAmount/balanceDuedon't match the frontdesk feed (folioId/no-room/status/aggregate-onlytotalCharges/balance) → effectively a one-column (guest name) report; also missingctx.propertyIdsfallback → 500s on period-only runs. - cash-drawer-report [CODE-GROUNDED] — declared
shiftDate/cashierId/drawerStatus/expectedBalancevs realopenedAt/cashier/absent/declaredCash; no receipts/payouts breakdown; missing propertyId fallback. - void-register [CODE-GROUNDED] — declared
voidReason/voidAmount/itemCount/outletIdvs realreason/amount/items/absent→ the amount + reason columns render blank.
MINOR / GAPS
- revenue-monthly — no chain rollup (only the first property in scope; multi-property GM silently under-reports). [CODE-GROUNDED]
- dsr —
paymentMode/orderTypedrill-down dims are hardcoded''(dead) though the MV data is fetched; no outlet-name resolution. (Money columns + APC null-safe are ACCURATE.) - payment-collection — MV buckets on UTC date not IST business day (G4 class) → 00:00-05:30 IST payments land on the wrong day; REFUND row must be excluded from any "total collected" rollup.
- refund-register —
refundedBy(authorised-by) exists on the MV but is dropped from the schema. - gstr1-summary — no B2CL/B2CS split, no CDNR (credit-note Table 9B), no invoice-wise B2B detail (buyerGstin/invoiceNo/IRN); RBAC excludes COMPLIANCE_OFFICER.
- einvoice — missing Ack Date, seller GSTIN, HSN/SAC, place-of-supply; RBAC set inconsistent with the other GST reports.
- tally-daybook — no gateway-fee netting (Bank ledger won't tie to statement), no source-ref column, no JSON/CSV on-screen preview.
- settlement-register — RBAC excludes ACCOUNTS (odd for a reconciliation report). ACCURATE otherwise.
- ACCURATE + COMPLETE (clean): payment-collection (cols), settlement-register, refund-register (bar refundedBy), dsr (accuracy), revenue-daily (cols).
Systemic patterns (repeat across reports — fix once, apply widely)
- P-A: live-proxy mapper drops
propertyId/date→ 400/500 on every run (night-audit, police-register, frro-c-form, folio-register, cash-drawer; AB#5213 class already fixed for void/discount/kot — not applied here). - P-B: live-proxy has NO field-mapping layer — declared schema column IDs must be byte-identical to the downstream DTO or they render blank (folio-register, cash-drawer, void-register). The
LiveProxyResolveronly normalises the envelope, never renames fields. - P-C: MV fed from
EventStorethat no consumer writes → permanently-empty reports (gst GSTR-3B, tax-audit-trail; #7588 class). - P-D: ÷0 → 0 instead of null (revenue-daily/monthly) — dsr's
ratioOrNullis the correct template. - P-E: statutory schema is the export ceiling — CSV/PDF/XML formatters render only
def.schema, so a missing column is silently dropped from the filed document.
Wave 2 — Finance/Revenue (16) — BA completeness DONE (SME accuracy pending)
CRITICAL/MAJOR — non-functional (P-A) or blank columns (P-B)
- city-ledger-report [CODE-GROUNDED] — P-A: mapper NEVER sends
propertyId→ every run 400s; P-B: 6/7 declared columns (accountCode/date/openingBalance/charges/payments/closingBalance) don't exist on the feed (which is a per-company aging snapshot, not a daily ledger). Completely non-functional. - high-balance-report [CODE-GROUNDED] — P-A: never sends
propertyId→ 400s; P-B:folioBalance↔realbalanceAmountmismatch, 5/8 columns absent on feed,roomNumberhardcoded null. Only guestName populates. - meal-plan-report [CODE-GROUNDED] — P-A: never sends
propertyId→ 400s; P-B: feed returns NESTED groups ({ratePlan, mealsExpected, guestList[]}) but schema expects flat rows → renders blank/garbage (resolver has no flattening). - ar-aging-detail [CODE-GROUNDED] — P-A: mapper has no
ctx.propertyIdsfallback → a period-only/scheduled run 500s. (Columns match the feed — P-B clean.)
GAPS
- ageing-report — period filter is a documented NO-OP (always all open folios); single-property only (no chain rollup); RBAC excludes GM/FINANCIAL_CONTROLLER; json/csv only.
- pnl-report — revenue is ONE "Total Revenue" line, no departmental breakout (Rooms/F&B/Banquet/Spa) despite the data carrying
source; no LY/Budget/Forecast comparative (material for a GOP P&L). - sale-register —
taxAmountlumped, no CGST/SGST/IGST split (can't reconcile GST heads). - pos-sales-register — no gross/discount/CGST/SGST/IGST/payment-mode/aggregator breakdown (all on the MV, none selected) → can't reconcile vs dsr/tax reports.
- ota-commission-statement-detail — "per-booking detail" but aggregates the SAME date+channel grain as commission-report; no bookingId/confirmation → can't reconcile against the OTA extranet.
- room-type-revenue / source-wise-revenue — single-property only (no chain rollup) despite comparative-LY implying enterprise use; RBAC excludes GM/FINANCIAL_CONTROLLER; json/csv only; source-wise missing room-nights/ADR.
- channel-production — description promises "cancellation rate" but no such measure exists (documented-but-undelivered); RBAC-naming inconsistency.
- commission-statement — no LY comparative. market-segment — RBAC excludes ACCOUNTS.
- CLEAN (BA): commission-report, expense-report.
Reinforces the systemic patterns
P-A (mapper drops/insufficiently-threads propertyId) + P-B (schema authored against an assumed shape never verified vs the real feed DTO) now confirmed across ~7 more live-proxy reports. Root cause is universal for live-proxy reports and ALL ship green because no .definition.spec.ts asserts the outbound param shape OR the response-field mapping. This is the single highest-leverage fix: (1) auto-inject ctx.propertyIds[0] + required date in LiveProxyResolver (or a shared mapper helper) so no report can omit them; (2) a contract test that each live-proxy report's declared schema field IDs match its real feed DTO; (3) a field-mapping layer in LiveProxyResolver (or per-report responseMapper) so a downstream rename can't silently blank a column.
Wave 4 — F&B/POS + HK + spa/banquet/CRM/distribution/audit (18) — DONE (combined SME+BA)
THE CLARIFYING SPLIT: MV-backed = mostly clean; live-proxy = systemically broken; stubs = empty
- CLEAN (MV-backed, accurate + complete): pos-apc, pos-daypart, food-cost-report (null-cost honest), cogs-by-category (Phase-1 honest null), sync-log-report, tip-out, housekeeping-productivity. item-wise-sales + server-productivity clean bar a MINOR P-D (÷0→0 not null on avgPrice/avgOrderValue).
CRITICAL — live-proxy P-A (400/500 every period-only run) + P-B (blank columns)
- rate-parity-audit [CODE-GROUNDED] — MOST BROKEN: nested feed (one row per room-type with a
channels[]array) vs flat schema → 6/8 cols blank + 2 mistyped; AND the channel feed has NO direct-rate concept at all, sodirectRate/disparity/disparityPctare unfillable without a producer change; + P-A 500. - kot-register [CODE-GROUNDED] — 8/9 declared columns blank (schema names match nothing in
KotRegisterItem);tableNois unfillable (no table on POSOrder/KOT). (P-A is fixed here.) - lost-and-found-register [CODE-GROUNDED] — P-A 500 + P-B (4/6 dims + the sole measure blank); +
getLostAndFoundnever filterstenantId(security follow-up, deviates from sibling feeds). - spa-service-sales-detail [CODE-GROUNDED] — P-A 500 + P-B (
therapist≠therapistName,basePrice≠serviceRevenue, no productRevenue source; 4/9 blank). - banquet-sales-detail [CODE-GROUNDED] — P-A 500 + P-B (no
eventType, novenueRent, no combinedfbRevenue— split food/bev; 5/9 blank). - role-change-audit [CODE-GROUNDED] — audit "WHO" is broken:
changedByhardcodednullin the producer (comment admits it) → a role-change audit can't say who changed the role;ipAddressis RBAC-gated but doesn't exist in the feed; P-B (5/9 cols blank). Compliance-surface defect.
CRITICAL — permanent empty stubs shipped as if live (P-C)
- linen-consumption [CODE-GROUNDED] —
queryunconditionally returns{rows:[], warmingUp:true}; no MV/model exists;scheduleable:true→ a Laundry Supervisor's scheduled report is forever empty with no ETA. - crm-feedback-pulse [CODE-GROUNDED] — same permanent
warmingUpstub; no GuestFeedback model; scheduleable.
MAJOR
- user-activity-audit [CODE-GROUNDED] — description overclaims "API activity" but the producer tracks LOGIN/LOGOUT only (weak for CERT-In/DPDP); P-B (4/7 blank, no
resourceId).
P0/P1 from this batch (into the fix list)
rate-parity-audit (producer + shaping) · kot-register (shaping) · lost-and-found (P-A+P-B+tenantId) · spa/banquet-detail (P-A+P-B) · linen/crm stubs (mark not-available or wire the MV) · role-change-audit changedBy (compliance) · user-activity-audit scope+P-B.
Wave 3b — Front-office + Performance (9) — DONE (combined SME+BA)
NEW pattern extension: MV-backed reports ALSO empty via consumer-topic / field-name mismatch
- P-C is broader than EventStore: an MV-backed report is ALSO permanently empty when (a) the analytics consumer subscribes to a topic string the producer never publishes, or (b) the definition
queryreads MV column names that don't exist. Both are mocked-green in the specs.
CRITICAL
- block-group-performance [CODE-GROUNDED] — analytics
ReservationConsumersubscribesreservation.group.created/modifiedbut reservation-service publishesgroup.created/group.updated→ topic strings NEVER match →MvGroupPerformancehas no writer → permanently empty. + missing cutoff/wash/ADR. - forecast-pace-report [CODE-GROUNDED] — the FORECAST half is always 0: the query reads
confidence/recommendationsAccepted/recommendationsRejectedbut the real MV columns areavgConfidence/humanApproved/humanRejected;predictedRevParLiftis never written by any consumer; the SPEC mocks the FICTIONAL field names → mocked-green. Only paceBookings/paceRevenue carry real data. - booking-enquiry-report [CODE-GROUNDED] —
MvBookingEnquiriesfed bycrm.lead.*topics that NO service publishes (crm-service has no Lead module) → permanently empty; AND double-counts (a row per lead-status transition, conversionRate computed per-row not as a funnel). Repoint at the existingReservation.status=ENQUIRYor build a real lead pipeline. - discrepancy-report [CODE-GROUNDED] — P-A (propertyId only from filters → period-only 500) + P-B (schema vs
DiscrepancyFeedItemtotal mismatch) + the backing controller ignores from/to (always "as of now") + the real resolution-audit (RoomDiscrepancy) is never joined soresolvedBy/resolution-time are unsourceable. - room-status [CODE-GROUNDED] — P-A (never sends propertyId → 400 every run) + P-B/wrong-grain (schema is per-room but the feed is floor/wing-aggregated
{group, clean, dirty, OOO...}). - room-status-detail [CODE-GROUNDED] — P-A (never sends from/to → 400) + P-B (schema flat per-room but feed is nested
{roomNumber, statusLog[]}— needs a flatten that doesn't exist).
OK / MINOR
- country-wise-statistics — accurate; MINOR P-D (avgStay/revenue→0 not null) + RBAC excludes front-office/reservations (needed for FRRO/visa nationality mix).
- front-office-pace — accurate; MAJOR completeness: no days-out bucketing / pickup curve (RM teams need it); raw cross-join grid.
- rate-plan-performance — accurate + wired correctly (ratePlanCode genuinely stamped); MINOR: RBAC excludes SALES_MGR; no channel/LOS split.
Wave 3a — Front-office ops (14) — DONE (combined SME+BA)
NEW SYSTEMIC PATTERN P-F — RBAC gates on PHANTOM roles (report is unrunnable)
- 9 of 14 gate on role strings NOT in the platform
UserRoleenum (GM/FRONT_OFFICE_MGR/FRONT_DESK_AGENT— real roles are SUPER_ADMIN/ADMIN/MANAGER/FRONT_DESK/HOUSEKEEPING/F_AND_B/ACCOUNTS/RESERVATIONS/STAFF/OWNER).ReportRBACServicedoes exact-membership (SUPER_ADMIN is the only bypass) → a real MANAGER/FRONT_DESK user cannot run in-house, guest-checked-in, guest-checked-out, pickup, manager-flash (the GM flash!), pre-arrival-pulse, booking, release, void. Fix = repoint to canonical roles (arrival/departure/occupancy/no-show/cancelled already do it right). [CODE-GROUNDED — highest-blast-radius, one-line-per-report fix]
CRITICAL
- in-house / guest-checked-in / guest-checked-out [CODE-GROUNDED] — P-A (mapper never sends propertyId; checkins/checkouts also need
date) → 400 every run; P-B (measuresnights/folioBalance/totalCharged/outstandingBalancedon't exist in the feed DTOs;checkInDate≠checkinDate/checkinTime);roomNumberhardcoded null in the frontdesk feed → Room column always blank; + phantom RBAC. - pickup-report [CODE-GROUNDED] — computes the WRONG metric: diffs two DIFFERENT stay-dates' current cumulative totals, not the SAME stay-date's booking count at two points in time. Its docstring's "negative = cancellations exceeded bookings" is structurally impossible (CONFIRMED counter never decrements). Should use the existing
MvFrontOfficePace (bookedDate × stayDate). The spec encodes the wrong behavior as truth. - pre-arrival-pulse [CODE-GROUNDED] — permanent no-op stub (
return {rows:[], warmingUp:true}), catalogued+scheduleable as if working (P-C class). - no-show-reservation [CODE-GROUNDED] —
MvDailyOccupancy.noShowsis NEVER incremented in real time — only SET by night-audit → shows silent0until audit runs (and permanently wrong if audit is skipped/rolled back), indistinguishable from "genuinely zero."
MATERIAL — wrong semantics / mislabeled
- arrival / departure — count ACTUAL check-ins/outs, NOT expected arrivals/departures (every incumbent's Arrival/Departure report is a pre-event guest roster for room-prep). Zero guest-level columns (name/room/ETA/VIP/source) + no drill-down → unusable as a front-desk worklist. Single-property-only chain rollup (drops other properties).
- occupancy-report —
occupancyPctreads the night-audit-only stale column instead of computing live from the same row'soccupiedRooms/totalRooms→ internally inconsistent row for the current day (manager-flash does it right inline). One-line fix. - manager-flash — metric math OK inline, but P-D (ratios→0 not null) +
roomsSold || occupiedRoomssilent metric fallback + phantom RBAC (P-F) + no LY/budget comparative. - booking-report — "Booking Date" dimension is actually the ARRIVAL date (outright mislabel) → "bookings today" shows reservations ARRIVING today, not CREATED today. cancelled/release/void share the arrival-vs-transaction-date ambiguity (all labelled just "Date").
MINOR
- P-D (÷0→0 not null) on manager-flash / country-wise / booking-enquiry ratios. Doc/code drift: arrival-report reads
MvDailyOccupancynot the documentedMvDailyArrivals.
ALL 6 BATCHES / 75 REPORTS REVIEWED — SYSTEMIC PATTERN TALLY
- P-A live-proxy mapper drops
propertyId/date→ 400/500 every run: night-audit, police-register, frro-c-form, folio-register, cash-drawer, city-ledger, high-balance, meal-plan, ar-aging-detail, discrepancy, room-status, room-status-detail, in-house, guest-checked-in, guest-checked-out, lost-and-found, spa-service-sales-detail, banquet-sales-detail, rate-parity-audit (~19). - P-B live-proxy schema field IDs ≠ downstream DTO → columns render blank (no field-mapping layer): folio-register, cash-drawer, void-register, city-ledger, high-balance, meal-plan, kot-register, lost-and-found, spa/banquet-detail, rate-parity, role-change-audit, user-activity-audit, discrepancy, room-status, room-status-detail, in-house, guest-checked-in, guest-checked-out (~18).
- P-C permanently empty (EventStore-no-writer / topic-mismatch / stub / column-name mismatch): gst GSTR-3B, tax-audit-trail, linen-consumption, crm-feedback-pulse, booking-enquiry, block-group-performance, forecast-pace (forecast half), pre-arrival-pulse (~8).
- P-D ÷0→0 not null: revenue-daily, revenue-monthly, item-wise-sales, server-productivity, manager-flash, country-wise, booking-enquiry (~7).
- P-E statutory schema = export ceiling (missing column silently dropped from the filed doc): gst, gstr1-summary, einvoice, police-register, frro-c-form.
- P-F RBAC gates on PHANTOM roles → unrunnable: in-house, guest-checked-in, guest-checked-out, pickup, manager-flash, pre-arrival-pulse, booking, release, void (~9) + assorted RBAC inconsistencies elsewhere.
- ACCURACY (real domain errors on MV-backed reports): revenue-daily/monthly (revenue-at-booking not per-night, no-show not reversed, occupancy incl OOO, monthly avg-of-ratios), pickup (wrong metric), occupancy (stale pct), gst B2B/B2C split.
- CLEAN (accurate + complete): dsr, pos-apc, pos-daypart, food-cost, cogs-by-category, sync-log, tip-out, housekeeping-productivity, payment-collection, settlement-register, refund-register(bar authorised-by), commission-report, expense-report, revenue-daily(cols), country-wise(bar P-D), rate-plan-performance, market-segment. (~17 largely clean, mostly MV-backed with matching columns.)
META: the unit specs are green because they mock the FICTIONAL field names / pre-populate the MV — a textbook "mocked-green, only-live" failure. The gating fix is a handful of SHARED changes (LiveProxyResolver auto-inject propertyId/date; a schema↔feed contract test; a response field-mapper; a phantom-role lint; the ÷0→null helper) that fix DOZENS of reports at once — plus per-report producer/semantic fixes for the accuracy + empty-MV cases.
Wave 2 — Finance/Revenue SME ACCURACY (16) — DONE (new consumer-layer DATA-CORRUPTION bugs)
These POISON the MV itself (not just the report) — the numbers are wrong for every report that reads these MVs:
- sale-register [CODE-GROUNDED] —
folio.consumer.ts:246HARDCODESsource:'ROOM'onMvDailySalesRegisterinstead of reusing the correctcategoryToSource(category)var computed 35 lines above (used correctly for MvRevenueBySource) → EVERY folio-routed charge (POS-to-room, spa-to-room, phone, cancellation/no-show/late-checkout fees) is mislabeled ROOM revenue → inflates Room, suppresses F&B/Spa/Phone. AlsonetRevenue = revenue − taxAmountDOUBLE-subtracts tax off an already tax-exclusive base (frontdesk storesamount= taxable base) → understated garbage. - commission / commission-statement / ota-commission-statement-detail [CODE-GROUNDED] —
MvCommissionis DOUBLE-WRITTEN (ChannelConsumer.onBookingReceived + ReservationConsumer.onCheckedOut both increment the same row, no shared dedup) → OTA revenue/bookings double-counted, commission% halved; AND never reversed on cancel (unlike the G2-fixed sibling MVs) → cancelled bookings permanently inflate. Plus MvCommission has NO GST-on-commission / §194-O TDS columns → the statutory legs a finance manager needs to reconcile the OTA payout are structurally absent. - source-wise-revenue / channel-production [CODE-GROUNDED] —
MvChannelPerformancehas ZERO reverse-on-cancel path (increment-only) → same G2 monotonic-overstatement class, left unfixed here. - channel-production [CODE-GROUNDED] — "ADR" = revenue ÷ bookingCount, NOT ÷ room-nights → ~5× inflated for a 5-night booking; internally inconsistent with room-type-revenue/market-segment which do ADR right.
- pos-sales-register [CODE-GROUNDED] —
MvPosRevenueno void/refund reversal (voided POS revenue stays forever); OUTLET/ITEM/SERVER rows share one flatrevenuecolumn → naive export SUM triple-counts. - ageing-report [CODE-GROUNDED] — reads
EventStoreforfolio.posted/folio.closed(no writer) → always all-zero AR buckets (P-C). - pnl — overclaims "USALI" (flat Total-Rev − Total-Exp GOP, no departmental lines); P-D on 4 percent fields. expense — ACCURATE. room-type-revenue / market-segment — accurate, MINOR P-D.