EP-013 — AI & Intelligence RTM
EPIC: EP-013 AI & Intelligence Reconciled: 2026-05-25 Wave 1 — see
./PLAYBOOK.mdfor the pattern. Source of truth for "% passing":hmp-docs/docs/platform-registry/2026-05-16-13-flow-demo-readiness.mduntil 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-serviceCLAUDE.md(hmp-analytics-service / hmp-web-dashboard / hmp-forecast-sidecar / hmp-revenue-strategy), live source underhmp-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/, dashboardsrc/app/(dashboard)/ai/+/revenue-manager/+/anomalies/.
Status legend
| Status | Meaning |
|---|---|
Built | Code exists + tests exist + tests pass |
Built-Untested | Code exists, no automated test coverage (or test exists but is a stub) |
Partial | Code exists for some acceptance criteria, gaps for others |
Stub | Code is a placeholder / TODO / returns mock data |
Not Started | No code |
Summary
| Metric | Count |
|---|---|
| Total stories | 11 |
| 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-Built | 1 (US-013-005 Anomaly Detection — Plan 3.1 shipped end-to-end with detection-strategies + cron + notification handler + flow test) |
| Built-Untested | 1 (US-013-009 NL Report Generation — narrative templates exist; no dedicated test inventory) |
| Partial | 7 (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) |
| Stub | 1 (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 Started | 1 (US-013-011 ML Model Training Pipeline — forecast-sidecar Prophet train route exists; A/B + drift + governance missing) |
| Drift findings | 12 (D-001..D-012; 5 P0 / 5 P1 / 2 P2) |
| [SME-PROPOSED] refinements | 22 |
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 byMvForecastrows 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-upsertsMvForecastfrom Prophet sidecar, 10k rows/call cap)hmp-analytics-service/src/reporting/forecast/forecast-upsert.service.ts— paired upsert servicehmp-analytics-service/src/reporting/comparator/forecast-enricher.ts(cited via CLAUDE.md Plan 3.2) — readsMvForecastfor revpar/adr/revenue/roomNights/occupancy, returnsWARMING_UP/OUT_OF_RANGEreasonshmp-forecast-sidecar/app/forecast/— Python FastAPI + Prophet train+infer module (app/main.pymountsapp.forecast.routesat/forecast)hmp-forecast-sidecar/app/scheduler.py— APScheduler that triggers nightly train + upsert into analytics-service viaINTERNAL_SERVICE_TOKENhmp-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 v2hmp-web-dashboard/src/components/modules/revenue/forecast-strip.tsx— revenue-manager forecast striphmp-web-dashboard/src/components/modules/operations-hub/tiles/report-card/forecast-pace-card.card.tsx— operations-hub mini-cardhmp-web-dashboard/src/components/modules/ai/(viakeynova.tsx) —RevenuePredictioncomponent (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 specshmp-analytics-service/src/reporting/forecast/forecast-upsert.service.spec.ts— upsert service specshmp-analytics-service/src/reporting/comparator/forecast-enricher.spec.ts— Forecast comparator/enricher specs (Plan 3.2WARMING_UP/OUT_OF_RANGEreasons)hmp-forecast-sidecar/tests/test_forecast_train.py— Prophet train pipelinehmp-forecast-sidecar/tests/test_scheduler.py— APScheduler boothmp-forecast-sidecar/tests/test_signal_store.py— input feature persistence
Flow tests:
- (no dedicated forecasting flow test in 13-flow doc —
MvForecastis exercised indirectly byforecast-pace-reportruns 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 toMvForecastvia the Plan 3.2 internal upsert. The CLAUDE.md says the comparator was swapped fromMvRevenueManagementKpitoMvForecaston 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. confidencedecay (review AC-1) vsconfidenceLow/confidenceHighbands (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.pyexists, but the review's GAP-3 (no curated state-prefix Indian holiday set) is still open at the sidecar level — Prophet's defaultIndiacalendar omits regional holidays (Pongal, Onam, Baisakhi, Bihu) and festival-week shoulder dates. hmp-forecast-sidecar/CLAUDE.mdexists (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 onMvDailyKpihistory 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-upsertsMvForecastrows via/api/internal/analytics/forecast/upsertwithINTERNAL_SERVICE_TOKENauth. (b)hmp-analytics-service.ForecastServicereads fromMvForecastfirst; falls back to Holt-Winters on the liveMvDailyKpitable when no row exists for the requested date OR the sidecar has been down >24h. (c)ForecastEnricherin the comparator pipeline reads only fromMvForecast."
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 withlower_window+upper_windowper 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-recommendationsCRUD + accept/reject endpoints (cited in dashboard build dir asAP-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 fromproperty.settingsJSON (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 inputshmp-revenue-strategy/src/decisions/decision-engine.service.ts— rate-decision producerhmp-revenue-strategy/src/decisions/decision-log.service.ts—PENDING_APPROVAL / APPROVED / REJECTED / MODIFIEDlifecyclehmp-revenue-strategy/src/decisions/decision.controller.ts—GET /revenue/decisions[?status=],GET /revenue/decisions/:idhmp-revenue-strategy/src/guardrails/guardrails.service.ts— DB-backedGuardrailmodel 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/rejecthmp-revenue-strategy/src/strategy/strategy.service.ts+strategy-template.service.ts— per-property RM strategy with rate floor/ceiling per room type per seasonhmp-forecast-sidecar/app/routes/pricing.py— Prophet-informed rate suggestionshmp-revenue-strategy/src/producers/—evt:revenue.decision.approved/evt:revenue.action.completed/evt:revenue.action.failedproducers consumed by analytics MvRevenueManagementKpi
Unit + integration tests:
hmp-property-service/src/pricing/dynamic-pricing.service.spec.ts— 13 specshmp-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 optimizerhmp-forecast-sidecar/tests/test_promo_engine.py— promo stackinghmp-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 rateTBD— not enumerated in2026-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 viaPOST /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-servicepricing/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/rateCeilingPctpair; the rev-strategyGuardrailmodel 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-strategyas the canonical owner of rate-recommendation lifecycle (decision generation, guardrails, approval, audit) per the RM Phase 1 charter in platform CLAUDE.md. Mark property-servicepricing/dynamic-pricing.service.tsas deprecated with a removal target; markpricing-recommendation.service.tsas the receive-decision endpoint (writes accepted rates toRatePlanRate) and document its narrow scope. Add an architecture-decision-record (ADR) underhmp-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-serviceSHALL consume and push to every active channel within 60 seconds; rate-parity audit SHALL verify the OTA-reported rate matches within ±0.5%. Drift triggersevt:channel.rate.parity.violationfor 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
pricingModeof 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
applyModeenum (SHADOW|LIVE|AB_TEST). SHADOW writes the decision todecision_logwithappliedAt=null. AB_TEST stamps anabTestArmfield 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 citesapps/ai-service/src/sentiment/sentiment.service.spec.tsbut there is nohmp-ai-servicerepo 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 viauseSentimentTrends)hmp-web-dashboard/src/components/modules/operations-hub/tiles/kpi/sentiment-24h.tile.tsx— operations-hub mini-tilehmp-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 testshmp-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-servicepath 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-intentIS 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
PartialtoStubuntil a live sentiment service is wired inhmp-analytics-serviceor 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/withSentimentService.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/integrationsLLM 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
guestIdONLY whenguest.preferences.consentSentimentAnalysis === true. Without consent, the score is stored againstpropertyId + reviewSourceIdwith 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 citesapps/ai-service/src/nl-reports/nl-reports.service.ts+apps/ai-service/src/llm/llm.service.ts; same situation as US-013-003 (theapps/ai-servicepath is not the current canonical location). The NLQ component IS live in dashboard.hmp-web-dashboard/src/components/modules/ai/nl-query-bar.tsx(viaai/keynova.tsx:7) —NLQueryBarinput with sample questions + conversation history + tools-used badgeshmp-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 inhmp-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.ts12 specs +llm.service.spec.ts5 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 callsPOST /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(orPOST /ai/nl-queryper the review's spec gap). If none, declare the backend a stub and bump catalog status fromBuilttoStubfor 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). EndpointPOST /api/analytics/nl-querywith{ 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 return429withcode=NLQ_COST_CAP_EXCEEDEDandretryAfter=<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(perhmp-analytics-service/CLAUDE.md§Plan 3.1)hmp-analytics-service/src/reporting/anomaly/anomaly.service.ts— query/dismiss servicehmp-analytics-service/src/reporting/anomaly/anomaly-detector.service.ts— 3 strategies (STATISTICAL / SEASONAL / FORECAST_RESIDUAL) for 4 metricshmp-analytics-service/src/reporting/anomaly/anomaly-cron.service.ts—@Cron('30 3 * * *')daily detectionhmp-analytics-service/src/reporting/anomaly/anomaly-notification-handler.service.ts— subscribes toanomaly.detectedtopic → DistributionService → opted-in tenants (TenantConfigANOMALY_NOTIFICATIONS)hmp-analytics-service/src/reporting/anomaly/detection-strategies.ts+ spec — 3 detection algorithmshmp-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 testshmp-web-dashboard/src/app/(dashboard)/anomalies/page.tsx— sortable index page (CRITICAL→LOW) with dismiss + show-dismissed togglehmp-web-dashboard/src/lib/api/anomalies.ts— typed clienthmp-web-dashboard/src/components/modules/ai/anomaly-alerts.tsx— AI-page widgethmp-web-dashboard/src/components/modules/reports/v2/anomaly-banner.tsx+.test.tsx— banner mounted above report runner when period has HIGH/CRITICALhmp-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.tshmp-analytics-service/src/reporting/anomaly/anomaly.service.spec.tshmp-analytics-service/src/reporting/anomaly/anomaly-cron.service.spec.tshmp-analytics-service/src/reporting/anomaly/anomaly-detector.service.spec.tshmp-analytics-service/src/reporting/anomaly/anomaly-notification-handler.service.spec.tshmp-analytics-service/src/reporting/anomaly/detection-strategies.spec.tshmp-analytics-service/src/reporting/anomaly/root-cause-hint-engine.spec.tshmp-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 rateTBD— 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.
RootCauseHintEngineis 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-DETECTIONfor 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_FAQarray (wifi/checkout/breakfast/etc.) with keyword-matchingaskQuestion()hmp-frontdesk-service/src/concierge/concierge.module.tshmp-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 repohmp-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
Stubuntil LLM + per-tenant FAQ + service-request creation lands. Add AC list defining v1 minimum: (a) per-propertyFaqEntrytable (replaces hardcoded array); (b)POST /guest-portal/concierge/askacceptslanguage+propertyId+reservationId?; (c) whenreservationIdpresent, the response can include guest-specific data (folio balance, room number); (d) "Make a service request" intent creates aHousekeepingTaskrow taggedsource=AI_CONCIERGE; (e) escalation: anyINQUIRYintent withconfidence<0.6opens 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— importsHousekeepingOptimizationcomponenthmp-web-dashboard/src/components/modules/ai/housekeeping-optimization.tsx(inferred from import path) — UI widget consuminguseHousekeepingRoutes(propertyId)hookhmp-housekeeping-service/src/—[CITED-BUT-NOT-FOUND]— noroute-opt*.ts/optimiz*.tsfiles underhmp-housekeeping-service/srcper grep. Either the optimizer lives elsewhere or the front-end consumes mock data- (The
hmp-forecast-sidecar/app/routes/optimization.pyexists 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
Stubmasquerading 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
Stuband author AC: "Route optimizer SHALL live inhmp-housekeeping-service/src/route-optimizer/as a deterministic TSP-approximation (Christofides or 2-opt). Inputs:roomIds[], floor-plan distance matrix fromRoomTopologytable, 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]— nosmart-assign*orroom-assignment*file under frontdesk-service src per grep at reconciliation time. EPIC body implementation note says "rule-based heuristics" — likely living insidecheckin.service.tsor similarhmp-web-dashboard/src/components/modules/front-desk/check-in/— assignment UI lives in check-in flowhmp-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-V2to extract the assignment logic fromcheckin.service.tsinto a dedicatedRoomAssignmentServicewith 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/(perhmp-analytics-service/CLAUDE.mdreporting framework v2) — 63 report definitions registered at boot viaReportDefinitionsRegistrarmanager-flash-report(catalog id) — frontoffice category, the closest match to a narrative daily-summary surface- (no dedicated
nl-narrative-generator.service.tsgrep-locates; narrative templates likely live alongside report definitions or in anarrative/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
.hbsfile underhmp-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 atext/markdownbody. Templates per report id undersrc/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 bodyUS-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 withRevenueKpiCards+ForecastStrip+RmReportsCards+RecommendationSummaryStrip(per dashboardCLAUDE.md)hmp-web-dashboard/src/components/modules/revenue/forecast-strip.tsx— 28-day forecast striphmp-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.tsxhmp-web-dashboard/src/lib/hooks/use-rm-decisions.ts—useRmDecisions(infinite scroll),useApproveDecision,useRejectDecision,useModifyApproveDecision,useBulkApprovehmp-web-dashboard/src/lib/api/revenue-strategy.ts— typed client for/revenue-strategy/*
Unit + integration tests:
- (Dashboard widget tests for
RevenueKpiCards,ForecastStrip,PendingApprovalsper AP-T11-009) - Various
revenue-manager/*component tests including the 4-cardRmReportsCardstest (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) ANDUS-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 modulehmp-forecast-sidecar/app/scheduler.py— APScheduler nightly triggerhmp-forecast-sidecar/app/main.py:25—prophet_routermounted at/forecast(train+infer routes)hmp-forecast-sidecar/tests/test_forecast_train.py— train pipeline testhmp-forecast-sidecar/tests/test_scheduler.py— scheduler testhmp-analytics-service/src/reporting/forecast/forecast-upsert.service.ts— receives sidecar's upserthmp-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.pyhmp-forecast-sidecar/tests/test_scheduler.pyhmp-forecast-sidecar/tests/test_anomaly_detector.pyhmp-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-metricstable. - 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 tableModelVersion(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 ontenantId+propertyId+datehash; per-cohort accuracy tracked. (d) Drift detection: if 7-day rolling MAPE > 1.5× training-time MAPE, fireevt:ml.drift.detectedand trigger retrain on next scheduler tick."