Search Dynamic Pricing - Dynamic Pricing Search Result
Description / Background
Search results currently show a static preview price and no reliable availability signal, so diners often see a different price or an unavailable slot after opening the store page. This work makes search cards reflect the same pricing and availability logic the store page uses, so the diner gets a consistent answer for the searched context and can compare restaurants more confidently.
Current State:
- Search shows one generic price no matter what date/time/pax is searched
- Diner discovers the real price only after clicking into the store page
- Fully booked / unavailable restaurants still appear as if bookable
- No default date/time/pax context — “starting from” price is a best-case sample, not a real bookable number
- “Come More, Pay Less” cards show an idealized per-person price assuming the full promo group size
Problem: Search result cards show a static “starting from” price pulled from a precomputed snapshot table, generated without any date, time, or pax context. Whatever the diner searches for, they see the same price and no availability signal.
Objectives
- Make the price and availability shown in search results reflect the diner’s searched date/time/pax
- Show cheapest per-person price across all bookable packages when no date/time/pax is set
- Surface availability (or lack of it) for the searched slot before the click-through
- Price parity between search and store page removes the “bait and switch” perception
- Clear, explainable, consistent pricing logic shared between search and store page
Flow 1 — Diner searches with explicit date, time, and/or pax
- Pass date, time, and pax through to the pricing and availability resolution layer
- Resolve the displayed per-person price using the same logic family as the store page
- Include dynamic pricing, tiered party-size pricing, and real “Come More, Pay Less” calculation
- Resolve availability using the same inventory/quota logic as the store page
Flow 2 — Diner searches with no date/time/pax set (default search)
- Show the cheapest available per-person price across all bookable packages
- Treat the displayed value as a clear “Starting from” floor price
- No implied date/time/pax assumption
Flow 3 — Price or availability changes between search and store page
- Store page remains the ultimate source of truth (unchanged)
- Never book at a price weren’t shown
Scope
- Kafka producer-side implementation (publishing pricing data)
- Date/time/pax-aware pricing resolution
- Availability signaling in search results
- Loading, partial-failure, and global-failure states
Out of Scope
- Frontend/consumer implementation (Pegasus)
- Real-time availability checks in search
- Store page changes
- Market-specific default pax assumptions (v1)
- Personal-data or fraud/points scope
Location
Backend (hh-server)
- Kafka event production for restaurant data
- Package pricing snapshot generation
Frontend (hh-pegasus) - NOT IMPLEMENTED
- Search results page display
- Dynamic pricing UI
How to find
How to test
Sequence Diagram / Flow
ERD
Backend Implementation
PR: https://github.com/hungryhub-team/hh-server/pull/8803
Adds a new package_pricing[] field to the restaurant Kafka event.
Each entry contains the per-package, date-aware pricing snapshot Puma needs for dated search, including:
- Base price
- Group pricing tiers
- Agendas
What It Does
Each restaurant Kafka event now carries:
{
"package_pricing": [
{
"package_id": 3272,
"package_type": "pp",
"pricing_type": "per_pack",
"base_price": 699.0,
"currency": "THB",
"group_pricing": [],
"pricing_rules": [],
"agendas": [
{
"days_of_week": [1, 2, 3, 4, 5, 6, 7],
"start_time": "11:00",
"end_time": "21:00"
}
]
}
]
}
Existing legacy fields remain unchanged:
packages[]price_per_personprice_per_pack
Producer-Side Filtering
- Only
per_personandper_packpackages producepackage_pricingentries because these are the pricing types supported by Puma’s enum. per_set,per_item, andala_cartepackages remain available only through the legacypackages[]aggregate.- Packages without an active
Pricingrow are dropped because Puma rejects snapshots withbase_price: 0. - Group pricing tiers with missing or reversed
start_date/end_dateare dropped. - Multi-currency snapshots are normalized to a single currency per snapshot.
Model Triggers
The following callbacks now include:
package_pricing: {}
in their Kafka update changed_attributes:
HhPackage::PackageAttrHhPackage::PricingHhPackage::PricingGroupHhPackage::RestaurantPackageHhPackage::ModelConcerns::PackageTraits
As a result, any pricing-related change triggers a rebuild of the package_pricing snapshot.
Test Coverage
99 examples across:
spec/models/hh_package/agenda_spec.rb
spec/services/event_driven_services/hh_search/schemas/restaurant_schema_spec.rb
Hybrid Implementation
No update.
Frontend Implementation
NOT IMPLEMENTED - Pending future implementation.
Mobile Implementation
NOT IMPLEMENTED - Pending future implementation.
PRD & Task
Design
Design link: (Pending)
API Blueprint
| Method | Path | URL | Description | Payload |
|---|---|---|---|---|
New Query
DB Schema / Database Migration
No new migrations required for Kafka producer implementation.
Improvement:
| Feature Name | Date | What Changed | Description |
|---|---|---|---|
| Dynamic Pricing Kafka | 2026-09-29 | Added package_pricing[] to Kafka | Publishes per-package date-aware pricing snapshot to Kafka for search results |