quine-RideRN/docs/pricing.md
Philip Peterson d0fb299f13 Price rides with rate cards, surge and driver share, by market
Dispatch now prices rides with a basic model inspired by public
information on Uber and Lyft (docs/pricing.md): base + per mile + per
minute, times a per-zone surge multiplier from recent requests against
available drivers, plus a booking fee, with a minimum. Riders get an
upfront quote per ride type and book against it; drivers see and earn
a configurable share (75%) of the fare.

Rate cards are per market, chosen by pickup location: San Francisco
(third-party 2026 rates) and Fairbanks, AK (its own UberX and UberXL
rates, Comfort from Anchorage, no Black). Everything is configurable
via PRICING_FILE or POST /test/reset.

Tests use a simple test card by default so auction scenarios keep
their numbers; new scenarios cover SF rates, Fairbanks rates and surge.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 08:47:10 -08:00

95 lines
8.9 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Pricing model
RideRN prices rides with a basic model inspired by what's publicly known about Uber and Lyft. Their actual models are proprietary machine-learning systems; this is a transparent approximation to build on, not a copy. The code is `server/pricing.mts`; the dispatch server applies it (`server/dispatch.mts`).
## What's publicly known
- **Base formula.** A base fare, plus a per-minute rate times estimated time, plus a per-mile rate times estimated distance, plus a booking fee, with a minimum fare. Rates differ by city and ride type. ([Ridester](https://www.ridester.com/uber-rates-cost/), [RideGuru](https://ride.guru/lounge/p/uber-pricing-how-does-it-work))
- **Surge / Prime Time.** A multiplier on that base when demand outstrips drivers. Lyft applies Prime Time as a percentage of the subtotal ([TaxiFareFinder](https://www.taxifarefinder.com/newsroom/2016/04/05/lyft-how-to-calculate-your-fare/)). Measuring Uber over four weeks in San Francisco and Manhattan, Northeastern researchers found surge is set per zone (a jigsaw of areas), updated every 5 minutes, usually lasts under 10 minutes, and strongly reduces demand while barely drawing more drivers ([paper](https://mislove.org/publications/Uber-IMC.pdf), [summary](https://news.northeastern.edu/2015/10/29/northeastern-researchers-unlock-details-of-ubers-surge-pricing-and-suggest-ways-to-avoid-it/)).
- **Upfront pricing.** Since 2016 Uber quotes a price before the trip, predicted from route, time of day and demand. In 2017 its "route-based pricing" used machine learning to estimate what groups of riders would pay for a route and time, so the same trip could cost more from a wealthier neighborhood ([Bloomberg](https://www.bloomberg.com/news/articles/2017-05-19/uber-s-future-may-rely-on-predicting-how-much-you-re-willing-to-pay), [Hoodline](https://hoodline.com/2017/05/uber-s-route-based-pricing-charges-riders-based-on-neighborhood/)).
- **Driver pay is separate.** After Uber's 2023 dynamic pay change, a study of 1.5M UK trips found its cut rose from about 25% to 29%, larger on higher fares ([Oxford](https://www.ox.ac.uk/news/2025-06-23-new-oxford-research-reveals-uber-s-algorithmic-pricing-leaves-drivers-and-passengers), [paper](https://arxiv.org/pdf/2506.15278)). Lyft guarantees drivers at least 70% of rider payments each week, and says they averaged about 88% after external fees ([Lyft](https://www.lyft.com/blog/posts/lyft-expands-driver-earnings-commitment-nationwide)).
- **APIs don't help.** Uber's price-estimate endpoint is restricted to approved partners and its terms forbid competitive price comparison ([Uber docs](https://developer.uber.com/docs/riders/references/api/v1.2/estimates-price-get)). Automated quote collection from their apps would breach their terms.
## The model
```
fare = max(minimumFare, (baseFare + perMile × miles + perMinute × minutes) × surge + bookingFee)
earnings = fare × driverShare
```
1. **Rate card** for the pickup's market and the ride type. A ride type with no card in a market isn't offered there.
2. **Surge** for the pickup's zone (below). The booking fee isn't surged.
3. **Minimum fare** applies to the total.
4. The rider gets an **upfront quote** with a price per ride type. Booking uses the quote's price if it hasn't expired (2 minutes); otherwise dispatch sends fresh prices to confirm.
5. The **auction** (see `docs/multi-phone-simulator.md`) starts from the quoted fare: its rounds are multiples of it.
6. The driver sees and earns **`driverShare`** (75%) of what the rider pays; the rest is the platform's cut.
Deliberately left out: pricing by predicted willingness to pay, i.e. by who the rider seems to be or where they're coming from. Surge by zone responds to supply and demand; neighborhood-based pricing is the practice that drew criticism, and a basic model doesn't need it.
### Base rates (San Francisco)
Uber no longer publishes rate cards, so these come from third parties.
| Type | Base | Per mile | Per minute | Booking fee | Minimum | Source |
| ------- | ----- | -------- | ---------- | ----------- | ------- | ------ |
| UberX | $2.50 | $1.55 | $0.32 | $3.00 | $7.00 | [RideWise, March 2026](https://getridewise.com/blog/uber-cost-per-mile) (checked against receipts); minimum from [TaxiFareFinder](https://www.taxifarefinder.com/main.php?city=Uber-X-San-Francisco-CA) |
| Comfort | $3.00 | $1.85 | $0.38 | $3.00 | $9.00 | Estimate: scaled from reported national Comfort vs X averages |
| UberXL | $3.00 | $2.10 | $0.40 | $3.00 | $9.25 | Estimate: [TaxiFareFinder SF](https://www.taxifarefinder.com/main.php?city=Uber-XL-San-Francisco-CA) base and minimum, national per-mile average |
| Black | $8.00 | $3.75 | $0.60 | $3.00 | $15.00 | Estimate: older SF Black rates and national averages |
Sources disagree: TaxiFareFinder lists older UberX rates of $2.00 + $1.21/mi + $0.22/min. Worth checking against a few real receipts before relying on any of them.
For the simulator's 5 km, 10 minute fixture trip these give UberX $13.52, Comfort $15.55, UberXL $16.52, Black $28.65.
### Base rates (Fairbanks, AK)
From [uphail.com's Fairbanks page](https://uphail.com/us/ak/fairbanks/) (September 2026). Fairbanks lists only UberX and UberXL, so Comfort uses [Anchorage's](https://uphail.com/us/ak/anchorage/) rates from the same source. Neither city lists Black, so Black isn't offered in Fairbanks.
| Type | Base | Per mile | Per minute | Booking fee | Minimum | Source |
| ------- | ----- | -------- | ---------- | ----------- | ------- | ------ |
| UberX | $2.77 | $1.86 | $0.31 | $2.55 | $8.43 | Fairbanks |
| Comfort | $3.74 | $1.92 | $0.40 | $3.58 | $11.75 | Anchorage (substitute) |
| UberXL | $3.25 | $2.25 | $0.36 | $2.55 | $8.70 | Fairbanks |
Other estimator sites disagree widely for Fairbanks: [taxis-fare.com](https://taxis-fare.com/uber-fare-city-fairbanks-fairbanks-north-star) lists UberX at $2.55 + $1.75/km + $0.35/min, apparently per-mile figures labeled per km, and [taxi-allo.com](https://www.taxi-allo.com/uber-cost-fairbanks-uber-price-alaska-uber-fare-united-states)'s figures don't fit the usual structure. Uphail's are the most complete and match what other searches return, but they're still third-party estimates: check them against real Fairbanks receipts.
For the 5 km, 10 minute fixture trip: UberX $14.20, Comfort $17.29, UberXL $16.39.
### Markets
A market is a center and a radius; a pickup uses the rate cards of the nearest market it's within (`marketFor` in `server/pricing.mts`), or `defaultMarket` if none.
| Market | Center | Radius |
| --- | --- | --- |
| `sf` San Francisco | 37.7749, −122.4194 | 60 km |
| `fairbanks` Fairbanks | 64.8378, −147.7164 | 60 km (includes North Pole and Fort Wainwright) |
The default market is San Francisco, so pickups anywhere else (Anchorage included) get San Francisco prices; that keeps the app usable in development wherever you are. A launch would rather refuse pickups outside its markets.
### Surge
The city is a grid of zones, `zoneSizeDegrees` (0.02°, about 2 km) across. For the pickup's zone:
- **demand** = ride requests from that zone in the last `demandWindowMs` (10 minutes)
- **supply** = drivers there who are connected, driving, and have room for a ride (drivers report their position)
- **multiplier** = 1 + (demand ÷ max(supply, 1) − `threshold`) × `sensitivity`, rounded down to `step` (0.1), between 1 and `maxMultiplier` (3)
With the defaults (threshold 1, sensitivity 0.5): 2 requests for 1 driver → 1.5×; 3 for 1 → 2×; as many requests as drivers → no surge. A zone's multiplier is recomputed at most every `intervalMs` (60 s); the 2015 study measured Uber at 5 minutes. The rider sees "Busy area · prices are 1.5× higher right now".
## Configuring it
Everything above is a setting, since the right values depend on the market:
| Setting | Default |
| --- | --- |
| `markets`, `defaultMarket` | San Francisco and Fairbanks; default `sf` |
| `rateCards` | `{ sf: { x, comfort, xl, black }, fairbanks: { x, comfort, xl } }` |
| `surge` | `{ enabled: true, zoneSizeDegrees: 0.02, intervalMs: 60000, demandWindowMs: 600000, threshold: 1, sensitivity: 0.5, step: 0.1, maxMultiplier: 3 }` |
| `driverShare` | `0.75` |
| `quoteTtlMs` | `120000` |
- **At startup:** `PRICING_FILE=path/to/pricing.json npm run dispatch`, with any subset of the settings; rate cards merge per market and ride type, so a file can adjust one rate or add a market (with its `markets` entry and `rateCards`).
- **Per test:** `POST /test/reset` with `{ "pricing": { ... } }`, or `cy.resetDispatch({ pricing, rates })`.
- **By market:** add a `markets` entry and its `rateCards`; `marketFor(pickup)` picks it for pickups in range.
Tests default to a simple UberX card (`TEST_PRICING` in `cypress/support/e2e.ts`: $2.50 + $0.85/min, so the fixture trip is exactly $11.00) with surge off, so auction scenarios don't depend on the real rates. The `base-rates`, `fairbanks-rates` and `surge-pricing` scenarios cover the real cards, markets and surge.