A matching engine that understands roads, time and trust.
Pairing commuters is a geospatial and scheduling problem. Our prototype solves it with PostGIS, self-hosted OSRM road routing and a transparent scoring model, running asynchronously so the app stays instant at rush hour.
We match trips, not dots on a map.
Point-to-point matching misses most real carpools. By storing each pilot's full road path as a PostGIS LineString, we can pair riders whose pickup and drop lie along that path, even when the two start points are kilometres apart.
Pickup and drop both within 2.5 km of the pilot's start and end.
e.g. Neighbours working in the same tech park.
Pickup and drop sit close to the pilot's road path with ≥ 75% overlap.
e.g. Rider joins 3 km down the pilot's highway and gets off before the pilot's office.
Both ends fall inside the 4 km corridor around either route line.
e.g. Rider is a short detour off the pilot's usual route.
Six stages from “request” to “ranked match card.”
Route captured
Origin, destination, window and days, plus the OSRM road polyline and distance.
Job enqueued
The API returns instantly. A BullMQ job carries the match work off the request path.
Spatial pre-filter
PostGIS ST_DWithin on GIST indexes: 8 km endpoints or a 4 km corridor, capped at 100.
Schedule filter
Shared weekdays (or the ride date) and overlapping departure windows.
Score & rank
Weighted route, time, detour and trust score. Anything below the threshold is dropped.
Notify both sides
Deduped pending matches with a 24 h TTL. Notifications go through their own queue.
Transparent, tunable, and explainable to users.
Every match gets a composite score from four signals. Because the weights are explicit, we can show riders why a pilot ranks first, and tune the model from real acceptance data once we launch.
score = route × 0.35
+ time × 0.35
+ (1 − min(detourKm / 10, 1)) × 0.20
+ (trust / 5) × 0.10From seat offer to settled payment.
Every interaction between the apps, API, queue, workers and database, in the order it happens for a single shared ride.
Commute demand spikes at 9 AM. Our system is built for that curve.
GIST spatial indexes
Candidate lookup is an index scan, not a full table scan, so latency stays flat as the city grows.
Hard candidate cap
Each job scores at most 100 candidates, which bounds CPU per match whatever the density.
Async by default
Matching can take 100 ms – 2 s at peak. Moving it off the HTTP thread keeps OTP, profile and payment p99s low.
Reverse matching
A new pilot offer enqueues jobs for nearby waiting riders. Supply finds demand, not only the other way round.
Retries with backoff
Three attempts with exponential backoff absorb transient DB or Redis failures without losing a request.
Dedupe + TTL
The same pilot–rider pair is never matched twice. Stale matches expire after 24 h.
Unfulfilled requests are kept, not deleted. After launch, that becomes a live heatmap of where pilot supply is missing, by corridor and by hour.
Over time, recurring pairs, ratings and trust scores form a commute graph that new entrants can't copy.