Update: 2026-07-02 05:27:04
This commit is contained in:
@@ -0,0 +1,172 @@
|
||||
<?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);
|
||||
|
||||
// أسعار الدقائق
|
||||
$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,
|
||||
ladyPrice = :ladyPrice,
|
||||
electricPrice = :electricPrice,
|
||||
vanPrice = :vanPrice,
|
||||
deliveryPrice = :deliveryPrice,
|
||||
mishwarVipPrice = :mishwarVipPrice,
|
||||
fixedPrice = :fixedPrice,
|
||||
awfarPrice = :awfarPrice,
|
||||
normalMinPrice = :normalMin,
|
||||
peakMinPrice = :peakMin,
|
||||
lateMinPrice = :lateMin
|
||||
WHERE country = :countryName";
|
||||
$upStmt = $con->prepare($updateSql);
|
||||
$upStmt->execute([
|
||||
':speedPrice' => $newSpeedPrice,
|
||||
':comfortPrice' => $newComfortPrice,
|
||||
':ladyPrice' => $newLadyPrice,
|
||||
':electricPrice' => $newElectricPrice,
|
||||
':vanPrice' => $newVanPrice,
|
||||
':deliveryPrice' => $newDeliveryPrice,
|
||||
':mishwarVipPrice' => $newMishwarVipPrice,
|
||||
':fixedPrice' => $newFixedPrice,
|
||||
':awfarPrice' => $newAwfarPrice,
|
||||
':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";
|
||||
?>
|
||||
Reference in New Issue
Block a user