Skip to main content

EP-012: Analytics & Reporting — Phase 1 Hardening

Phase 1 hardening sprint, 2026-04. All work committed to feature branches in hmp-analytics-service and hmp-infrastructure. Not yet pushed.

The analytics epic landed two interlocking changes in Phase 1: the materialized view consumers were registered into the module graph (C2), and the EventDispatcherService was introduced to wire those consumers to the message bus topics they expected to receive (C5). Without C5, the C2 consumers existed but received zero events.


C2 — Materialized View Consumers Registered​

Branch: feat/enable-mv-consumers (in hmp-analytics-service)

What was fixed​

Five consumer classes in hmp-analytics-service/src/consumers/ had been written but were not in ConsumersModule's providers array, so NestJS never instantiated them and they silently produced zero materialized view updates.

The fix is one-line per consumer: add to ConsumersModule.providers.

Files touched​

  • hmp-analytics-service/src/consumers/consumers.module.ts (added 5 providers)
  • hmp-analytics-service/src/consumers/reservation.consumer.ts (no code change, now exported)
  • hmp-analytics-service/src/consumers/folio.consumer.ts
  • hmp-analytics-service/src/consumers/pos.consumer.ts
  • hmp-analytics-service/src/consumers/hk.consumer.ts
  • hmp-analytics-service/src/consumers/channel.consumer.ts

Tests​

6 new tests on ConsumersModule (each consumer instantiated, each consumer's update* method delegates to the underlying MV repo). All passing.


C5 — Event Subscriptions + Dispatcher​

Branch: feat/enable-event-flow (in hmp-infrastructure, hmp-analytics-service)

What was fixed​

C2 made the consumers exist, but they still didn't receive events because nothing was calling kafkaService.subscribe() for them. C5 introduced EventDispatcherService (hmp-analytics-service/src/dispatcher/), a single class with one onModuleInit() method that subscribes the five consumers to the nine topics they need:

TopicConsumer.method
reservation.createdReservationConsumer.onCreated
reservation.cancelledReservationConsumer.onCancelled
reservation.modifiedReservationConsumer.onModified
folio.postedFolioConsumer.onPosted
folio.closedFolioConsumer.onClosed
kot.completedPosConsumer.onKotCompleted
order.closedPosConsumer.onOrderClosed
task.completedHkConsumer.onTaskCompleted
channel.sync.failedChannelConsumer.onSyncFailed

The infrastructure half of the fix was setting SKIP_EVENT_SUBSCRIPTIONS=false on every backend service except api-gateway in both staging and prod compose files (docker-compose.staging-aws.yml and docker-compose.prod-aws.yml). The flag's historical default is true (skip), which is fine for dev but defeats the dispatcher in shared environments.

Files touched​

  • hmp-analytics-service/src/dispatcher/event-dispatcher.service.ts (new)
  • hmp-analytics-service/src/dispatcher/event-dispatcher.spec.ts (new — 6 tests)
  • hmp-analytics-service/src/app.module.ts (registered DispatcherModule)
  • hmp-infrastructure/docker/docker-compose.staging-aws.yml
  • hmp-infrastructure/docker/docker-compose.prod-aws.yml

Tests​

31/31 tests pass on feat/enable-event-flow:

  • 6 dispatcher tests (each subscription correctly routed; idempotent on re-init; logs failures without crashing)
  • 25 across the five consumers (event payload parsing, MV upsert, error propagation)

Architecture documentation​

Full pattern in architecture/analytics-event-flow.md.

Operational notes​

  • Backfill required. When this lands in any environment, the consumer group starts at the latest stream offset, which means historical events are not replayed automatically. Run hmp-analytics-service/scripts/backfill-mv.ts to seed each MV from the operational databases.
  • Lag alerts. analytics_consumer_lag_seconds is now scraped by Prometheus. Alert threshold is 60 seconds (configured in hmp-infrastructure/monitoring/alerts/hmp-service-alerts.yaml).

Summary​

StoryStatus beforeStatus afterNotes
US-012-04 (Occupancy MV)PartialBuiltC2 + C5 enabled the flow
US-012-05 (Revenue MV)PartialBuiltSame
US-012-06 (F&B Revenue MV)PartialBuiltSame
US-012-07 (HK Productivity)PartialBuiltSame
US-012-12 (Channel Parity)Not StartedPartialC5 enables the channel feed; UI surface still pending

Story-level status promotions: 4 (Partial → Built), 1 (Not Started → Partial).