Update: 2026-06-22 00:31:28
This commit is contained in:
122
backend/ride/pricing/auto_adapt.php
Normal file
122
backend/ride/pricing/auto_adapt.php
Normal file
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
/**
|
||||
* auto_adapt.php
|
||||
* Auto-Adaptive Pricing — تعديل أسعار kazan الأساسية تلقائياً
|
||||
*
|
||||
* المعادلة: سعر_الكيلو_الجديد = أقل_متوسط_سعر_منافس × 0.92
|
||||
* الحماية: ما ينزل عن 85% من السعر الحالي ولا يزيد عن 115%
|
||||
*
|
||||
* التشغيل: cron job كل 30-60 دقيقة
|
||||
* CLI: php backend/ride/pricing/auto_adapt.php
|
||||
* HTTP: curl -X POST https://.../backend/ride/pricing/auto_adapt.php -H "X-Internal-Key: ..."
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../../core/bootstrap.php';
|
||||
|
||||
try {
|
||||
$con = Database::get('main');
|
||||
} catch (Exception $e) {
|
||||
error_log("[AutoAdapt] DB connection failed: " . $e->getMessage());
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$countries = [
|
||||
'Syria' => 'SY',
|
||||
'Jordan' => 'JO',
|
||||
'Egypt' => 'EG',
|
||||
'Iraq' => 'IQ'
|
||||
];
|
||||
|
||||
$undercutFactor = 0.92;
|
||||
$minFloorRatio = 0.85;
|
||||
$maxCeilRatio = 1.15;
|
||||
|
||||
$kmColumns = [
|
||||
'speedPrice', 'comfortPrice', 'ladyPrice', 'electricPrice',
|
||||
'vanPrice', 'deliveryPrice', 'mishwarVipPrice', 'fixedPrice', 'awfarPrice'
|
||||
];
|
||||
|
||||
$logEntries = [];
|
||||
|
||||
foreach ($countries as $country => $cc) {
|
||||
// 1. متوسط سعر الكيلو لكل منافس (آخر 24 ساعة)
|
||||
$sql = "SELECT competitor_name, AVG(price_per_km) AS avg_ppm
|
||||
FROM competitor_prices
|
||||
WHERE country_code = :cc
|
||||
AND created_at >= DATE_SUB(NOW(), INTERVAL 24 HOUR)
|
||||
AND price_per_km > 0
|
||||
GROUP BY competitor_name
|
||||
ORDER BY avg_ppm ASC";
|
||||
$stmt = $con->prepare($sql);
|
||||
$stmt->execute([':cc' => $cc]);
|
||||
$competitorAvgs = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
if (empty($competitorAvgs)) {
|
||||
error_log("[AutoAdapt] $country — لا توجد بيانات منافسين");
|
||||
continue;
|
||||
}
|
||||
|
||||
$lowestAvg = (float)$competitorAvgs[0]['avg_ppm'];
|
||||
|
||||
// 2. الأسعار الحالية من kazan
|
||||
$kazanSql = "SELECT * FROM kazan WHERE country = :country LIMIT 1";
|
||||
$stmtK = $con->prepare($kazanSql);
|
||||
$stmtK->execute([':country' => $country]);
|
||||
$kazanRow = $stmtK->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$kazanRow) {
|
||||
error_log("[AutoAdapt] $country — لا يوجد صف في kazan");
|
||||
continue;
|
||||
}
|
||||
|
||||
$currentSpeed = (float)$kazanRow['speedPrice'];
|
||||
$targetSpeed = $lowestAvg * $undercutFactor;
|
||||
|
||||
$minAllowed = $currentSpeed * $minFloorRatio;
|
||||
$maxAllowed = $currentSpeed * $maxCeilRatio;
|
||||
|
||||
if ($targetSpeed < $minAllowed) {
|
||||
$targetSpeed = $minAllowed;
|
||||
} elseif ($targetSpeed > $maxAllowed) {
|
||||
$targetSpeed = $maxAllowed;
|
||||
}
|
||||
|
||||
$ratio = $targetSpeed / $currentSpeed;
|
||||
|
||||
$updates = [];
|
||||
$params = [':country' => $country];
|
||||
|
||||
foreach ($kmColumns as $col) {
|
||||
$oldVal = (float)($kazanRow[$col] ?? 0);
|
||||
if ($oldVal > 0) {
|
||||
$newVal = round($oldVal * $ratio, 2);
|
||||
$updates[] = "`$col` = :$col";
|
||||
$params[":$col"] = (string)$newVal;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($updates)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$updateSql = "UPDATE kazan SET " . implode(', ', $updates) . " WHERE country = :country";
|
||||
$stmtU = $con->prepare($updateSql);
|
||||
$stmtU->execute($params);
|
||||
|
||||
$logEntries[] = [
|
||||
'country' => $country,
|
||||
'code' => $cc,
|
||||
'old_speed' => $currentSpeed,
|
||||
'new_speed' => $targetSpeed,
|
||||
'lowest_competitor' => $lowestAvg,
|
||||
'ratio' => round($ratio, 4),
|
||||
];
|
||||
|
||||
error_log("[AutoAdapt] ✅ $country ($cc): speedPrice $currentSpeed → $targetSpeed (ratio: $ratio)");
|
||||
}
|
||||
|
||||
if (php_sapi_name() === 'cli') {
|
||||
echo json_encode(['status' => 'success', 'updates' => $logEntries], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
||||
} else {
|
||||
echo json_encode(['status' => 'success', 'updates' => $logEntries]);
|
||||
}
|
||||
@@ -124,6 +124,16 @@ function calculateDynamicPrice($country, $minFare, $distance, $duration, $kazanR
|
||||
$demandCount = (int)$redis->get("demand:grid:" . $grid_id);
|
||||
$availableDrivers = 0;
|
||||
|
||||
// Check competitor surge opportunities
|
||||
$competitorSurgeMultiplier = 1.0;
|
||||
$surgeOpsJson = $redis->get("surge:opportunities");
|
||||
if ($surgeOpsJson) {
|
||||
$surgeOps = json_decode($surgeOpsJson, true);
|
||||
if (is_array($surgeOps) && isset($surgeOps[$grid_id])) {
|
||||
$competitorSurgeMultiplier = (float)$surgeOps[$grid_id];
|
||||
}
|
||||
}
|
||||
|
||||
// Driver locations are handled by Location Redis (no prefix)
|
||||
try {
|
||||
if (isset($redisLocation) && $redisLocation !== null) {
|
||||
@@ -139,6 +149,11 @@ function calculateDynamicPrice($country, $minFare, $distance, $duration, $kazanR
|
||||
$surgeMultiplier = min(3.0, $surgeMultiplier); // Cap at 3.0
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-Adaptive Pricing: Apply competitor surge if it's higher
|
||||
if ($competitorSurgeMultiplier > $surgeMultiplier) {
|
||||
$surgeMultiplier = $competitorSurgeMultiplier;
|
||||
}
|
||||
} catch (Exception $e) {}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user