fivenines
7/38
Lesson 1.5 99.9%

Quotes and pricing v1: surge as market clearing

🎯 Objectives
  • Assemble a fare quote from base rates, ETA, and a surge multiplier — and make it binding.
  • Explain surge as the exchange's price-discovery mechanism, computed per hex cell.
🔗 Connect

From 0.2: surge is how the exchange balances supply and demand. From 1.3: the Geo facade returns cached cell-pair ETAs. From 1.4: the geo index knows supply per cell — pricing reuses both.

A quote = base fare + rate_per_km × distance + rate_per_min × ETA, all per city and vehicle tier, times a surge multiplier. Surge v1 is refreshingly simple: a periodic job (every 30 s) computes, per hex cell neighborhood, the ratio of open ride requests to available drivers, maps it through a step function to a multiplier (1.0× – 3.0×), smooths it against the previous value to avoid flapping, and publishes the result to a cache that pricing reads. That's it — no ML at this tier. It reappears as a streaming job in 2.10.

The subtle requirement is that a quote is an offer the rider accepts — a mini contract. So quotes are persisted with an id, a price, and a 5-minute expiry; the ride request references the quote id and is honored at that price even if surge moved meanwhile. Legally and UX-wise, re-pricing at request time is a bait-and-switch; architecturally, persisting quotes gives us an audit trail and a natural idempotency anchor for the request flow (1.6).

sequenceDiagram
    autonumber
    participant R as Rider app
    participant GW as API gateway
    participant P as Pricing service
    participant G as Geo facade
    participant S as Surge cache
    participant DB as PostgreSQL
    R->>GW: quote request {pickup, dropoff, tier}
    GW->>P: forward with rider id
    P->>G: ETA + distance (cell-pair, mostly cached)
    G-->>P: 14 min, 6.2 km
    P->>S: surge multiplier for pickup cell
    S-->>P: 1.4x
    P->>P: fare = (base + km + min rates) × 1.4
    P->>DB: persist quote {id, fare, expires in 5 min}
    P-->>R: quote id + fare + ETA for all tiers
    Note over R: rider taps Request — quote id anchors the price (1.6)
Worked example: one quote. Two cache reads and one insert — the whole path is built to be cheap because riders quote ~5× more often than they ride.
flowchart LR
  RD[("Redis geo index — supply per cell")] --> SJ["Surge job — every 30 s"]
  TRIP[("Open requests — demand per cell")] --> SJ
  SJ --> SM["ratio to multiplier step function + smoothing"]
  SM --> SC[("Surge cache — cell to multiplier")]
  SC --> P["Pricing service"]
  SC --> HEAT["Driver app heatmap — steer supply toward surge"]
Surge v1: a 30-second batch loop. Publishing the heatmap to drivers closes the market loop — price moves supply, not just demand.
⚠️ Pitfall

Surge computed per single cell is noisy garbage — one request in an empty cell reads as infinite demand. Compute over a neighborhood (the cell plus two rings), smooth over time, and clamp the multiplier. Pricing bugs are trust-destroying incidents, not just technical ones.

Next step

See what actually stuck.

Take the practice scenarios now.