Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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
  • 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_person
  • price_per_pack

Producer-Side Filtering

  • Only per_person and per_pack packages produce package_pricing entries because these are the pricing types supported by Puma’s enum.
  • per_set, per_item, and ala_carte packages remain available only through the legacy packages[] aggregate.
  • Packages without an active Pricing row are dropped because Puma rejects snapshots with base_price: 0.
  • Group pricing tiers with missing or reversed start_date / end_date are 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::PackageAttr
  • HhPackage::Pricing
  • HhPackage::PricingGroup
  • HhPackage::RestaurantPackage
  • HhPackage::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

MethodPathURLDescriptionPayload

New Query

DB Schema / Database Migration

No new migrations required for Kafka producer implementation.


Improvement:

Feature NameDateWhat ChangedDescription
Dynamic Pricing Kafka2026-09-29Added package_pricing[] to KafkaPublishes per-package date-aware pricing snapshot to Kafka for search results