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 Scoring Logic

This doc explains, in plain language, how search results are ordered and how RPV affects that order.

Quick picture

  • Search starts with text relevance (how well the name/tags match the keyword).
  • We then boost results using a few popularity and quality signals.
  • These boosts change ordering, they do not remove results.
  • RPV logic is implemented for both guest users and members.
  • Metarank is used on the search page and for search suggestions to re-rank results for members only
  • AWS Personalize is used in homepage sections and group landing pages to re-rank restaurant results for members only.

Signals we use

SignalFieldFactorModifierWeightWhat it meansWhen it applies
Revenuetop_gmv0.0001log1p200Restaurants that drive more revenue get a lift.Always
Coverstotal_covers0.001log1p100More bookings = more trust.Always
Reviews countreviews_count0.1log1p150Higher volume of reviews gets a lift.Always
Review qualitybayesian_reviews_score50nonedefaultStrong ratings for established restaurants.When reviews count is 5+
New restaurant qualitygoogle_review_score50nonedefaultA fair start for new restaurants.When reviews count is below 5 or missing
Favoritesfavorites_count0.5log1p50More favorites means more interest.Always
RPV (multi-window)bayesian_rpv_score_1d / 7d / 30d1.0none75 / 45 / 30Revenue per view compared to the average.Always
Distancecoordinates-gauss150Closer restaurants get a lift.When location is provided and sort is not nearest-first

If a field is missing, it does not add any boost. We can adjust the weight to make a signal more or less dominant. RPV uses a fixed 50% / 30% / 20% split across 1d/7d/30d, which translates to weights 75 / 45 / 30.

What RPV means

RPV stands for Revenue Per View. We compute it for the last 1 day, 7 days, and 30 days, then blend the three windows in the scoring function. It answers:

When people view this restaurant, how much revenue does it generate compared to the average restaurant?

To keep it fair for low-traffic restaurants, we smooth the score:

  • If a restaurant has very few views, we lean more on the global average.
  • If it has many views, we trust its own data more.

The score is normalized:

  • 1.0 means about average
  • > 1.0 means better than average
  • < 1.0 means below average

RPV affects ranking for all search requests by default and always blends 1d/7d/30d scores with a fixed split.

RPV data source

RPV is computed from ClickHouse analytics data.

In Puma, the implementation reads from the ClickHouse table:

  • ecommerce_purchases_item_id_report (in the configured database CLICKHOUSE_DATABASE, default: google_analytics)

and uses these columns:

  • itemId → restaurant id (stored/queried as a string)
  • itemsViewed → views in the time window
  • itemRevenue → revenue in the time window
  • date → day partition used for the 1-day lookback

The query scans the last 30 days and uses conditional sums to derive the 1d/7d/30d windows. It groups by itemId and ignores invalid ids ('(not set)' or empty).

RPV formula

smoothed_rpv = (median_views * global_avg_rpv + revenue) / (median_views + views)
bayesian_rpv_score = global_avg_rpv > 0 ? smoothed_rpv / global_avg_rpv : 0

Where:

  • views = total views in the window (1d/7d/30d)
  • revenue = total revenue in the window (1d/7d/30d)
  • median_views = median views across restaurants with views > 0 in that window
  • global_avg_rpv = total revenue / total views across restaurants with views > 0 in that window

Every day at 03:00 GMT+7, a scheduled job:

  • Pulls the last 30 days of views and revenue from ClickHouse.
  • Calculates 1d/7d/30d RPV for each restaurant.
  • Writes the results into bayesian_rpv_score_1d, bayesian_rpv_score_7d, and bayesian_rpv_score_30d.

When a restaurant is created or reindexed, Puma will reuse the existing score if it exists; otherwise it fetches it from ClickHouse.

Defaults and fallbacks

  • If ClickHouse is disabled or a lookup fails, the system returns a neutral score for each window.
  • If an RPV score is missing in OpenSearch, it adds no boost for that window.

Where to look (for engineers)

  • Scoring logic: apps/puma/src/services/common/query-utils.ts
  • Restaurant search: apps/puma/src/services/restaurant/base-query-builder.ts
  • Suggestions search: apps/puma/src/services/suggestion/search-query-builder.ts
  • RPV calculation: apps/puma/src/services/rpv/rpv-metrics-service.ts
  • Scheduled job: apps/puma/src/services/data-queue/worker-scheduled.ts