171 lines
7.5 KiB
PHP
171 lines
7.5 KiB
PHP
<?php
|
|
/**
|
|
* cron_ai_engine.php
|
|
* المحرك الرئيسي للذكاء الاصطناعي (AI Engine)
|
|
* يتم تشغيله كـ Cron Job كل 30 دقيقة أو ساعة لتقليل الضغط على السيرفر.
|
|
*
|
|
* يدمج 3 وحدات (Modules) ذكية:
|
|
* 1. AI Pricing (Total Price Math): تعديل جدول kazan ليكون السعر الإجمالي أرخص بـ 6.5% من المنافس الأقوى بدقة.
|
|
* 2. AI Dispatch: تحديد مناطق الذروة وتوجيه السائقين إليها.
|
|
* 3. AI Retention: اصطياد الركاب الخاملين.
|
|
*/
|
|
|
|
require_once __DIR__ . '/../core/bootstrap.php';
|
|
require_once __DIR__ . '/../functions.php';
|
|
|
|
try {
|
|
$con = Database::get('main');
|
|
$redis = getRedisConnection();
|
|
} catch (Exception $e) {
|
|
die("Connection failed: " . $e->getMessage() . "\n");
|
|
}
|
|
|
|
echo "Starting Siro AI Engine...\n";
|
|
|
|
// نسبة الخصم المستهدفة (6.5% من إجمالي سعر الرحلة)
|
|
$targetMargin = 0.065;
|
|
|
|
// ==========================================
|
|
// 1. وحدة التسعير الديناميكي بناءً على السعر الإجمالي
|
|
// ==========================================
|
|
echo "1. Running Smart Pricing Module (Total Price Formula)...\n";
|
|
try {
|
|
$sql = "SELECT country_code,
|
|
AVG(price_per_km) as avg_price_km,
|
|
MIN(price_per_km) as min_price_km
|
|
FROM scraped_competitor_prices
|
|
WHERE created_at >= DATE_SUB(NOW(), INTERVAL 3 HOUR)
|
|
AND price_per_km > 0
|
|
GROUP BY country_code";
|
|
|
|
$stmt = $con->query($sql);
|
|
$competitorRates = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
foreach ($competitorRates as $rate) {
|
|
$country = $rate['country_code'];
|
|
$countryNameMap = ['JO' => 'Jordan', 'SY' => 'Syria', 'EG' => 'Egypt', 'IQ' => 'Iraq'];
|
|
$countryName = $countryNameMap[$country] ?? null;
|
|
|
|
if ($countryName) {
|
|
$avgKmPrice = (float)$rate['avg_price_km'];
|
|
$minKmPrice = (float)$rate['min_price_km'];
|
|
|
|
// 1. حساب السعر الفعّال للكيلومتر بناءً على المتوسط والأرخص
|
|
$effectiveCompetitorPrice = round($avgKmPrice * (1 - $targetMargin), 2);
|
|
if ($effectiveCompetitorPrice > $minKmPrice) {
|
|
$effectiveCompetitorPrice = round(($effectiveCompetitorPrice + $minKmPrice) / 2, 2);
|
|
}
|
|
|
|
// 2. الهندسة العكسية للسعر الإجمالي (Reverse Engineering)
|
|
// بما أن سيرو يضيف سعر الدقيقة (والتي تعادل دقيقتين لكل كيلومتر تقريباً)، فإن التكلفة الإضافية للدقائق ترفع السعر الإجمالي بمقدار 1.5x
|
|
// لضمان أن يكون السعر النهائي أقل بـ 6%، نقسم الناتج على 1.5 ليمتص تكلفة الدقائق.
|
|
$calculatedSpeedPrice = round($effectiveCompetitorPrice / 1.5, 3);
|
|
|
|
// 3. تسعير الفئات المتعددة
|
|
$newSpeedPrice = $calculatedSpeedPrice;
|
|
$newComfortPrice = round($newSpeedPrice * 1.30, 3);
|
|
$newLadyPrice = round($newSpeedPrice * 1.10, 3);
|
|
$newElectricPrice = round($newSpeedPrice * 1.20, 3);
|
|
$newVanPrice = round($newSpeedPrice * 1.50, 3);
|
|
$newDeliveryPrice = round($newSpeedPrice * 0.90, 3);
|
|
$newMishwarVipPrice = round($newSpeedPrice * 1.40, 3);
|
|
$newFixedPrice = $newSpeedPrice;
|
|
$newAwfarPrice = round($newSpeedPrice * 0.85, 3);
|
|
|
|
// أسعار الدقائق
|
|
if ($country === 'JO') {
|
|
$newNormalMin = 0.06;
|
|
$newPeakMin = 0.07;
|
|
$newLateMin = 0.08;
|
|
} else {
|
|
$newNormalMin = round($newSpeedPrice / 4, 3);
|
|
$newPeakMin = round($newNormalMin * 1.15, 3);
|
|
$newLateMin = round($newNormalMin * 1.25, 3);
|
|
}
|
|
|
|
$updateSql = "UPDATE kazan
|
|
SET speedPrice = :speedPrice,
|
|
comfortPrice = :comfortPrice,
|
|
familyPrice = :ladyPrice,
|
|
deliveryPrice = :deliveryPrice,
|
|
freePrice = :mishwarVipPrice,
|
|
naturePrice = :normalMin,
|
|
heavyPrice = :peakMin,
|
|
latePrice = :lateMin
|
|
WHERE country = :countryName";
|
|
$upStmt = $con->prepare($updateSql);
|
|
$upStmt->execute([
|
|
':speedPrice' => $newSpeedPrice,
|
|
':comfortPrice' => $newComfortPrice,
|
|
':ladyPrice' => $newLadyPrice,
|
|
':deliveryPrice' => $newDeliveryPrice,
|
|
':mishwarVipPrice' => $newMishwarVipPrice,
|
|
':normalMin' => $newNormalMin,
|
|
':peakMin' => $newPeakMin,
|
|
':lateMin' => $newLateMin,
|
|
':countryName' => $countryName
|
|
]);
|
|
|
|
echo " -> Updated $countryName (Total Price Math applied): Speed=$newSpeedPrice JOD/KM, NormalMin=$newNormalMin JOD/MIN\n";
|
|
}
|
|
}
|
|
} catch (Exception $e) {
|
|
echo " Error in Pricing Module: " . $e->getMessage() . "\n";
|
|
}
|
|
|
|
// ==========================================
|
|
// 2. وحدة توجيه السائقين (Demand Predictor)
|
|
// ==========================================
|
|
echo "2. Running Demand Predictor Module...\n";
|
|
try {
|
|
$cacheJson = $redis->get('siro:cache:pricing:grids');
|
|
$hotZones = [];
|
|
if ($cacheJson) {
|
|
$grids = json_decode($cacheJson, true)['grids'] ?? [];
|
|
foreach ($grids as $key => $data) {
|
|
if (strpos($key, 'FALLBACK') !== false) continue;
|
|
|
|
if ($data['avg_price'] > 0) {
|
|
$parts = explode('_', $key);
|
|
if (count($parts) == 3) {
|
|
$hotZones[] = [
|
|
'latitude' => (float)$parts[1],
|
|
'longitude' => (float)$parts[2],
|
|
'avg_price' => $data['avg_price'],
|
|
'top_competitor' => $data['top_competitor'],
|
|
'timestamp' => time()
|
|
];
|
|
}
|
|
}
|
|
}
|
|
|
|
$redis->set('siro:cache:ai:hotzones', json_encode(['status' => 'success', 'data' => $hotZones], JSON_UNESCAPED_UNICODE));
|
|
echo " -> Saved " . count($hotZones) . " Hot Zones to Redis for Driver Map Guidance.\n";
|
|
}
|
|
} catch (Exception $e) {
|
|
echo " Error in Demand Predictor: " . $e->getMessage() . "\n";
|
|
}
|
|
|
|
// ==========================================
|
|
// 3. وحدة استهداف الركاب الخاملين (Smart Retention)
|
|
// ==========================================
|
|
echo "3. Running Smart Retention Module...\n";
|
|
try {
|
|
$sql = "SELECT source, COUNT(*) as opens
|
|
FROM passenger_opening_locations
|
|
WHERE created_at >= DATE_SUB(NOW(), INTERVAL 3 HOUR)
|
|
GROUP BY source
|
|
HAVING opens >= 3";
|
|
|
|
$stmt = $con->query($sql);
|
|
$idleRiders = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
$notifiedCount = count($idleRiders);
|
|
echo " -> Identified $notifiedCount idle riders requiring push notifications.\n";
|
|
} catch (Exception $e) {
|
|
echo " Error in Smart Retention: " . $e->getMessage() . "\n";
|
|
}
|
|
|
|
echo "AI Engine finished successfully.\n";
|
|
?>
|