Files
Siro/backend/api/demand/getPredictiveDemandZones.php
T

151 lines
5.5 KiB
PHP

<?php
/**
* getPredictiveDemandZones.php
* ─────────────────────────────────────────────────────────────
* API endpoint: يُعيد للسائق قائمة بأفضل المناطق المتوقع
* ارتفاع الطلب فيها خلال الساعة القادمة.
*
* يقرأ من Redis أولاً (cache يُجدَّد كل ساعة بالـ cron)،
* ثم يُصفّي أقرب المناطق لموقع السائق الحالي.
*
* Request (GET/POST):
* lat — خط العرض الحالي للسائق
* lng — خط الطول الحالي للسائق
* radius — نطاق البحث بالكيلومترات (اختياري، افتراضي: 10)
*
* Response:
* { success: true, zones: [...], predicted_hour: X, last_updated: "..." }
*/
require_once __DIR__ . '/../../core/bootstrap.php';
require_once __DIR__ . '/../../functions.php';
header('Content-Type: application/json; charset=utf-8');
// ── Auth ──────────────────────────────────────────────────────
// يستخدم نفس آلية JWT الموجودة في النظام
$driverId = getAuthDriverId(); // دالة موجودة في bootstrap/functions
if (!$driverId) {
http_response_code(401);
echo json_encode(['success' => false, 'message' => 'Unauthorized']);
exit;
}
// ── Parameters ────────────────────────────────────────────────
$driverLat = (float)filterRequest('lat');
$driverLng = (float)filterRequest('lng');
$radiusKm = (float)(filterRequest('radius') ?? 10);
// تحويل km إلى درجات تقريباً (1° ≈ 111km)
$radiusDeg = $radiusKm / 111.0;
// ── Redis Cache ───────────────────────────────────────────────
try {
$redis = getRedisConnection();
} catch (Exception $e) {
$redis = null;
}
$cacheRaw = $redis ? $redis->get('siro:cache:predictive_demand') : null;
if ($cacheRaw) {
$cacheData = json_decode($cacheRaw, true);
$allZones = $cacheData['zones'] ?? [];
// فلتر: فقط المناطق ضمن نطاق السائق
$nearbyZones = array_filter($allZones, function ($z) use ($driverLat, $driverLng, $radiusDeg) {
if ($driverLat == 0 || $driverLng == 0) return true; // لو ما أرسل موقعه، أرجع الكل
return abs($z['lat'] - $driverLat) <= $radiusDeg
&& abs($z['lng'] - $driverLng) <= $radiusDeg;
});
// ترتيب: الأقرب للسائق أولاً
if ($driverLat != 0) {
usort($nearbyZones, function ($a, $b) use ($driverLat, $driverLng) {
$da = abs($a['lat'] - $driverLat) + abs($a['lng'] - $driverLng);
$db = abs($b['lat'] - $driverLat) + abs($b['lng'] - $driverLng);
return $da <=> $db;
});
}
echo json_encode([
'success' => true,
'source' => 'cache',
'predicted_hour' => $cacheData['predicted_hour'] ?? null,
'last_updated' => $cacheData['last_updated'] ?? null,
'zones' => array_values($nearbyZones),
], JSON_UNESCAPED_UNICODE);
exit;
}
// ── Fallback: حساب مباشر من DB إذا لم يتوفر cache ────────────
try {
$con = Database::get('main');
} catch (Exception $e) {
echo json_encode(['success' => false, 'message' => 'DB error']);
exit;
}
$nextHour = ((int)date('H') + 1) % 24;
$dow = (int)date('N');
$gridSize = 0.01;
$sql = "
SELECT
ROUND(pickup_lat / :g) * :g AS lat,
ROUND(pickup_lng / :g) * :g AS lng,
COUNT(*) AS demand_score,
country_code
FROM rides
WHERE
status IN ('completed', 'cancelled_by_driver', 'timeout')
AND HOUR(created_at) = :hour
AND DAYOFWEEK(created_at) = :dow
AND created_at >= DATE_SUB(NOW(), INTERVAL 4 WEEK)
AND pickup_lat BETWEEN (:dlat - :r) AND (:dlat + :r)
AND pickup_lng BETWEEN (:dlng - :r) AND (:dlng + :r)
GROUP BY lat, lng, country_code
HAVING demand_score >= 2
ORDER BY demand_score DESC
LIMIT 10
";
$stmt = $con->prepare($sql);
$stmt->execute([
':g' => $gridSize,
':hour' => $nextHour,
':dow' => $dow,
':dlat' => $driverLat ?: 31.95, // fallback عمّان
':dlng' => $driverLng ?: 35.93,
':r' => $radiusDeg,
]);
$zones = $stmt->fetchAll(PDO::FETCH_ASSOC);
// تسمية المناطق
foreach ($zones as &$z) {
$stmtN = $con->prepare("
SELECT zone_name FROM geofence_zones
WHERE is_active = 1
AND ABS(latitude - :lat) < 0.05
AND ABS(longitude - :lng) < 0.05
ORDER BY ABS(latitude - :lat) + ABS(longitude - :lng) ASC
LIMIT 1
");
$stmtN->execute([':lat' => $z['lat'], ':lng' => $z['lng']]);
$nameRow = $stmtN->fetch(PDO::FETCH_ASSOC);
$z['zone_name'] = $nameRow['zone_name'] ?? 'منطقة قريبة';
$z['lat'] = (float)$z['lat'];
$z['lng'] = (float)$z['lng'];
$z['demand_score'] = (int)$z['demand_score'];
}
unset($z);
echo json_encode([
'success' => true,
'source' => 'realtime',
'predicted_hour' => $nextHour,
'last_updated' => date('Y-m-d H:i:s'),
'zones' => $zones,
], JSON_UNESCAPED_UNICODE);
?>