Skip to content

Surge Detection

Taxi & Private HireProductsSurge › Surge Detection

beta

Continuously finds where demand is genuinely outstripping supply and turns each hotspot into a single, priced live event. A few times an hour it reads how busy each small area is, groups neighbouring busy areas into one demand cluster per company, and tracks each cluster through its life — formed as it appears, updated as it grows, merged when two overlap, and wound down once it cools (smoothed so it does not flicker on and off). Each cluster knows the operator zones it covers — the anchor used to recruit drivers, so a hotspot with no drawn zone is tracked but will not surge — and the window it is active for. The cluster is the unit that pricing charges against and that the driver pool pays out for.

From a busy map to a surge zone

The platform reads how busy each cell of a fine geographic grid is, against its own learned capacity. When a contiguous patch of cells is genuinely over capacity, they're grouped into a single surge zone — a first-class, tracked area that pricing and the driver pool both key off. It updates every few minutes, two adjacent hotspots that grow together merge into one zone, and a brief blip ramps down instead of flickering on and off.

Why you can't dodge it by nudging your pickup

The uplift isn't a hard wall at the zone's edge. Two things keep it smooth. First, each cell's busyness is blended with its neighbours, so neighbouring cells read almost the same — a noisy single-point reading can't spike or dodge a cell on its own. Second, the price follows a damped curve that eases in from the onset threshold and ramps up toward the peak, rather than jumping. So the uplift decreases gradually toward the boundary: moving a pickup a few metres only steps to a slightly lower uplift — it never falls off a cliff to zero.

+40% +12% +25% move 50 m Uplift fades toward the edge peak of the zone · +40% inner · +25% edge · +12% just outside · +5% beyond · no uplift Nudging a pickup a few metres steps from +12% to +5% — never to zero. The neighbour blend and the smooth ramp leave no cliff to game.
The surge zone, shaded by uplift. Intensity (and price) eases down ring by ring to the boundary; the dashed line is where the operator's zone covers it.
+40% 0 onset (busy) at capacity a hard step would be gameable → damped curve — eases in, no cliff
Busyness drives the uplift along a smooth, damped curve from onset to capacity — not a step at the threshold. There's also a platform cap: surge can never more than double a fare.

What the customer pays is Surge Pricing; the demand signal comes from busy-area metrics.

Fields

FieldTypeDescription
clientId bigint Tenant scope. Every tenant-aware entity carries this; `ClientFilter` enforces row-level isolation on read; the multi-tenancy routing layer (`/client/{clientId}`) sets it at create time. Surfaced only under `admin` / `tripLog` groups — never to end users.
internalKey string Optional client-supplied external reference / idempotency key. When present, lets external systems correlate platform-side records back to their own source-of-truth ids. Not persisted to a column — populated by the request handler when the caller sets it.
__objectType string Discriminator string (entity class short-name) emitted alongside the id in serialized output. Resolved at read time by `getObjectType()`; lets the FE dispatch entity-specific rendering without inspecting the URL.
id bigint Snowflake-style primary key (unsigned BIGINT). Generated by `IdFactory` at create time; surfaced to the FE / API as a `G`-prefixed string and stripped back to plain bigint server-side before Doctrine lookup.
createdDate integer Unix timestamp the row was first persisted. Set in the entity's PrePersist hook; never rewritten on subsequent updates.
updatedDate integer Unix timestamp the row was last touched. Bumped on every commit that hits the Doctrine UoW for this entity; drives FE invalidation + the listing change cursor.
passiveUpdatedDate int Read-through alias for `updatedDate` exposed under different serializer groups. Lets the FE distinguish "real edit" from "background touch" projections without changing the underlying column.
listingUpdatedDate int Listing-projection timestamp surfaced only under the `listMode` group. Driven by `TripCache` and other listing-shape refreshers separately from `updatedDate` so a listing rebuild doesn't trigger detail-page invalidation.