Overview
The driver matching system uses a sophisticated algorithm to find the best available drivers for incoming trip requests, considering location, vehicle compatibility, driver status, and real-time availability.
Matching Flow
Driver Availability System
Availability Entity
Drivers maintain real-time availability status:
Driver Availability Structure
{
id : string ;
driver : User ; // Driver user entity
isOnline : boolean ; // Presence status
isAvailableForTrips : boolean ; // Matching eligibility
availabilityReason : AvailabilityReason | null ; // NULL = available, else reason code
lastLocation : Point ; // GeoJSON Point (PostGIS)
lastLocationTimestamp : Date ;
currentTripId ?: string ; // Active trip reference
currentVehicleId ?: string ; // Active vehicle
lastOnlineTimestamp : Date ;
lastPresenceTimestamp : Date ; // Heartbeat tracking
}
Availability Reasons
src/modules/drivers-availability/entities/driver-availability.entity.ts
enum AvailabilityReason {
OFFLINE = 'offline' , // Driver explicitly offline
ON_TRIP = 'on_trip' , // Currently serving a trip
UNAVAILABLE = 'unavailable' , // Failed eligibility checks
}
When availabilityReason is null, the driver is eligible for matching.
Operational Eligibility
Before a driver can receive trip offers, they must pass eligibility checks:
src/modules/drivers-availability/services/driver-availability.service.ts:573-681
private async checkOperationalEligibility (
driverId : string ,
currentVehicleId : string | null ,
manager ?: EntityManager ,
): Promise <{
ok : boolean ;
vehicleId : string | null ;
details : {
userOk : boolean ;
profileOk : boolean ;
walletActive : boolean ;
vehicleOk : boolean ;
reason ?: string ;
};
}> {
// 1. User validation
const user = await this . userRepo . findById ( driverId );
const userOk = !! user &&
user. userType === UserType . DRIVER &&
user.status !== UserStatus.BANNED;
// 2. Driver profile validation
const profile = await this . driverProfilesRepo . findByUserIdForEligibility ( driverId , manager );
const profileOk = !! profile &&
profile. isApproved === true &&
profile. driverStatus === DriverStatus . ACTIVE ;
// 3. Wallet validation
const walletActive = await this . driverBalanceRepo . isActiveByDriverId ( driverId , manager );
// 4. Vehicle validation
let vehicleIdToCheck = currentVehicleId ;
if (! vehicleIdToCheck ) {
const candidate = await this . vehiclesRepo . findPrimaryInServiceByDriver ( driverId , manager );
vehicleIdToCheck = candidate ?. id ?? null ;
}
let vehicleOk = false ;
if ( vehicleIdToCheck ) {
const vehicle = await this . vehiclesRepo . findById ( vehicleIdToCheck );
if ( vehicle ) {
const isInService = vehicle . status === VehicleStatus . IN_SERVICE ;
const belongsToDriver = vehicle . driver ?. id === driverId ;
vehicleOk = isInService && belongsToDriver ;
}
}
const ok = userOk && profileOk && walletActive && vehicleOk ;
return { ok , vehicleId : vehicleOk ? vehicleIdToCheck : null , details : { userOk , profileOk , walletActive , vehicleOk } };
}
User Status Driver must be ACTIVE and not BANNED
Profile Approved Driver profile must be approved and active
Wallet Active Driver’s wallet/balance must be in good standing
Vehicle Ready Vehicle must be IN_SERVICE and belong to driver
Location Tracking
Drivers send periodic location pings via WebSocket:
src/modules/drivers-availability/services/driver-availability.service.ts:382-516
async ingestLocationPing ( driverId : string , dto : DriverAvailabilityPingDto ) {
// 1. Ensure driver availability row exists
await this . driverAvailabilityRepo . ensureForDriver ( driverId , {});
const av = await this . driverAvailabilityRepo . findByDriverId ( driverId );
const now = new Date ();
const hasLocation = typeof dto . lat === 'number' && typeof dto . lng === 'number' ;
// 2. Check if driver is matchable
const isMatchable = av . isOnline === true &&
av . isAvailableForTrips === true &&
av . availabilityReason === null &&
! av . currentTripId ;
let shouldSaveLocation = false ;
// 3. First location seed (always save)
if ( hasLocation && ! av . lastLocation ) {
shouldSaveLocation = true ;
}
// 4. Intelligent location updates
if ( ! shouldSaveLocation && hasLocation && isMatchable ) {
const lastWriteTs = Math . max (
av . lastLocationTimestamp ?. getTime () ?? 0 ,
av . updatedAt ?. getTime () ?? 0 ,
);
const secondsSinceLastWrite = ( now . getTime () - lastWriteTs ) / 1000 ;
const MIN_WRITE_INTERVAL_SECONDS = 3 ;
const MAX_LOCATION_AGE_SECONDS = 45 ;
const MIN_DISTANCE_METERS = 8 ;
if ( dto . forceSave ) {
shouldSaveLocation = true ;
} else if ( secondsSinceLastWrite >= MAX_LOCATION_AGE_SECONDS ) {
shouldSaveLocation = true ; // Stale location
} else if ( secondsSinceLastWrite >= MIN_WRITE_INTERVAL_SECONDS ) {
// Check distance moved
const dist = await this . driverAvailabilityRepo . distanceToCurrentLastLocation (
driverId ,
dto . lng ,
dto . lat ,
);
if ( dist === null || dist >= MIN_DISTANCE_METERS ) {
shouldSaveLocation = true ;
}
}
}
// 5. Update location or just heartbeat
if ( hasLocation && shouldSaveLocation ) {
const saved = await this . driverAvailabilityRepo . updateLocationByDriverId ( driverId , {
lastLocation: toGeoPoint ( dto . lat , dto . lng ),
lastLocationTimestamp: dto . reportedAt ? new Date ( dto . reportedAt ) : now ,
lastPresenceTimestamp: now ,
});
this . eventEmitter . emit ( DriverAvailabilityEvents . LocationUpdated , {
at: now . toISOString (),
snapshot: toDriverAvailabilityResponseDto ( saved ),
});
} else {
// Heartbeat only (no location update)
const saved = await this . driverAvailabilityRepo . updateLocationByDriverId ( driverId , {
lastPresenceTimestamp: now ,
});
}
}
The system intelligently throttles location updates based on:
Time since last update : Only if > 3 seconds
Distance moved : Must move > 8 meters
Age of data : Force update if > 45 seconds old
Force flag : Client can force immediate update
Finding Nearby Drivers
The matching algorithm uses PostGIS spatial queries:
Finding Available Drivers (Repository)
// Uses PostGIS ST_DWithin for efficient spatial query
async findNearbyAvailable (
longitude : number ,
latitude : number ,
radiusMeters : number ,
limit : number ,
ttlSeconds : number ,
): Promise < DriverAvailability [] > {
const qb = this . repo . createQueryBuilder ( 'da' )
.leftJoinAndSelect( 'da.driver' , 'driver' )
.leftJoinAndSelect( 'da.currentVehicle' , 'vehicle' )
.where( 'da.isOnline = :online' , { online : true })
.andWhere( 'da.isAvailableForTrips = :avail' , { avail : true })
.andWhere( 'da.availabilityReason IS NULL' )
.andWhere( 'da.currentTripId IS NULL' )
.andWhere( 'da.lastLocationTimestamp > :cutoff' , {
cutoff : new Date ( Date . now () - ttlSeconds * 1000 ),
})
.andWhere(
`ST_DWithin(
da.lastLocation::geography,
ST_SetSRID(ST_MakePoint(:lng, :lat), 4326)::geography,
:radius
)` ,
{ lng : longitude , lat : latitude , radius : radiusMeters },
)
.orderBy(
`ST_Distance(
da.lastLocation::geography,
ST_SetSRID(ST_MakePoint(:lng, :lat), 4326)::geography
)` ,
'ASC' ,
)
.limit(limit);
return qb.getMany();
}
The query filters drivers by:
Online status : isOnline = true
Availability : isAvailableForTrips = true and availabilityReason IS NULL
No active trip : currentTripId IS NULL
Fresh location : Updated within ttlSeconds (default 90s)
Proximity : Within radiusMeters of pickup location
Results are sorted by distance (nearest first).
Assignment Process
Creating an Offer
When a suitable driver is found:
{
id : string ; // Assignment UUID
trip : Trip ; // Reference to trip
driver : User ; // Selected driver
vehicle : Vehicle ; // Driver's vehicle
status : AssignmentStatus ; // Initially OFFERED
offeredAt : Date ; // Timestamp of offer
ttlExpiresAt : Date ; // Auto-expire time (e.g., +20s)
metadata : {
etaSeconds : number ; // Estimated time to pickup
distanceMeters : number ; // Distance to pickup
};
}
Assignment Statuses
enum AssignmentStatus {
OFFERED = 'offered' , // Pending driver response
ACCEPTED = 'accepted' , // Driver accepted
REJECTED = 'rejected' , // Driver declined
EXPIRED = 'expired' , // TTL timeout
CANCELLED = 'cancelled' , // Trip cancelled or other assignment accepted
}
Sequential Matching
If a driver rejects or times out:
src/modules/trip/services/trip.service.ts:629-703
async rejectAssignment (
assignmentId : string ,
dto : RejectAssignmentDto ,
): Promise < ApiResponseDto < { assignmentId : string ; nextAssignmentId ?: string ; message : string } >> {
const now = new Date ();
// 1. Mark offer as REJECTED
const tripId = await withQueryRunnerTx (
this . dataSource ,
async ( _qr , manager ) => {
const a = await this . tripAssignmentRepo . getOfferedById ( assignmentId , manager );
if ( ! a ) throw new ConflictException ( 'Assignment is not active or not found' );
await this . tripAssignmentRepo . rejectOffer ( assignmentId , now , manager );
await this . tripEventsRepo . append (
a . trip . id ,
TripEventType . DRIVER_REJECTED ,
now ,
{
assignment_id: assignmentId ,
driver_id: a . driver . id ,
reason: dto ?. reason ?? null ,
},
manager ,
);
this . events . emit ( TripDomainEvents . DriverRejected , {
at: now . toISOString (),
tripId: a . trip . id ,
assignmentId ,
driverId: a . driver . id ,
reason: dto ?. reason ?? null ,
});
return a . trip . id ;
},
{ logLabel: 'trip.assignment.reject' },
);
// 2. Immediately try next candidate
const next = await this . tripHelpers . runMatchingOnce ( tripId , {
searchRadiusMeters: 3000 ,
maxCandidates: 5 ,
offerTtlSeconds: 20 ,
});
return {
success : true ,
message : 'Assignment rejected; matching continued' ,
data : {
assignmentId ,
nextAssignmentId : next . assignmentId ,
message : next . message ,
},
};
}
The system tries drivers sequentially (one at a time) rather than broadcasting to multiple drivers simultaneously. This prevents multiple drivers racing to the same trip.
Matching Parameters
Configurable Settings
{
searchRadiusMeters : 5000 , // Initial search radius (5km)
maxCandidates : 10 , // Max drivers to consider
offerTtlSeconds : 20 , // Time driver has to respond
expandRadiusOnRetry : true , // Expand radius if no drivers found
maxRetries : 3 , // Max matching attempts
}
Vehicle Compatibility
The matching considers:
Vehicle Matching Criteria
// Trip specifies:
{
requestedVehicleCategory : VehicleCategory ; // e.g., Auto, Moto, Van
requestedServiceClass : VehicleServiceClass ; // e.g., Standard, Premium
}
// Driver's vehicle must match:
// - Same category OR compatible category
// - Same or higher service class
// - Status = IN_SERVICE
Real-Time Notifications
When a driver receives an offer:
WebSocket Event to Driver
// Event: 'trip:assignment:offered'
{
assignmentId : "assignment-uuid" ,
tripId : "trip-uuid" ,
ttlSec : 20 ,
expiresAt : "2024-03-15T10:30:00Z" ,
pickup : {
lat : 23.1136 ,
lng : - 82.3666 ,
address : "Havana, Cuba"
},
destination : {
lat : 23.1330 ,
lng : - 82.3830 ,
address : "Vedado, Havana"
},
fareEstimate : 150.00 ,
etaSeconds : 180
}
Matching Edge Cases
If no drivers are available or all reject/timeout: // Trip transitions to NO_DRIVERS_FOUND state
await this . tripRepo . moveToNoDriversFoundWithLock ( tripId , now , manager );
this . events . emit ( TripDomainEvents . NoDriversFound , {
at: now . toISOString (),
tripId ,
reason: 'no_candidates_found' ,
});
If driver doesn’t respond within TTL: // Scheduled job marks assignment as EXPIRED
await this . tripAssignmentRepo . expireOffer ( assignmentId , now , manager );
// Automatically try next candidate
const next = await this . tripHelpers . runMatchingOnce ( tripId , matchingOptions );
Driver Becomes Unavailable
If driver goes offline or loses eligibility:
Active offers remain valid until TTL
Driver won’t receive new offers until available again
System logs availability change event
Optimization Strategies
Spatial Indexing PostGIS GIST index on lastLocation for fast proximity queries
Location Throttling Smart updates reduce DB writes by 80% while maintaining accuracy
Eligibility Caching Driver eligibility pre-computed on status changes
Sequential Offers One driver at a time prevents race conditions and fairness issues
Trip Lifecycle See how matching fits into the full trip flow
Real-Time Communication Learn about WebSocket notifications