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

Pricing Rules by Capacity & Demand

Adjust package prices based on real-time booking demand and seat availability.

Note: Past-performance rules (Total GMV / Total Cover / Total Booking over a historical period) are a separate rule type (performance_based) and are documented separately. This PRD covers only demand and capacity signals derived from seat availability.


Demand & Capacity-Based Rule

Automatically adjusts prices according to real-time booking demand and seat availability.

AspectBehavior
Condition CheckReal-time
Seat ScopePer time window (aggregate across 15-minute slots)
ApprovalAuto-approve or Always require approval (user choice)

Condition Options

MetricDescriptionRelation
Total Seats Booked (absolute)Total seats already booked across all 15-minute slots in the rule’s time window for a given date>, <, >=, <=, =, between
Total Seats Booked (%)Percentage of total capacity booked across all 15-minute slots in the rule’s time window for a given date>, <, >=, <=, =, between
Total Seats Remaining (absolute)Total seats still available across all 15-minute slots in the rule’s time window for a given date>, <, >=, <=, =, between
Total Seats Remaining (%)Percentage of total capacity still available across all 15-minute slots in the rule’s time window for a given date>, <, >=, <=, =, between

Action

By Percentage (%) or By Fixed Amount.

Scenarios

ScenarioConditionBehavior
ATotal Seats Booked > N (absolute)Increase price when aggregate bookings across the window exceed N
BTotal Seats Remaining < N (absolute)Increase price when aggregate remaining seats across the window fall below N
CTotal Seats Booked > N% (%)Increase price when aggregate % booked across the window exceeds N%
DTotal Seats Remaining < N% (%)Increase price when aggregate % remaining across the window falls below N%
ETotal Seats Booked between N% and M%Apply tiered pricing for mid-demand range across the window
FTotal Seats Remaining between N and MApply discount when aggregate availability is in a specific range

Scenario Examples

The tables below use hourly labels for readability. The actual capacity data is stored and evaluated at 15-minute granularity (e.g., 9:00, 9:15, 9:30, 9:45, …). The hourly values represent the aggregate of the underlying 15-minute records.

Scenario 1 — High-demand lunch window

  • Rule: Jan 1-15, 9am-12pm, Mon-Sun
  • Condition: Total Seats Booked > 48 pax
  • Action: Increase price by 10%
  • Result: When aggregate bookings across the 9am-12pm window exceed 48 pax, every API slot within the window is priced at +10%.
Date9am-9:5910am-10:5911am-11:59Window total (9am-12pm)Price for slots in [9:00, 12:00)
1 Jan10202050110
2 Jan50510100
3 Jan0246100

Window 9am-12pm includes all 15-minute slots whose start time is >= 09:00 and < 12:00 (e.g., 9:00, 9:15, …, 11:45). Slots starting at 12:00 are outside the window.

Scenario 2 — Low-demand weekday window

  • Rule: Mon-Fri, 11am-3pm
  • Condition: Total Seats Booked < 20 pax
  • Action: Decrease price by 20%
  • Result: When aggregate bookings across the 11am-3pm window are below 20 pax, every API slot within the window is priced at -20%.
Day10am-10:5911am-11:5912pm-12:591pm-1:592pm-2:59Window total (11am-3pm)Price for slots in [11:00, 15:00)
Sunday1020203040—100
Monday505051080
Tuesday020430256100

Window 11am-3pm includes all 15-minute slots whose start time is >= 11:00 and < 15:00 (e.g., 11:00, 11:15, …, 14:45). Slots starting at 15:00 are outside the window.

Note on Scenario 2: The “Between 10-20 pax” note in the Excel refers to a separate between scenario/condition type, not the < 20 pax rule shown above.


Scope of Seat (Demand & Capacity)

  • Per time window — Capacity is aggregated across all 15-minute time slots that fall within the rule’s configured time window for a given date.
  • Time slot granularity — 15 minutes, matching the hh-server API v5 available_start_times_based_on_packages.json response format.
  • Window boundary semantics — A 15-minute slot is included in the window if its start time is >= window_start_time and < window_end_time ([start, end)). For example, a rule “11am-3pm” includes all 15-minute slots starting at 11:00, 11:15, …, 14:45, but excludes slots starting at 15:00.
  • API slot price adjustment — The price adjustment applies only to API slots whose start_time falls within the rule window. Adjacent 15-minute records are aggregated for evaluation, but the visible time-picker slot must lie inside the window to receive the adjusted price.
  • Time slot allotment — Uses total allotment count, not remaining inventory after cancellations.

When seat scope is per time window, the system:

  1. Sums the relevant capacity metric across all 15-minute slots in the window for the date.
  2. Compares the aggregate value against the rule’s threshold.
  3. Applies the price adjustment uniformly to every API slot whose start_time falls within the window if the condition matches.

Threshold Relations

  • gt — greater than (>)
  • gte — greater than or equal (>=)
  • lt — less than (<)
  • lte — less than or equal (<=)
  • eq — equal (=)
  • between — inclusive range [value_min, value_max]

A single Demand & Capacity rule can contain multiple conditions and/or condition items, enabling tiered pricing such as:

  • 0–50% booked: base price
  • 51–80% booked: +5%
  • 81–100% booked: +10%

Composition with Date & Time Rules

A Demand & Capacity rule is a single pricing_rule record that combines two checks on the same rule:

  1. Date & time eligibility — the rule’s date_time_conditions_attributes window (same vocabulary as Specific Date & Time: everyday, specific_date, specific_time, specific_date_time, day_of_week, day_time_of_week) determines when the rule is eligible.
  2. Capacity threshold — the aggregate capacity across the rule’s time window determines whether the rule fires for the requested date.

Both checks must pass for the rule to apply to a slot. This is an AND relationship within the rule.

Within-rule: multiple demand conditions

A single Demand & Capacity rule may contain multiple demand_capacity_conditions_attributes. If more than one condition matches the aggregate capacity, all matching conditions apply cumulatively (see PricingEngine/013-DEMAND-CAPACITY-CONDITIONS.md).

Cross-rule: interaction with other pricing_rule records

Demand & Capacity rules and Specific Date & Time rules are sibling rule types stored as separate pricing_rule records. When a diner opens the time picker, pricing-engine evaluates all active rules whose date/time window contains the requested slot.

The end-to-end resolution happens in two layers:

  1. pricing-engine customer API (get_available_rules_by_time)

    • Per package, per slot, selects up to 2 rules: one percentage and one fixed.
    • Selection order: lowest priority → highest weight → newest created_at.
    • Decrease rules are preferred over increase rules when both are available.
    • See PricingEngine/016-CUSTOMER-BOOKING-API.md and PricingEngine/014-PRICE-MODIFIER.md for the exact algorithm.
  2. hh-pegasus FE (getDynamicPricingV2Rule)

    • Receives the up-to-2 rules from the API.
    • Picks exactly ONE rule to display and book:
      • If any decrease rule exists, pick the one with the biggest discount (best for the diner).
      • Otherwise, pick the increase rule with the smallest surcharge.
    • Therefore, the diner sees only one price adjustment per slot/package at a time, even when multiple backend rules match.

A standalone Specific Date & Time rule and a standalone Demand & Capacity rule that both match the same slot do not stack multiplicatively. Priority/value resolution at the engine layer, followed by the FE’s best-for-diner selection, determines which single adjustment is shown.

Example

  • Rule A (Specific Date & Time): every Friday, +10%, value_type = percentage, priority = 10.
  • Rule B (Demand & Capacity): Friday 7pm–9pm, if total seats booked ≥ 20, +5%, value_type = percentage, priority = 5.

For a Friday 7:30pm slot when 25 seats are already booked:

  • Both Rule A and Rule B match.
  • The engine returns both (same value_type, so up to 2 rules possible).
  • The engine priority resolution selects Rule B (lower priority number = higher precedence).
  • hh-pegasus getDynamicPricingV2Rule confirms Rule B is the best increase rule and displays the slot at 105.
  • If Rule B had priority = 15, Rule A would win and the price would be 110.

Date/time window vs. capacity aggregation window

Do not confuse the two windows:

  • Date/time eligibility window — defined by the rule’s date_type and condition_items. It determines which calendar dates and clock times the rule is eligible to run. A slot must lie inside this window to be considered.
  • Capacity aggregation window — for the requested date, the rule aggregates capacity across all 15-minute slots whose start_time falls inside the rule’s start_time/end_time. This aggregate is what the threshold compares against.

In most configurations these windows are the same time range, but the model allows them to differ if future rule types need it.


Customer-Facing Behaviour

Demand & Capacity rules are evaluated per time window in the booking picker. The result is surfaced to the diner as:

  • Per-slot price label in the time picker (packages/shared-ui/src/components/time-picker/TimePickerButton2.vue) — every API slot whose start_time lies within the matched window shows the same adjusted price.
  • low-price / high-demand styling based on whether the adjusted price is lower or higher than the base price.
  • Real-time evaluation every time the customer opens the time picker for a date, via POST /api/v1/get_available_rules_by_time.

Because evaluation is per time window, multiple adjacent API slots on the same date share the same adjusted price when demand conditions are met.

See the full customer flow in Dynamic Pricings 2.0 — Customer Booking Flow (HH Pegasus).