EP-012: Analytics & Reporting — Phase 1 Hardening
Phase 1 hardening sprint, 2026-04. All work committed to feature branches in
hmp-analytics-serviceandhmp-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.tshmp-analytics-service/src/consumers/pos.consumer.tshmp-analytics-service/src/consumers/hk.consumer.tshmp-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:
| Topic | Consumer.method |
|---|---|
reservation.created | ReservationConsumer.onCreated |
reservation.cancelled | ReservationConsumer.onCancelled |
reservation.modified | ReservationConsumer.onModified |
folio.posted | FolioConsumer.onPosted |
folio.closed | FolioConsumer.onClosed |
kot.completed | PosConsumer.onKotCompleted |
order.closed | PosConsumer.onOrderClosed |
task.completed | HkConsumer.onTaskCompleted |
channel.sync.failed | ChannelConsumer.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.ymlhmp-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.tsto seed each MV from the operational databases. - Lag alerts.
analytics_consumer_lag_secondsis now scraped by Prometheus. Alert threshold is 60 seconds (configured inhmp-infrastructure/monitoring/alerts/hmp-service-alerts.yaml).
Summary
| Story | Status before | Status after | Notes |
|---|---|---|---|
| US-012-04 (Occupancy MV) | Partial | Built | C2 + C5 enabled the flow |
| US-012-05 (Revenue MV) | Partial | Built | Same |
| US-012-06 (F&B Revenue MV) | Partial | Built | Same |
| US-012-07 (HK Productivity) | Partial | Built | Same |
| US-012-12 (Channel Parity) | Not Started | Partial | C5 enables the channel feed; UI surface still pending |
Story-level status promotions: 4 (Partial → Built), 1 (Not Started → Partial).