Surge Detection
Taxi & Private Hire › Products › Surge › 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.
What the customer pays is Surge Pricing; the demand signal comes from busy-area metrics.
Related
Fields
| Field | Type | Description |
|---|---|---|
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. |