Update: 2026-07-02 16:52:25
This commit is contained in:
@@ -449,6 +449,86 @@ if (!empty($passenger_id)) {
|
||||
}
|
||||
}
|
||||
|
||||
// 🆕 المطابقة الذكية للوجهة (Destination Matching)
|
||||
// Architecture: GEORADIUS (O(log N+K)) → detail fetch → SQL fallback
|
||||
$isDestinationMatch = false;
|
||||
$matchedDriverId = 0;
|
||||
|
||||
try {
|
||||
$bestDriverId = 0;
|
||||
$foundViaRedis = false;
|
||||
|
||||
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
// 🟥 Step 1: Redis GEORADIUS — ultra-fast O(log N + K) search
|
||||
// Finds all drivers whose destination is within 3.5 km of
|
||||
// the passenger's drop-off point. No PHP math. No full scan.
|
||||
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
if (isset($redis)) {
|
||||
try {
|
||||
// geoRadius(key, longitude, latitude, radius, unit, [options])
|
||||
$nearbyDriverIds = $redis->geoRadius(
|
||||
'geo:driver:destinations',
|
||||
(float)$destLng,
|
||||
(float)$destLat,
|
||||
3.5,
|
||||
'km',
|
||||
['COUNT' => 1, 'ASC'] // closest first, stop after 1
|
||||
);
|
||||
|
||||
if (!empty($nearbyDriverIds)) {
|
||||
$candidateId = (string)$nearbyDriverIds[0];
|
||||
// Verify destination is still active today (read detail JSON)
|
||||
$detailJson = $redis->get("driver:destination:{$candidateId}");
|
||||
$detail = $detailJson ? json_decode($detailJson, true) : null;
|
||||
|
||||
if ($detail
|
||||
&& !empty($detail['is_active'])
|
||||
&& ($detail['usage_date'] ?? '') === date('Y-m-d')
|
||||
) {
|
||||
$bestDriverId = $candidateId;
|
||||
$foundViaRedis = true;
|
||||
} else {
|
||||
// Stale entry — clean up from geo index
|
||||
$redis->zRem('geo:driver:destinations', $candidateId);
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
error_log("[Destination Matching] Redis GEORADIUS error: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
// 🟦 Step 2: SQL Fallback (only if Redis unavailable or empty)
|
||||
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
if (!$foundViaRedis) {
|
||||
$stmtDest = $con->prepare("
|
||||
SELECT driver_id,
|
||||
( 6371 * acos( cos( radians(:endLat) ) * cos( radians( target_latitude ) )
|
||||
* cos( radians( target_longitude ) - radians(:endLng) ) + sin( radians(:endLat) )
|
||||
* sin( radians( target_latitude ) ) ) ) AS dest_distance
|
||||
FROM driver_destinations
|
||||
WHERE is_active = 1 AND usage_date = CURDATE()
|
||||
HAVING dest_distance <= 3.5
|
||||
ORDER BY dest_distance ASC
|
||||
LIMIT 1
|
||||
");
|
||||
$stmtDest->execute([':endLat' => $destLat, ':endLng' => $destLng]);
|
||||
$destMatch = $stmtDest->fetch(PDO::FETCH_ASSOC);
|
||||
if ($destMatch) {
|
||||
$bestDriverId = $destMatch['driver_id'];
|
||||
}
|
||||
}
|
||||
|
||||
if ($bestDriverId) {
|
||||
$isDestinationMatch = true;
|
||||
$matchedDriverId = $bestDriverId;
|
||||
// Passenger gets 14% discount for Destination Match
|
||||
$discount = max((float)$discount, 14.0);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
error_log("[Destination Matching] Error: " . $e->getMessage());
|
||||
}
|
||||
|
||||
// Calculate prices for all categories
|
||||
foreach ($categories as $key => $carType) {
|
||||
$result = calculateDynamicPrice($country, $minFare, $distance, $duration, $kazanRow, $startNameAddress, $endNameAddress, $destLat, $destLng, $passengerLat, $passengerLng, $carType);
|
||||
@@ -497,6 +577,8 @@ if (isset($encryptionHelper)) {
|
||||
// ✅ FIX R6: تضمين distance و duration في الـ token لمنع التلاعب
|
||||
'distance' => $distance,
|
||||
'duration' => $duration,
|
||||
'is_destination_match' => $isDestinationMatch ? 1 : 0,
|
||||
'matched_driver_id' => $matchedDriverId,
|
||||
'expires' => time() + 420, // Valid for 7 minutes
|
||||
'prices' => $pricesRaw
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user