feat: integrate seasonal surge multipliers and grid-specific commission discounts into pricing logic
This commit is contained in:
@@ -51,7 +51,7 @@ try {
|
|||||||
// 1. Economy tier (الأكثر تنافسية)
|
// 1. Economy tier (الأكثر تنافسية)
|
||||||
// 2. Standard tier (إذا ما في Economy)
|
// 2. Standard tier (إذا ما في Economy)
|
||||||
// 3. أي tier ثاني بأعلى R²
|
// 3. أي tier ثاني بأعلى R²
|
||||||
$tierPriority = ['standard', 'economy', 'premium'];
|
$tierPriority = ['economy', 'standard', 'premium'];
|
||||||
$bestFormula = null;
|
$bestFormula = null;
|
||||||
$bestTierIdx = 999;
|
$bestTierIdx = 999;
|
||||||
|
|
||||||
@@ -192,17 +192,19 @@ try {
|
|||||||
$avgMult = round($data['avg_multiplier'] / $data['count'], 3);
|
$avgMult = round($data['avg_multiplier'] / $data['count'], 3);
|
||||||
$peakHoursStr = implode(', ', array_unique($data['peak_hours']));
|
$peakHoursStr = implode(', ', array_unique($data['peak_hours']));
|
||||||
|
|
||||||
// تخزين معلومات الـ Surge في Redis — مفتاحين:
|
// تخزين ملخص إحصائي (object) على مفتاح خاص ومستقل — لا نكتب على
|
||||||
// 1. surge:opportunities (متوافق مع get.php الحالي)
|
// surge:opportunities / surge:opportunities:{country} لأنها خرائط
|
||||||
// 2. surge:opportunities:{country} (متوافق مع cron_kazan_adjuster.php)
|
// grid_id → multiplier يملأها ويقرأها نظام آخر بالكامل
|
||||||
|
// (cron_surge_opportunity.php / get.php / get_surge_heatmap.php /
|
||||||
|
// winback_hotspot_targets.php / cron_kazan_adjuster.php). كتابة
|
||||||
|
// object مسطّح على نفس المفتاح كانت تبهدل تلك الخريطة كل 3 ساعات.
|
||||||
$surgeData = [
|
$surgeData = [
|
||||||
'avg_competitor_surge' => $avgMult,
|
'avg_competitor_surge' => $avgMult,
|
||||||
'suggested_multiplier' => round(1.0 + ($avgMult - 1.0) * 0.6, 3),
|
'suggested_multiplier' => round(1.0 + ($avgMult - 1.0) * 0.6, 3),
|
||||||
'peak_hours' => $data['peak_hours'],
|
'peak_hours' => $data['peak_hours'],
|
||||||
'updated_at' => date('Y-m-d H:i:s')
|
'updated_at' => date('Y-m-d H:i:s')
|
||||||
];
|
];
|
||||||
$redis->setex("surge:opportunities", 7200, json_encode($surgeData));
|
$redis->setex("pricing_engine:competitor_surge_summary:{$country}", 7200, json_encode($surgeData));
|
||||||
$redis->setex("surge:opportunities:{$country}", 7200, json_encode($surgeData));
|
|
||||||
|
|
||||||
echo " ⚡ $country: Avg competitor surge = {$avgMult}x, peak hours: {$peakHoursStr}\n";
|
echo " ⚡ $country: Avg competitor surge = {$avgMult}x, peak hours: {$peakHoursStr}\n";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,6 +89,7 @@ try {
|
|||||||
// 3. تحديد فرص الذروة
|
// 3. تحديد فرص الذروة
|
||||||
$opportunities = [];
|
$opportunities = [];
|
||||||
$gridSurgeZones = [];
|
$gridSurgeZones = [];
|
||||||
|
$gridSurgeZonesByCountry = [];
|
||||||
|
|
||||||
foreach ($zones as $key => &$zone) {
|
foreach ($zones as $key => &$zone) {
|
||||||
$zone['opportunity'] = (
|
$zone['opportunity'] = (
|
||||||
@@ -118,6 +119,15 @@ try {
|
|||||||
|
|
||||||
// حفظ المنطقة مع المضاعف المقترح في Redis (للقراءة من get.php بعدين)
|
// حفظ المنطقة مع المضاعف المقترح في Redis (للقراءة من get.php بعدين)
|
||||||
$gridSurgeZones[$key] = $suggestedMultiplier;
|
$gridSurgeZones[$key] = $suggestedMultiplier;
|
||||||
|
|
||||||
|
// نفس البيانات مجمّعة حسب الدولة — يقرأها get_surge_heatmap.php،
|
||||||
|
// winback_hotspot_targets.php، و cron_kazan_adjuster.php كخريطة
|
||||||
|
// grid_id → multiplier لكل دولة على حدة
|
||||||
|
$countryCode = $zone['country_code'];
|
||||||
|
if (!isset($gridSurgeZonesByCountry[$countryCode])) {
|
||||||
|
$gridSurgeZonesByCountry[$countryCode] = [];
|
||||||
|
}
|
||||||
|
$gridSurgeZonesByCountry[$countryCode][$key] = $suggestedMultiplier;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
unset($zone);
|
unset($zone);
|
||||||
@@ -137,6 +147,21 @@ try {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 5. تخزين/مسح النسخة المقسّمة حسب الدولة — نفس مفتاح surge:opportunities:{CC}
|
||||||
|
// اللي يقرأه get_surge_heatmap.php و winback_hotspot_targets.php و cron_kazan_adjuster.php
|
||||||
|
if (isset($redis) && $redis !== null) {
|
||||||
|
$knownCountries = ['SY', 'JO', 'EG', 'IQ'];
|
||||||
|
foreach ($knownCountries as $cc) {
|
||||||
|
$countryKey = "surge:opportunities:{$cc}";
|
||||||
|
if (!empty($gridSurgeZonesByCountry[$cc])) {
|
||||||
|
$redis->setex($countryKey, 600, json_encode($gridSurgeZonesByCountry[$cc]));
|
||||||
|
echo "[".date('Y-m-d H:i:s')."] [$cc] Stored ".count($gridSurgeZonesByCountry[$cc])." surge zones.\n";
|
||||||
|
} else {
|
||||||
|
$redis->del($countryKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
echo "[".date('Y-m-d H:i:s')."] cron_surge_opportunity completed successfully.\n";
|
echo "[".date('Y-m-d H:i:s')."] cron_surge_opportunity completed successfully.\n";
|
||||||
|
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
|
|||||||
@@ -118,6 +118,12 @@ function calculateDynamicPrice($country, $minFare, $distance, $duration, $kazanR
|
|||||||
global $redis, $redisLocation, $con;
|
global $redis, $redisLocation, $con;
|
||||||
|
|
||||||
$surgeMultiplier = 1.0;
|
$surgeMultiplier = 1.0;
|
||||||
|
$kazanDiscountFactor = 1.0;
|
||||||
|
|
||||||
|
// كود الدولة المستخدم بمفاتيح Redis (surge:seasonal:{CC}, surge:kazan_discounts:{CC})
|
||||||
|
$countryCodeMap = ['Jordan' => 'JO', 'Syria' => 'SY', 'Egypt' => 'EG', 'Iraq' => 'IQ'];
|
||||||
|
$countryCode = $countryCodeMap[$kazanRow['country'] ?? ''] ?? strtoupper(substr($kazanRow['country'] ?? '', 0, 2));
|
||||||
|
|
||||||
if (isset($redis) && $redis !== null) {
|
if (isset($redis) && $redis !== null) {
|
||||||
try {
|
try {
|
||||||
$refLat = 33.5;
|
$refLat = 33.5;
|
||||||
@@ -160,13 +166,35 @@ function calculateDynamicPrice($country, $minFare, $distance, $duration, $kazanR
|
|||||||
if ($competitorSurgeMultiplier > $surgeMultiplier) {
|
if ($competitorSurgeMultiplier > $surgeMultiplier) {
|
||||||
$surgeMultiplier = $competitorSurgeMultiplier;
|
$surgeMultiplier = $competitorSurgeMultiplier;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// التسعير الموسمي (رمضان/عيد/طقس سيء) — cron_seasonal_pricing.php
|
||||||
|
// نطبّق الأعلى بين مضاعف الذروة الحالي والمضاعف الموسمي، بنفس منطق
|
||||||
|
// "خذ الأعلى" المستخدم أصلاً بين الطلب الداخلي وذروة المنافسين
|
||||||
|
$seasonalJson = $redis->get("surge:seasonal:{$countryCode}");
|
||||||
|
if ($seasonalJson) {
|
||||||
|
$seasonalData = json_decode($seasonalJson, true);
|
||||||
|
$seasonalMultiplier = (float)($seasonalData['multiplier'] ?? 1.0);
|
||||||
|
if ($seasonalMultiplier > $surgeMultiplier) {
|
||||||
|
$surgeMultiplier = $seasonalMultiplier;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// خصم العمولة بمناطق ذروة المنافسين — cron_kazan_adjuster.php
|
||||||
|
// خريطة grid_id → معامل خصم (0.70 أو 0.85) لنفس الـ grid_id المحسوب فوق
|
||||||
|
$kazanDiscountsJson = $redis->get("surge:kazan_discounts:{$countryCode}");
|
||||||
|
if ($kazanDiscountsJson) {
|
||||||
|
$kazanDiscounts = json_decode($kazanDiscountsJson, true);
|
||||||
|
if (is_array($kazanDiscounts) && isset($kazanDiscounts[$grid_id])) {
|
||||||
|
$kazanDiscountFactor = (float)$kazanDiscounts[$grid_id];
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (Exception $e) {}
|
} catch (Exception $e) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
$naturePrice = (float) ($kazanRow['normalMinPrice'] ?? 0);
|
$naturePrice = (float) ($kazanRow['normalMinPrice'] ?? 0);
|
||||||
$heavyPrice = (float) ($kazanRow['peakMinPrice'] ?? 0);
|
$heavyPrice = (float) ($kazanRow['peakMinPrice'] ?? 0);
|
||||||
$latePrice = (float) ($kazanRow['lateMinPrice'] ?? 0);
|
$latePrice = (float) ($kazanRow['lateMinPrice'] ?? 0);
|
||||||
$kazanPercent = (float) ($kazanRow['kazanPercent'] ?? 10);
|
$kazanPercent = (float) ($kazanRow['kazanPercent'] ?? 10) * $kazanDiscountFactor;
|
||||||
$startPrice = (float) ($kazanRow['startPrice'] ?? 0);
|
$startPrice = (float) ($kazanRow['startPrice'] ?? 0);
|
||||||
|
|
||||||
// === General Settings ===
|
// === General Settings ===
|
||||||
|
|||||||
Reference in New Issue
Block a user