Skip to main content

EP-013 — AI & Intelligence RTM

EPIC: EP-013 AI & Intelligence Reconciled: 2026-05-25 Wave 1 — see ./PLAYBOOK.md for the pattern. Source of truth for "% passing": hmp-docs/docs/platform-registry/2026-05-16-13-flow-demo-readiness.md until re-measured. Authoring sources: EPIC body (epics/EP-013-ai-and-intelligence.md), catalog (01-epic-catalog.md §EP-013), review files (reviews/EP-013/US-013-001..004-review.md), per-service CLAUDE.md (hmp-analytics-service / hmp-web-dashboard / hmp-forecast-sidecar / hmp-revenue-strategy), live source under hmp-analytics-service/src/, hmp-revenue-strategy/src/, hmp-forecast-sidecar/app/, hmp-property-service/src/{pricing,rates,seasons}, hmp-frontdesk-service/src/concierge/, hmp-shared-packages/packages/inbound-intent/, dashboard src/app/(dashboard)/ai/ + /revenue-manager/ + /anomalies/.

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 stories11
Catalog claims "Built"5 (US-013-004, -005, -007, -009, -010)
Catalog claims "Partial"5 (US-013-001, -002, -003, -006, -008)
Catalog claims "Not Started"1 (US-013-011)
Verified-Built1 (US-013-005 Anomaly Detection — Plan 3.1 shipped end-to-end with detection-strategies + cron + notification handler + flow test)
Built-Untested1 (US-013-009 NL Report Generation — narrative templates exist; no dedicated test inventory)
Partial7 (US-013-001 Demand Forecasting; US-013-002 Dynamic Pricing; US-013-003 Sentiment; US-013-004 NLQ; US-013-006 AI Concierge Chatbot; US-013-007 Housekeeping Route Opt; US-013-008 Smart Room Assignment)
Stub1 (US-013-010 RM Advisor UI / Dynamic Pricing UI — /revenue-manager/actions shipped but pricing-acceptance loop runs against rule-based, not ML, recommendations)
Not Started1 (US-013-011 ML Model Training Pipeline — forecast-sidecar Prophet train route exists; A/B + drift + governance missing)
Drift findings12 (D-001..D-012; 5 P0 / 5 P1 / 2 P2)
[SME-PROPOSED] refinements22

US-013-001 — Demand Forecasting (7/30/90 Day)​

Catalog status: Partial (P2, 8 points) — note EPIC body lists this as P1/13 points (D-001 numbering drift) Verified status: Partial

Code locations:

  • hmp-analytics-service/src/forecast/forecast.controller.ts:43-72 — GET /api/analytics/forecast?from=&to= (JWT-gated, 7 operational roles)
  • hmp-analytics-service/src/forecast/forecast.service.ts — current implementation (Holt-Winters per review; backed by MvForecast rows written by sidecar — hmp-analytics-service/CLAUDE.md §Plan 3.2)
  • hmp-analytics-service/src/reporting/forecast/forecast-upsert.controller.ts — POST /api/internal/analytics/forecast/upsert (INTERNAL_SERVICE only; bulk-upserts MvForecast from Prophet sidecar, 10k rows/call cap)
  • hmp-analytics-service/src/reporting/forecast/forecast-upsert.service.ts — paired upsert service
  • hmp-analytics-service/src/reporting/comparator/forecast-enricher.ts (cited via CLAUDE.md Plan 3.2) — reads MvForecast for revpar/adr/revenue/roomNights/occupancy, returns WARMING_UP / OUT_OF_RANGE reasons
  • hmp-forecast-sidecar/app/forecast/ — Python FastAPI + Prophet train+infer module (app/main.py mounts app.forecast.routes at /forecast)
  • hmp-forecast-sidecar/app/scheduler.py — APScheduler that triggers nightly train + upsert into analytics-service via INTERNAL_SERVICE_TOKEN
  • hmp-forecast-sidecar/app/routes/forecast.py — legacy demand routes (kept for compatibility)
  • hmp-web-dashboard/src/components/modules/front-desk/tape-chart/forecast-strip.tsx + use-forecast-data.ts — 28-day pacing strip on tape-chart v2
  • hmp-web-dashboard/src/components/modules/revenue/forecast-strip.tsx — revenue-manager forecast strip
  • hmp-web-dashboard/src/components/modules/operations-hub/tiles/report-card/forecast-pace-card.card.tsx — operations-hub mini-card
  • hmp-web-dashboard/src/components/modules/ai/ (via keynova.tsx) — RevenuePrediction component (per review still mock-data-driven, GAP-9)

Unit + integration tests:

  • hmp-analytics-service/src/forecast/forecast.service.spec.ts — 8 specs per review (empty property, day count, occupancy bounds, demand class, model params, date format, confidence decay, weekend factor)
  • hmp-analytics-service/src/reporting/forecast/forecast-upsert.controller.spec.ts — internal upsert controller specs
  • hmp-analytics-service/src/reporting/forecast/forecast-upsert.service.spec.ts — upsert service specs
  • hmp-analytics-service/src/reporting/comparator/forecast-enricher.spec.ts — Forecast comparator/enricher specs (Plan 3.2 WARMING_UP / OUT_OF_RANGE reasons)
  • hmp-forecast-sidecar/tests/test_forecast_train.py — Prophet train pipeline
  • hmp-forecast-sidecar/tests/test_scheduler.py — APScheduler boot
  • hmp-forecast-sidecar/tests/test_signal_store.py — input feature persistence

Flow tests:

  • (no dedicated forecasting flow test in 13-flow doc — MvForecast is exercised indirectly by forecast-pace-report runs in the reports-v2 spec suite)

Real-backend smokes: none specific

E2E (Playwright): indirectly via e2e/reports-v2.spec.ts Forecast Pace report

Use cases referencing this story:

  • (none — no UC YAML maps to forecast; surfaced in D-001 as a Day-2 gap)

SME notes:

  • The story is two systems: a Holt-Winters baseline inside analytics-service (review's GAP-1) AND a Prophet sidecar (hmp-forecast-sidecar) writing to MvForecast via the Plan 3.2 internal upsert. The CLAUDE.md says the comparator was swapped from MvRevenueManagementKpi to MvForecast on 2026-05-01, which means the Prophet path IS the production source for the comparator today — the review's "model is Holt-Winters" verdict is stale on that surface.
  • confidence decay (review AC-1) vs confidenceLow/confidenceHigh bands (Plan 3.2 row shape) — both shapes coexist in the codebase. The internal contract IS bands (5 metrics × tenant × property × date); the surface contract is single-value confidence. New engineers will reasonably implement one and break the other.
  • Indian-hospitality reality: Prophet needs holidays. Today the holiday set in hmp-forecast-sidecar/app/forecast/holidays.py exists, but the review's GAP-3 (no curated state-prefix Indian holiday set) is still open at the sidecar level — Prophet's default India calendar omits regional holidays (Pongal, Onam, Baisakhi, Bihu) and festival-week shoulder dates.
  • hmp-forecast-sidecar/CLAUDE.md exists (we didn't read it but it's the second source of truth for the Python side); any spec change here must update both NestJS + Python service docs.

Drift findings:

D-001 — [P0] [Catalog-vs-EPIC-body] Catalog (01-epic-catalog.md) and EPIC body (epics/EP-013-ai-and-intelligence.md) disagree on every numbering and priority in EP-013: catalog uses 3-digit IDs (US-013-001) priority P2/P3 and 5/8 points; EPIC body uses 2-digit IDs (US-013-01) priority P1/P2 and 8/13 points. Even the story-name-to-ID mapping diverges: catalog US-013-004 = NL Query Bar, EPIC body US-013-04 = Anomaly Detection. Two referent docs disagreeing on what US-013-004 MEANS is a P0 pilot blocker. The review files follow the catalog (US-013-004-review.md = NL Query Bar).

  • [SME-PROPOSED] Adopt the catalog as canonical (3-digit IDs, current numbering, P2-skewed). Rewrite EPIC body §10 to use the catalog IDs + names + priorities + points. Add an alias note at the top of each EPIC-body section: > Previously titled "X" in EPIC body draft prior to 2026-05-25 reconciliation. Or: bump catalog priorities to P1 (EPIC body's instinct) IF Revenue Management is a Property #1 launch concern.

D-002 — [P1] [Story-incomplete + two-system gap] Story does not narrate the analytics-service ↔ forecast-sidecar split. The Holt-Winters fallback inside analytics-service AND the Prophet sidecar writing to MvForecast are both shipped, but the story body talks about a single forecast endpoint. New engineers will not know which path to extend, which one is canonical, or how INTERNAL_SERVICE_TOKEN rotation affects them.

  • [SME-PROPOSED] Add AC: "Demand forecasting SHALL be a two-tier system: (a) hmp-forecast-sidecar (Python + Prophet) trains models on MvDailyKpi history fetched via /api/internal/analytics/forecast/mv-daily-kpi, runs predictions for the 5 metrics (revpar/adr/revenue/roomNights/occupancy) × all active properties, and bulk-upserts MvForecast rows via /api/internal/analytics/forecast/upsert with INTERNAL_SERVICE_TOKEN auth. (b) hmp-analytics-service.ForecastService reads from MvForecast first; falls back to Holt-Winters on the live MvDailyKpi table when no row exists for the requested date OR the sidecar has been down >24h. (c) ForecastEnricher in the comparator pipeline reads only from MvForecast."

D-003 — [P1] [Story-incomplete + India-hospitality-critical] Story does not require an Indian holiday + festival calendar in the Prophet model. Prophet's built-in country_holidays('IN') covers ~14 national holidays but misses regional festivals (Pongal, Onam, Baisakhi, Bihu, Eid lunar dates, regional Ganesh Chaturthi, regional Durga Puja) AND wedding season block (Nov 15 – Feb 28). These are the demand drivers Indian RMs actually price against.

  • [SME-PROPOSED] Add AC: "Prophet model SHALL include country_holidays='IN' PLUS a curated regional holiday set sourced from a state-keyed YAML (hmp-forecast-sidecar/app/forecast/holidays/<state>.yaml). Festival impact windows: Diwali ±3 days, Holi day-of, Onam ±2 days, Eid (lunar — annual rebuild), Christmas+New Year (Dec 22–Jan 2), Republic Day weekend, Independence Day weekend, Wedding Season block (Nov 15 – Feb 28). Each festival flagged with lower_window + upper_window per Prophet idiom."

US-013-002 — Dynamic Pricing Recommendations​

Catalog status: Partial (P2, 8 points) — EPIC body says P1/13 (D-001 drift) Verified status: Partial

Code locations:

  • hmp-property-service/src/rates/pricing-recommendation.controller.ts — /pricing-recommendations CRUD + accept/reject endpoints (cited in dashboard build dir as AP-property-{GET,POST}-pricing-recommendations-{,id-accept,id-reject}.mdx)
  • hmp-property-service/src/rates/pricing-recommendation.service.ts — recommendation generator with configurable floor/ceiling guardrails read from property.settings JSON (Implementation Update 2026-03-07 fix per review)
  • hmp-property-service/src/pricing/dynamic-pricing.controller.ts — duplicate dynamic-pricing surface (review GAP-12 — two implementations)
  • hmp-property-service/src/pricing/dynamic-pricing.service.ts + dynamic-pricing.service.spec.ts — rule-based engine (13 specs per review)
  • hmp-property-service/src/seasons/seasons.service.ts — peak/holiday/seasonal pricing inputs
  • hmp-revenue-strategy/src/decisions/decision-engine.service.ts — rate-decision producer
  • hmp-revenue-strategy/src/decisions/decision-log.service.ts — PENDING_APPROVAL / APPROVED / REJECTED / MODIFIED lifecycle
  • hmp-revenue-strategy/src/decisions/decision.controller.ts — GET /revenue/decisions[?status=], GET /revenue/decisions/:id
  • hmp-revenue-strategy/src/guardrails/guardrails.service.ts — DB-backed Guardrail model with hard/soft + per-room-type + evaluate() returning per-rule pass/fail (real implementation, supersedes the simple floor/ceiling clamp in property-service)
  • hmp-revenue-strategy/src/approvals/approval.controller.ts — POST /revenue/approvals/:id/approve + :id/reject
  • hmp-revenue-strategy/src/strategy/strategy.service.ts + strategy-template.service.ts — per-property RM strategy with rate floor/ceiling per room type per season
  • hmp-forecast-sidecar/app/routes/pricing.py — Prophet-informed rate suggestions
  • hmp-revenue-strategy/src/producers/ — evt:revenue.decision.approved / evt:revenue.action.completed / evt:revenue.action.failed producers consumed by analytics MvRevenueManagementKpi

Unit + integration tests:

  • hmp-property-service/src/pricing/dynamic-pricing.service.spec.ts — 13 specs
  • hmp-analytics-service/src/revenue-optimization/revenue-optimizer.service.spec.ts — 7 specs (NOTE: review path; verify under analytics-service repo — [CITED-BUT-NOT-FOUND] at repo root grep, may have moved)
  • hmp-forecast-sidecar/tests/test_rate_optimizer.py — Python pricing optimizer
  • hmp-forecast-sidecar/tests/test_promo_engine.py — promo stacking
  • hmp-forecast-sidecar/tests/test_overbooking_calculator.py — overbooking pickup math

Flow tests:

  • hmp-infrastructure/testing/flow-tests/src/flows/rm-advisor-approval.flow.spec.ts — 8-scenario approval-queue end-to-end (S1 seed → S2 list → S3 approve → S4 summary delta → S5 modify-approve → S6 reject → S7 bulk-approve → S8 tenant isolation). PASS rate TBD — not enumerated in 2026-05-16-13-flow-demo-readiness.md

Real-backend smokes: none specific

E2E (Playwright):

  • hmp-web-dashboard/e2e/revenue-manager-approval-flow.spec.ts — list render, filter narrow, single approve, bulk approve, reject validation, modify-approve respects floor/ceiling. Backend stub via POST /revenue-strategy/decisions/test-seed (SUPER_ADMIN, dev-only)

Use cases referencing this story:

  • (none — surfaced in D-004 as a Day-2 gap)

SME notes:

  • This story has three competing implementations: (i) property-service pricing-recommendation.service.ts (review's primary path, now with property-settings guardrails), (ii) property-service pricing/dynamic-pricing.service.ts (review's GAP-12 duplicate), and (iii) hmp-revenue-strategy (RM Phase 1 — the canonical new home per platform CLAUDE.md). The PMS-side (i) and (ii) both still ship; the rev-strategy decision lifecycle is the right long-term home but coexistence is not narrated anywhere.
  • Indian-hospitality rate-floor reality: rate floors are typically set per room type per season (peak/lean/festival), NOT global. Today the property-settings guardrails (review fix) carry a single rateFloorPct/rateCeilingPct pair; the rev-strategy Guardrail model is more flexible but the property-service surface (the one the rate-plan UI consumes) is single-pair only. Mismatch surface for ops.
  • Rate-parity with OTAs is the elephant: dynamic pricing decisions MUST push the same rate to all channels via hmp-channel-service. Today the producer chain (evt:revenue.decision.approved) eventually fans out, but there is no explicit AC requiring parity-verified push.

Drift findings:

D-004 — [P0] [Three-implementations + ownership-gap] Three live implementations of "dynamic pricing recommendation" coexist (pricing-recommendation.service.ts, pricing/dynamic-pricing.service.ts, hmp-revenue-strategy). Catalog says "Partial" once; reality is three Partial paths competing for ownership. Production rate decisions in PROD today route through one of them — undocumented which.

  • [SME-PROPOSED] Adopt hmp-revenue-strategy as the canonical owner of rate-recommendation lifecycle (decision generation, guardrails, approval, audit) per the RM Phase 1 charter in platform CLAUDE.md. Mark property-service pricing/dynamic-pricing.service.ts as deprecated with a removal target; mark pricing-recommendation.service.ts as the receive-decision endpoint (writes accepted rates to RatePlanRate) and document its narrow scope. Add an architecture-decision-record (ADR) under hmp-docs/docs/architecture/ mapping every endpoint to its owning service.
  • [SME-PROPOSED] Add AC: "An accepted rate decision SHALL emit evt:revenue.decision.approved; hmp-channel-service SHALL consume and push to every active channel within 60 seconds; rate-parity audit SHALL verify the OTA-reported rate matches within ±0.5%. Drift triggers evt:channel.rate.parity.violation for ops alerting."

D-005 — [P1] [Story-incomplete + GST-inclusive trap] Story does not address GST-inclusive vs GST-exclusive rate decisions. If a rate plan is "GST inclusive ₹4,000" and the AI recommends "₹4,500", does that mean inclusive or exclusive? Today: ambiguous in code; the rate-plan's pricingMode (D-008 in EP-002 RTM) is read at folio-post time but NOT necessarily at recommend time, so a recommendation may quote exclusive while the plan stores inclusive.

  • [SME-PROPOSED] Add AC: "Every pricing recommendation SHALL carry the pricingMode of the target rate plan (GST_INCLUSIVE | GST_EXCLUSIVE). The UI displays the price with explicit (incl.) / (excl.) suffix. The recommendation engine SHALL compute floor/ceiling in the same mode as the plan."

D-006 — [P1] [Story-incomplete + governance gap] Story does not require A/B testing or shadow-mode for dynamic pricing changes. Today: an accepted AI recommendation goes live immediately on all channels. Real RM ops want: shadow-mode (model proposes but doesn't apply), A/B (50% of stays at AI rate, 50% at rule-based baseline), with revenue uplift attribution.

  • [SME-PROPOSED] Add AC: "Each pricing decision SHALL carry an applyMode enum (SHADOW | LIVE | AB_TEST). SHADOW writes the decision to decision_log with appliedAt=null. AB_TEST stamps an abTestArm field on each affected reservation; analytics-service computes per-arm RevPAR and surfaces uplift on /revenue-manager/ab-results."

US-013-003 — Guest Sentiment Analysis (Reviews + Feedback)​

Catalog status: Partial (P2, 5 points) — EPIC body says P1/8 (D-001) Verified status: Partial

Code locations:

  • hmp-analytics-service/src/constants/sentiment-topics.ts — 15 predefined topics across 6 categories (Implementation Update 2026-03-07 per review); normalizeRatingToSentiment() (1-5 ↔ -1.0/+1.0 mapping)
  • hmp-analytics-service/src/... — [CITED-BUT-NOT-FOUND] — the review cites apps/ai-service/src/sentiment/sentiment.service.spec.ts but there is no hmp-ai-service repo at HMP root; the sentiment service itself is not in analytics-service code paths at reconciliation time (verified by grep). The review's own "Still Deferred" note (2026-03-07) confirms: "Sentiment analysis service itself (no sentiment module in analytics-service)"
  • hmp-web-dashboard/src/components/modules/ai/sentiment-chart.tsx — front-end widget (per review, connected to backend via useSentimentTrends)
  • hmp-web-dashboard/src/components/modules/operations-hub/tiles/kpi/sentiment-24h.tile.tsx — operations-hub mini-tile
  • hmp-shared-packages/packages/inbound-intent/ — E1 heuristic intent classifier (auto-responder, EN+HI keyword scan, 7 spec files / 1164 LOC of tests) — note: intent ≠ sentiment, but ships the language-detection scaffolding sentiment needs

Unit + integration tests:

  • hmp-analytics-service/src/constants/sentiment-topics.spec.ts (cited in 2026-03-07 update; verify under repo) — normalization round-trip tests
  • hmp-shared-packages/packages/inbound-intent/src/__tests__/ — 139 specs (auto-responder 27, classify-edge-cases 47, classify-en 22, classify-hi 22, keywords 7, keyword-scan 22, types 14) covering keyword-scan + intent classification in EN+HI
  • (no live sentiment.service.spec.ts in the current code tree per grep — the review's 20-spec claim is on a legacy apps/ai-service path that does not map to any current HMP service)

Flow tests: none

Real-backend smokes: none

Use cases referencing this story:

  • (none catalogued — surfaced in D-007)

SME notes:

  • The review's path apps/ai-service/src/sentiment/ does not map to any current service in the HMP repo set; the constants + normalizer landed in analytics-service but the SERVICE-LEVEL sentiment analysis was deferred. This is a Built-Untested artefact masquerading as Partial.
  • @hmp/inbound-intent IS the only shipped natural-language pipeline today — but it's intent classification (FEEDBACK / COMPLAINT / INQUIRY / OOO / AUTO_RESPONDER) not sentiment scoring (-1.0..+1.0). The two are siblings, not the same.
  • Indian-hospitality reality: OTA reviews are heavily Hindi/Hinglish on MakeMyTrip and Goibibo; English-only sentiment ignores 40-60% of reviews on Tier-2 properties. Inbound-intent has EN+HI keyword sets (keywords/en.ts + keywords/hi.ts) that could seed a sentiment lexicon.
  • DPDPA constraint: storing raw review text + linking it to a guest is PII under DPDPA art. 5; sentiment scoring requires either anonymisation OR explicit guest-consent flag before persistence.

Drift findings:

D-007 — [P0] [Catalog-says-Partial + service-doesn't-exist] Catalog/review present a "Partial" sentiment analysis service; the actual sentiment SERVICE (analyzeFeedback, getSentimentTrends, LLM prompt, fallback keyword analysis) cited by the review file is on a path that does not exist in the current HMP repo set (apps/ai-service/src/sentiment/). What ships is: 15 topic constants + a 1-5/-1..+1 normaliser + dashboard chart components consuming a hook that calls an endpoint with no live receiver. The story IS NOT Partial — it is Stub on the backend, Built on the constants + UI shell.

  • [SME-PROPOSED] Bump verified status from Partial to Stub until a live sentiment service is wired in hmp-analytics-service or a dedicated new service. Re-author the story to reference the inbound-intent EN+HI keyword sets as the seed lexicon.
  • [SME-PROPOSED] Add AC: "Sentiment analysis SHALL be a NEW module in hmp-analytics-service/src/sentiment/ with SentimentService.analyzeFeedback(feedbackId) returning { overallScore: -1.0..+1.0, topics: TopicScore[], confidence: 0..1, language: 'en'|'hi'|'hi-en' }. EN + HI processing via the inbound-intent keyword sets as the offline fallback; LLM (via @hmp/integrations LLM client) as the primary path."

D-008 — [P1] [DPDPA-compliance] Story does not address DPDPA consent for sentiment storage. Storing review text + topic scores linked to guestId is PII processing under DPDPA art. 5(a) requiring purpose-specific consent.

  • [SME-PROPOSED] Add AC: "Sentiment analysis results SHALL be stored against the feedback row with guestId ONLY when guest.preferences.consentSentimentAnalysis === true. Without consent, the score is stored against propertyId + reviewSourceId with the guest identifier hashed. Default for new guests: false (opt-in)."

US-013-004 — Natural Language Query Bar in Dashboard​

Catalog status: Built (P2, 5 points) — EPIC body says US-013-04 = Anomaly Detection (D-001) Verified status: Partial

Note: Catalog title "NL Query Bar in Dashboard" matches the review file US-013-004-review.md. EPIC body §10 calls this US-013-04 but reuses the title for Anomaly Detection — see D-001.

Code locations:

  • hmp-analytics-service/src/... — [CITED-BUT-NOT-FOUND] — review cites apps/ai-service/src/nl-reports/nl-reports.service.ts + apps/ai-service/src/llm/llm.service.ts; same situation as US-013-003 (the apps/ai-service path is not the current canonical location). The NLQ component IS live in dashboard.
  • hmp-web-dashboard/src/components/modules/ai/nl-query-bar.tsx (via ai/keynova.tsx:7) — NLQueryBar input with sample questions + conversation history + tools-used badges
  • hmp-web-dashboard/src/app/(dashboard)/ai/keynova.tsx — page mount
  • (Possible new home: i18n service in auth-service — review's 2026-03-07 update cites i18n.service.ts.resolveNlqMetric() adding 40+ HI→EN keyword mappings; verify in hmp-auth-service/src/i18n/)

Unit + integration tests:

  • hmp-web-dashboard/src/app/(dashboard)/ai/__tests__/ai-page.test.tsx — page smoke (mocks API client)
  • (review cites apps/ai-service/src/nl-reports/nl-reports.service.spec.ts 12 specs + llm.service.spec.ts 5 specs — both on a path that does not currently grep to a live service in the HMP repo)

Flow tests: none

Real-backend smokes: none

E2E (Playwright): covered indirectly by e2e/ai tests (if any) — none enumerated

Use cases referencing this story: none

SME notes:

  • Same path-not-found pattern as US-013-003: the NLQ backend cited in the review (apps/ai-service/src/nl-reports/) is not in the current code tree. The dashboard widget IS live and calls POST /ai/query (per review's GAP-9) — that endpoint must terminate in an HMP service, but the review's repo path appears legacy. Surface as Task-6 verification work.
  • Hindi support is a real Indian-hospitality lift: even GMs at metro 5-star properties switch to Hindi for quick check-ins ("aaj kitne arriving"). Today the dashboard widget has English sample questions; the HI keyword mapping in i18n.service (review 2026-03-07) is half the path — the LLM prompt must also accept Hindi context.
  • Cost-control gap: every NLQ call is an LLM call. Without query caching + per-tenant rate limiting, a curious user can rack up provider bills.

Drift findings:

D-009 — [P0] [Backend-service-location-unknown] Same as D-007 but for NLQ: the review's referenced backend at apps/ai-service/src/nl-reports/ is not in any current HMP service repo per grep. Either the path is legacy (since-renamed) OR the NLQ POST never had a live receiver. Without verification, the dashboard's NLQueryBar UI is shipping against an undefined endpoint.

  • [SME-PROPOSED] Audit task: verify which service registers POST /ai/query (or POST /ai/nl-query per the review's spec gap). If none, declare the backend a stub and bump catalog status from Built to Stub for US-013-004.
  • [SME-PROPOSED] Add AC: "NLQ SHALL be served by hmp-analytics-service/src/nlq/ (canonical home given the metric tools live on MVs in analytics). Endpoint POST /api/analytics/nl-query with { question, propertyId, sessionId?, context? }. Roles: SUPER_ADMIN, ADMIN, MANAGER, REVENUE_MANAGER. Per-tenant rate-limit 60 queries/hour; per-tenant LLM cost cap surfaced in /admin/billing."

D-010 — [P1] [Story-incomplete + cost-control] Story does not bound LLM cost or query latency. Every NLQ call is a billable LLM call (today; would-be tomorrow if the receiver gets wired). No caching, no rate limit, no per-tenant cap.

  • [SME-PROPOSED] Add AC: "Identical NLQ questions (normalised query + propertyId + business-date) SHALL be cached for 5 minutes via BusinessCacheService. Per-tenant rate limit: 60 queries/hour. Per-tenant monthly cost cap (default ₹2,000); on cap-breach return 429 with code=NLQ_COST_CAP_EXCEEDED and retryAfter=<seconds-to-next-month>."

US-013-005 — Anomaly Detection (Booking/Revenue/Fraud)​

Catalog status: Built (P2, 5 points) — EPIC body says US-013-04 P1/8 Verified status: Built

Code locations:

  • hmp-analytics-service/src/reporting/anomaly/anomaly.controller.ts — 4 endpoints: GET /anomalies, GET /anomalies/:id, PATCH /anomalies/:id/dismiss, POST /anomalies/recalculate (per hmp-analytics-service/CLAUDE.md §Plan 3.1)
  • hmp-analytics-service/src/reporting/anomaly/anomaly.service.ts — query/dismiss service
  • hmp-analytics-service/src/reporting/anomaly/anomaly-detector.service.ts — 3 strategies (STATISTICAL / SEASONAL / FORECAST_RESIDUAL) for 4 metrics
  • hmp-analytics-service/src/reporting/anomaly/anomaly-cron.service.ts — @Cron('30 3 * * *') daily detection
  • hmp-analytics-service/src/reporting/anomaly/anomaly-notification-handler.service.ts — subscribes to anomaly.detected topic → DistributionService → opted-in tenants (TenantConfig ANOMALY_NOTIFICATIONS)
  • hmp-analytics-service/src/reporting/anomaly/detection-strategies.ts + spec — 3 detection algorithms
  • hmp-analytics-service/src/reporting/anomaly/root-cause-hint-engine.ts + spec — 5 rule-based hints attached to HIGH/CRITICAL anomalies
  • Prisma model: MvAnomaly (composite unique (tenantId, propertyId, date, metric, detectionMethod) per analytics CLAUDE.md)
  • hmp-forecast-sidecar/tests/test_anomaly_detector.py — Python-side anomaly detector tests
  • hmp-web-dashboard/src/app/(dashboard)/anomalies/page.tsx — sortable index page (CRITICAL→LOW) with dismiss + show-dismissed toggle
  • hmp-web-dashboard/src/lib/api/anomalies.ts — typed client
  • hmp-web-dashboard/src/components/modules/ai/anomaly-alerts.tsx — AI-page widget
  • hmp-web-dashboard/src/components/modules/reports/v2/anomaly-banner.tsx + .test.tsx — banner mounted above report runner when period has HIGH/CRITICAL
  • hmp-web-dashboard/src/components/modules/operations-hub/tiles/portfolio-strip/anomaly-leaderboard.tsx — operations-hub leaderboard tile

Unit + integration tests:

  • hmp-analytics-service/src/reporting/anomaly/anomaly.controller.spec.ts
  • hmp-analytics-service/src/reporting/anomaly/anomaly.service.spec.ts
  • hmp-analytics-service/src/reporting/anomaly/anomaly-cron.service.spec.ts
  • hmp-analytics-service/src/reporting/anomaly/anomaly-detector.service.spec.ts
  • hmp-analytics-service/src/reporting/anomaly/anomaly-notification-handler.service.spec.ts
  • hmp-analytics-service/src/reporting/anomaly/detection-strategies.spec.ts
  • hmp-analytics-service/src/reporting/anomaly/root-cause-hint-engine.spec.ts
  • hmp-web-dashboard/src/components/modules/reports/v2/anomaly-banner.test.tsx

Flow tests:

  • hmp-infrastructure/testing/flow-tests/src/flows/anomaly-detection.flow.spec.ts — 6 scenarios (S1 recalc → S2 list HIGH/CRITICAL → S3 detail+rootCauseHint → S4 dismiss → S5 dismissed-filter → S6 tenant-scope isolation). PASS rate TBD — not enumerated in 13-flow doc

Real-backend smokes: none specific

Use cases referencing this story:

  • (none catalogued — anomaly endpoints owned end-to-end by analytics-service per its CLAUDE.md)

SME notes:

  • This is the healthiest story in EP-013: 4 controllers + 3 detection strategies + cron + notification handler + 7 spec files + flow test + dashboard banner + index page + leaderboard tile + root-cause-hint engine. The whole CQRS spine is there.
  • Story mentions "fraud" in title — today's 3 strategies cover statistical/seasonal/forecast-residual on revenue/expense metrics; fraud-pattern detection (impossibly fast multi-check-ins, refund cycles, void-then-recreate) is not in the detection set.
  • RootCauseHintEngine is the buried gem — 5 rules attached only to HIGH/CRITICAL. The story doesn't tell readers it exists; new engineers will reinvent.

Drift findings:

D-011 — [P2] [Story-incomplete + scope-gap] Story title says "Booking/Revenue/Fraud" but actual implementation covers revenue/expense statistical anomalies. Fraud-specific detection is not shipped: no rapid-check-in pattern detector, no refund-cycle detector, no void-recreate detector. Calling the story "Built" leaves the F-word silently unmet.

  • [SME-PROPOSED] Either (a) rename catalog title to "Statistical Anomaly Detection (Revenue/Expense)" and author a new US-013-NEW-FRAUD-DETECTION for the fraud surface, or (b) keep title and add ACs explicitly listing fraud-pattern detectors that ship in v1.
  • [SME-PROPOSED] Add AC (if keeping title): "Anomaly detector SHALL include a FRAUD strategy with rule set: (a) ≥3 reservations to same guestId in 24h flagged HIGH; (b) refund-to-charge ratio >0.5 over 7-day window flagged CRITICAL; (c) void-then-recreate within 1h on same room flagged HIGH; (d) ≥5 failed payment attempts on same merchantAccount in 1h flagged CRITICAL. All fraud anomalies attach a root-cause hint pointing to the relevant audit-trail view."

US-013-006 — AI Concierge Chatbot (Guest FAQ)​

Catalog status: Partial (P3, 8 points) — EPIC body says US-013-09 P2/13 Verified status: Partial

Code locations:

  • hmp-frontdesk-service/src/concierge/concierge.controller.ts — 3 public endpoints: POST /guest-portal/concierge/ask, GET /guest-portal/concierge/faqs, GET /guest-portal/concierge/faqs/:id (all @Public())
  • hmp-frontdesk-service/src/concierge/concierge.service.ts — HOTEL_FAQ array (wifi/checkout/breakfast/etc.) with keyword-matching askQuestion()
  • hmp-frontdesk-service/src/concierge/concierge.module.ts
  • hmp-shared-packages/packages/inbound-intent/ — separate but complementary path: 5-intent classifier on inbound guest conversations (FEEDBACK / COMPLAINT / INQUIRY / OOO / AUTO_RESPONDER), EN+HI, 139 tests

Unit + integration tests:

  • hmp-frontdesk-service/src/concierge/*.spec.ts — [CITED-BUT-NOT-FOUND] — no spec file enumerated in source-map walk; verify under repo
  • hmp-shared-packages/packages/inbound-intent/src/__tests__/ — 139 specs for the sibling intent classifier

Flow tests: none

Real-backend smokes: none

Use cases referencing this story: none

SME notes:

  • Implementation is keyword-matching against a hardcoded FAQ array (wifi password, checkout time, breakfast hours…). No LLM, no embeddings, no per-tenant FAQ. "Concierge" oversells: this is a static FAQ search.
  • Two @Public() endpoints on a non-payment service is unusual — verify rate-limiting (gateway middleware) catches them; otherwise a bot can spray the endpoint.
  • Indian-hospitality reality: guest queries skew toward extra towels / late check-out / iron requesst / airport transfer cost — none of these are handleable by static FAQ. WhatsApp + Hindi + per-property FAQ is the real surface.

Drift findings:

D-012 — [P1] [Story-overpromises] Story title "AI Concierge Chatbot" with AC mentions like "create a service request for room 405" implies dynamic, stateful chat with PMS integration. Reality: hardcoded FAQ keyword match. Status "Partial" obscures how thin the implementation is.

  • [SME-PROPOSED] Demote catalog status to Stub until LLM + per-tenant FAQ + service-request creation lands. Add AC list defining v1 minimum: (a) per-property FaqEntry table (replaces hardcoded array); (b) POST /guest-portal/concierge/ask accepts language + propertyId + reservationId?; (c) when reservationId present, the response can include guest-specific data (folio balance, room number); (d) "Make a service request" intent creates a HousekeepingTask row tagged source=AI_CONCIERGE; (e) escalation: any INQUIRY intent with confidence<0.6 opens a conversation in the staff Inbox (Plan E3) and replies "I've connected you with a team member".

US-013-007 — Housekeeping Route Optimization​

Catalog status: Built (P3, 5 points) — EPIC body says US-013-05 P2/8 Verified status: Partial (no clear server-side route optimizer found at reconciliation time — front-end widget exists in dashboard AI page)

Code locations:

  • hmp-web-dashboard/src/app/(dashboard)/ai/keynova.tsx:8 — imports HousekeepingOptimization component
  • hmp-web-dashboard/src/components/modules/ai/housekeeping-optimization.tsx (inferred from import path) — UI widget consuming useHousekeepingRoutes(propertyId) hook
  • hmp-housekeeping-service/src/ — [CITED-BUT-NOT-FOUND] — no route-opt*.ts / optimiz*.ts files under hmp-housekeeping-service/src per grep. Either the optimizer lives elsewhere or the front-end consumes mock data
  • (The hmp-forecast-sidecar/app/routes/optimization.py exists but its scope is revenue optimization, not housekeeping)

Unit + integration tests: none enumerated

Flow tests: none

Real-backend smokes: none

Use cases referencing this story: none

SME notes:

  • Catalog says "Built" but the server-side optimizer is not findable in the current repo set. EPIC body describes a TSP-approximation algorithm; no such implementation grep-locates. This is likely a Stub masquerading as Built.
  • Indian-hospitality reality: housekeeping efficiency is real but constraints differ: staff don't carry mobile devices in many properties; routes are paper-printed; floor-bias matters more than room-distance because staff stockpile trolleys per floor.

Drift findings: (covered under D-011's pattern — story-says-Built, code-may-be-stub)

  • [SME-PROPOSED] Re-verify the optimizer location in Task 6. If absent, bump status to Stub and author AC: "Route optimizer SHALL live in hmp-housekeeping-service/src/route-optimizer/ as a deterministic TSP-approximation (Christofides or 2-opt). Inputs: roomIds[], floor-plan distance matrix from RoomTopology table, priority (DEPARTURE > STAYOVER > VACANT_DIRTY). Output: ordered roomIds + estimated minutes per room. Endpoint: POST /housekeeping/route-optimizer/compute."

US-013-008 — Smart Room Assignment​

Catalog status: Partial (P2, 5 points) — EPIC body says US-013-06 P2/8 Verified status: Partial

Code locations:

  • hmp-frontdesk-service/src/... — [CITED-BUT-NOT-FOUND] — no smart-assign* or room-assignment* file under frontdesk-service src per grep at reconciliation time. EPIC body implementation note says "rule-based heuristics" — likely living inside checkin.service.ts or similar
  • hmp-web-dashboard/src/components/modules/front-desk/check-in/ — assignment UI lives in check-in flow
  • hmp-frontdesk-service/src/checkin/checkin.service.ts — assignment cascade (per EP-002 RTM US-002-004) likely contains the rule chain

Unit + integration tests: none specifically named

Flow tests: indirectly via flow-6-check-in.spec.ts (6/13 PASS at 2026-05-16)

Use cases referencing this story: none

SME notes:

  • Same locate-the-implementation gap as US-013-007. The "rule-based heuristics" in EPIC body ARE shipped (check-in works), but the dedicated "Smart Room Assignment" surface is not a discoverable module — it's embedded in the check-in cascade.
  • Indian-hospitality reality: room assignment in Indian hotels is heavily preference-driven (Vastu/east-facing for many guests; quiet floor for elderly; ground floor for joint families with elders; sea-view premium for honeymooners). Today's rule chain likely doesn't capture this.

Drift findings: (covered by D-011 / orphaned-implementation pattern)

  • [SME-PROPOSED] Author a new story US-013-NEW-SMART-ASSIGNMENT-V2 to extract the assignment logic from checkin.service.ts into a dedicated RoomAssignmentService with explicit ACs covering: returning guest preferences, floor balancing, recent-maintenance avoidance, Vastu/orientation requests, and group/family clustering.

US-013-009 — NL Report Generation​

Catalog status: Built (P3, 5 points) — EPIC body says US-013-08 P2/5 Verified status: Built-Untested

Code locations:

  • hmp-analytics-service/src/reporting/reports/ (per hmp-analytics-service/CLAUDE.md reporting framework v2) — 63 report definitions registered at boot via ReportDefinitionsRegistrar
  • manager-flash-report (catalog id) — frontoffice category, the closest match to a narrative daily-summary surface
  • (no dedicated nl-narrative-generator.service.ts grep-locates; narrative templates likely live alongside report definitions or in a narrative/ subdir)

Unit + integration tests: none specifically named for narrative generation

Flow tests: none

Use cases referencing this story: none

SME notes:

  • EPIC body says "template-based report generation produces narrative summaries" — this is plausible because the reporting framework v2 has 5 formatters (JSON/PDF/XLSX/XML/CSV) and Handlebars templates. A narrative template would be a Handlebars .hbs file under hmp-analytics-service/src/reporting/format/templates/.
  • The Built-Untested classification is honest: the substrate ships, but the specific "narrative report" story has no dedicated tests calling out narrative correctness (sentence structure, data interpolation, insight bullets).

Drift findings: (none new beyond the EP-013 D-001 numbering drift)

  • [SME-PROPOSED] Add AC: "Narrative generation SHALL be a NarrativeFormatter (sibling to JsonFormatter / PdfFormatter) producing a text/markdown body. Templates per report id under src/reporting/format/templates/narrative/<id>.hbs. Mandatory sections: 1-line headline, 3-5 KPI bullets with ±% deltas, 1-2 insight callouts derived from anomaly detection (US-013-005 cross-link), 1 recommended action."

US-013-010 — Revenue Prediction Dashboard​

Catalog status: Built (P2, 5 points) — EPIC body says US-013-10 = "Dynamic Pricing UI & Approval" P1/8 Not Started Verified status: Partial (per EPIC body intent — Stub if the catalog title is read literally; the actual /revenue-manager pages + RM advisor approval loop ARE shipped, so Partial is more honest)

Note: This is the most extreme catalog-vs-EPIC drift in EP-013. Catalog US-013-010 = Revenue Prediction Dashboard (Built). EPIC body US-013-10 = Dynamic Pricing UI & Approval (Not Started). Two different stories sharing the same ID. See D-001.

Code locations (interpreting per CATALOG title — revenue prediction dashboard):

  • hmp-web-dashboard/src/app/(dashboard)/revenue-manager/page.tsx — Revenue Manager home with RevenueKpiCards + ForecastStrip + RmReportsCards + RecommendationSummaryStrip (per dashboard CLAUDE.md)
  • hmp-web-dashboard/src/components/modules/revenue/forecast-strip.tsx — 28-day forecast strip
  • hmp-web-dashboard/src/lib/hooks/use-revenue-manager.ts — useRevenueKpis, useRevenueForecast, useRevenueChannelMix, useRmActionsSummary, useRmRecentActions
  • All wired to live analytics endpoints (/analytics/dashboard/metrics, /analytics/forecast, /channel-stats/summary) — NO mock data per dashboard CLAUDE.md AP-T11-009

Code locations (interpreting per EPIC-BODY title — dynamic pricing UI & approval):

  • hmp-web-dashboard/src/app/(dashboard)/revenue-manager/actions/page.tsx — RM advisor approval queue (single approve / modify-approve / reject / bulk approve)
  • hmp-web-dashboard/src/components/modules/revenue-manager/recommendation-card.tsx + modify-approve-dialog.tsx + reject-reason-dialog.tsx + approval-filters.tsx + recommendation-summary-strip.tsx
  • hmp-web-dashboard/src/lib/hooks/use-rm-decisions.ts — useRmDecisions (infinite scroll), useApproveDecision, useRejectDecision, useModifyApproveDecision, useBulkApprove
  • hmp-web-dashboard/src/lib/api/revenue-strategy.ts — typed client for /revenue-strategy/*

Unit + integration tests:

  • (Dashboard widget tests for RevenueKpiCards, ForecastStrip, PendingApprovals per AP-T11-009)
  • Various revenue-manager/* component tests including the 4-card RmReportsCards test (Plan 2.4)

Flow tests:

  • rm-advisor-approval.flow.spec.ts (already enumerated under US-013-002) covers the approval queue end-to-end

E2E (Playwright):

  • hmp-web-dashboard/e2e/revenue-manager-approval-flow.spec.ts

Use cases referencing this story: none

SME notes:

  • The naming collision (D-001) is most visible here: the EPIC-body's "Dynamic Pricing UI & Approval" describes a feature that IS shipped end-to-end (the RM advisor approval loop) but catalog calls it "Built" under a different name. The catalog's "Revenue Prediction Dashboard" describes the home page that also IS shipped. Both interpretations land on "the surface exists" but the AC sets are different.
  • Property #1 risk: if you pick "Revenue Prediction Dashboard" as canonical, the approval queue's ACs are never enforced. If you pick "Dynamic Pricing UI & Approval", the forecast-strip + KPI-cards stay un-AC'd.

Drift findings: (rolled into D-001 — both interpretations need ACs)

  • [SME-PROPOSED] Split into TWO stories per resolution of D-001: US-013-010 = Revenue Prediction Dashboard (home page KPI/forecast/channel-mix/RM-actions surface) AND US-013-010b = Dynamic Pricing UI & Approval Queue (advisor approval loop). Map the existing code/tests cleanly to each.

US-013-011 — AI Model Training Pipeline​

Catalog status: Not Started (P3, 8 points) — EPIC body says US-013-11 P1/13 Partial Verified status: Partial (per EPIC body — Prophet train route + APScheduler exist in sidecar; A/B + drift + governance missing)

Note: Catalog says "Not Started"; EPIC body says "Partial". Both are partially right — see D-001.

Code locations:

  • hmp-forecast-sidecar/app/forecast/ — Prophet train + infer module
  • hmp-forecast-sidecar/app/scheduler.py — APScheduler nightly trigger
  • hmp-forecast-sidecar/app/main.py:25 — prophet_router mounted at /forecast (train+infer routes)
  • hmp-forecast-sidecar/tests/test_forecast_train.py — train pipeline test
  • hmp-forecast-sidecar/tests/test_scheduler.py — scheduler test
  • hmp-analytics-service/src/reporting/forecast/forecast-upsert.service.ts — receives sidecar's upsert
  • hmp-analytics-service/src/reporting/forecast/forecast-upsert.controller.ts:GET /mv-daily-kpi — sidecar reads training history from here
  • (no MLflow / model registry / A-B test framework / drift detector found in any service)

Unit + integration tests:

  • hmp-forecast-sidecar/tests/test_forecast_train.py
  • hmp-forecast-sidecar/tests/test_scheduler.py
  • hmp-forecast-sidecar/tests/test_anomaly_detector.py
  • hmp-forecast-sidecar/tests/test_signal_store.py

Flow tests: none

Use cases referencing this story: none

SME notes:

  • "Partial" is more honest than "Not Started": the sidecar trains, schedules, and writes predictions every night. What's missing is the ML-ops scaffolding: model versioning, A/B between Prophet vs gradient-boost, drift detection, alerting on retraining failure, hold-out evaluation surfacing into a model-metrics table.
  • Indian-hospitality reality: model retraining cadence matters for festival windows — a model trained in March before Diwali (Nov) is stale by Nov; quarterly retrain is the floor, monthly is better.

Drift findings: (rolled into D-001 numbering drift)

  • [SME-PROPOSED] Add AC: "ML model lifecycle SHALL include: (a) per-model version stamps in MvForecast.modelVersion; (b) model registry table ModelVersion(id, name, version, trainedAt, trainingDataFrom, trainingDataTo, mape, rmse, r2, deployedAt?); (c) A/B test framework — every prediction request randomly assigned to baseline vs candidate model based on tenantId+propertyId+date hash; per-cohort accuracy tracked. (d) Drift detection: if 7-day rolling MAPE > 1.5× training-time MAPE, fire evt:ml.drift.detected and trigger retrain on next scheduler tick."