Update: 2026-07-09 01:55:25
This commit is contained in:
@@ -10,6 +10,7 @@
|
||||
|
||||
require_once __DIR__ . '/../core/bootstrap.php';
|
||||
require_once __DIR__ . '/../functions.php';
|
||||
require_once __DIR__ . '/../ride/pricing/pricing_helper.php';
|
||||
|
||||
try {
|
||||
$con = Database::get('main');
|
||||
@@ -20,6 +21,46 @@ try {
|
||||
|
||||
echo "Starting Siro AI Engine v2 (Powered by Pricing Engine)...\n";
|
||||
|
||||
// ==========================================
|
||||
// 0. جدول افتراضات عمولة المنافسين (لميزة "أرباحك أعلى" للسائق)
|
||||
// ==========================================
|
||||
$con->exec("
|
||||
CREATE TABLE IF NOT EXISTS `competitor_commission_assumptions` (
|
||||
`country_code` VARCHAR(5) NOT NULL,
|
||||
`competitor_name` VARCHAR(64) NOT NULL,
|
||||
`commission_pct` DECIMAL(5,2) NOT NULL,
|
||||
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`country_code`, `competitor_name`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
");
|
||||
|
||||
// زرع أولي بأرقام حقيقية زوّدنا فيها — INSERT IGNORE عشان ما نبهدل أي تعديل يدوي لاحق من الأدمن
|
||||
$commissionSeed = [
|
||||
// الأردن — كل التطبيقات المذكورة أعطتنا نفس النسبة 23%
|
||||
['JO', 'com.careem.ae', 23.0],
|
||||
['JO', 'com.ubercab', 23.0],
|
||||
['JO', 'com.jeeny.app', 23.0],
|
||||
['JO', 'com.taxif.passenger', 23.0],
|
||||
['JO', 'gogo', 23.0],
|
||||
['JO', 'petra_ride', 23.0],
|
||||
// سوريا
|
||||
['SY', 'yallago', 20.0],
|
||||
['SY', 'zaken', 17.0],
|
||||
['SY', 'tufaddal', 15.0],
|
||||
// مصر — استخدمنا الطرف الأدنى من كل مجال أعطانا إياه المستخدم (تحفظاً)
|
||||
['EG', 'uber', 25.0],
|
||||
['EG', 'careem', 20.0],
|
||||
['EG', 'didi', 15.0],
|
||||
['EG', 'indrive', 14.0],
|
||||
];
|
||||
$seedStmt = $con->prepare("
|
||||
INSERT IGNORE INTO competitor_commission_assumptions (country_code, competitor_name, commission_pct)
|
||||
VALUES (:cc, :name, :pct)
|
||||
");
|
||||
foreach ($commissionSeed as [$cc, $name, $pct]) {
|
||||
$seedStmt->execute([':cc' => $cc, ':name' => $name, ':pct' => $pct]);
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// 1. التسعير الديناميكي بناءً على المعادلات الإحصائية
|
||||
// ==========================================
|
||||
@@ -156,6 +197,33 @@ try {
|
||||
]);
|
||||
|
||||
echo " ✅ Updated $countryName pricing in kazan table.\n";
|
||||
|
||||
// ── تخزين معاملات المنافس الحالي بـ Redis لميزة "أرباحك أعلى" للسائق ──
|
||||
// get.php / add_ride.php يقرأوا هذا المفتاح فقط (بدون أي استعلام DB
|
||||
// وقت إنشاء الرحلة) عبر estimateDriverEarningsAdvantage()
|
||||
try {
|
||||
$commStmt = $con->prepare("
|
||||
SELECT commission_pct FROM competitor_commission_assumptions
|
||||
WHERE country_code = :cc AND competitor_name = :name LIMIT 1
|
||||
");
|
||||
$commStmt->execute([':cc' => $country, ':name' => $bestFormula['competitor_name']]);
|
||||
$commissionPct = (float)($commStmt->fetchColumn() ?: 20.0);
|
||||
|
||||
$driverComparisonData = [
|
||||
'competitor_name' => $bestFormula['competitor_name'],
|
||||
'base_fare' => $bestBaseFare,
|
||||
'km_rate' => $bestKmRate,
|
||||
'min_rate' => $bestMinRate,
|
||||
'commission_pct' => $commissionPct,
|
||||
'currency' => getCurrencyByCountry($countryName),
|
||||
'updated_at' => date('Y-m-d H:i:s'),
|
||||
];
|
||||
// TTL أطول من فترة الجدولة (3 ساعات) بهامش أمان
|
||||
$redis->setex("pricing:driver_comparison:{$country}", 14400, json_encode($driverComparisonData));
|
||||
echo " 💰 Cached driver-comparison data for $countryName (commission assumed: {$commissionPct}%)\n";
|
||||
} catch (Exception $e) {
|
||||
echo " ⚠️ Failed to cache driver-comparison data: " . $e->getMessage() . "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
|
||||
@@ -349,7 +349,7 @@ function notifyPassengerOnRideServer($passenger_id, $payload) {
|
||||
// ============================================================================
|
||||
// دالة توزيع الطلب (Dispatch Function) - النسخة المصححة
|
||||
// ============================================================================
|
||||
function dispatchRideToDrivers($driversData, $rideId, $payloadTemplate, $startNameLoc, $encryptionHelper) {
|
||||
function dispatchRideToDrivers($driversData, $rideId, $payloadTemplate, $startNameLoc, $encryptionHelper, $extraFcmData = []) {
|
||||
$countDrivers = count($driversData);
|
||||
error_log("🚀 [DISPATCH_START] RideID: $rideId | Drivers Count: $countDrivers");
|
||||
|
||||
@@ -377,11 +377,11 @@ function dispatchRideToDrivers($driversData, $rideId, $payloadTemplate, $startNa
|
||||
|
||||
// 3. إرسال FCM
|
||||
if (!empty($driverToken)) {
|
||||
$fcmData = [
|
||||
$fcmData = array_merge([
|
||||
'DriverList' => $payloadForDriver,
|
||||
'order_id' => (string)$rideId
|
||||
];
|
||||
|
||||
], $extraFcmData);
|
||||
|
||||
// 🆕 Destination Match Indicator
|
||||
$title = "طلب جديد 🔔";
|
||||
if (!empty($driver['is_destination_match'])) {
|
||||
|
||||
@@ -116,4 +116,54 @@ function getCurrencyByCountry(string $countryCode): string {
|
||||
|
||||
return $currencies[$countryCode] ?? 'SYP'; // افتراضي: ليرة سورية
|
||||
}
|
||||
|
||||
/**
|
||||
* يقدّر مقدار زيادة أجرة السائق عندنا مقارنة بأقرب منافس لنفس الرحلة.
|
||||
* يعتمد على معاملات المنافس المخزّنة بـ Redis (يحدّثها cron_ai_engine.php
|
||||
* كل 3 ساعات على مفتاح pricing:driver_comparison:{country_code}) — لا يوجد
|
||||
* أي استعلام مباشر لقاعدة البيانات هنا لتفادي أي تأخير وقت إنشاء الرحلة.
|
||||
*
|
||||
* @return array|null ['extra' => float, 'currency' => string, 'competitor_name' => string]
|
||||
* أو null إذا ما في بيانات كافية أو الفرق مش مجدي يُعرض
|
||||
*/
|
||||
function estimateDriverEarningsAdvantage(
|
||||
string $countryCode,
|
||||
float $distanceKm,
|
||||
float $durationMin,
|
||||
float $ourDriverPrice,
|
||||
$redis
|
||||
): ?array {
|
||||
if (!$redis || $ourDriverPrice <= 0) return null;
|
||||
|
||||
try {
|
||||
$json = $redis->get("pricing:driver_comparison:{$countryCode}");
|
||||
} catch (Exception $e) {
|
||||
return null;
|
||||
}
|
||||
if (!$json) return null;
|
||||
|
||||
$data = json_decode($json, true);
|
||||
if (!is_array($data) || empty($data['competitor_name'])) return null;
|
||||
|
||||
$competitorFare = (float)($data['base_fare'] ?? 0)
|
||||
+ (float)($data['km_rate'] ?? 0) * $distanceKm
|
||||
+ (float)($data['min_rate'] ?? 0) * $durationMin;
|
||||
|
||||
if ($competitorFare <= 0) return null;
|
||||
|
||||
$commissionPct = (float)($data['commission_pct'] ?? 20.0);
|
||||
$competitorDriverPayout = $competitorFare * (1 - $commissionPct / 100);
|
||||
|
||||
$extra = round($ourDriverPrice - $competitorDriverPayout, 2);
|
||||
|
||||
// حد أدنى نسبي (3% من أجرة سائق المنافس) حتى ما نعرض فرق تافه أو غير موثوق
|
||||
$minMeaningful = $competitorDriverPayout * 0.03;
|
||||
if ($extra <= $minMeaningful) return null;
|
||||
|
||||
return [
|
||||
'extra' => $extra,
|
||||
'currency' => $data['currency'] ?? 'SYP',
|
||||
'competitor_name' => $data['competitor_name'],
|
||||
];
|
||||
}
|
||||
?>
|
||||
|
||||
@@ -16,23 +16,23 @@ try {
|
||||
// =================================================================================
|
||||
// 🛠️ دالة مساعدة: إرسال الرحلة لسوق السائقين (Marketplace Broadcast)
|
||||
// =================================================================================
|
||||
function broadcastRideToMarket($rideId, $lat, $lng, $payloadData) {
|
||||
$url = getenv('LOCATION_SOCKET_URL');
|
||||
function broadcastRideToMarket($rideId, $lat, $lng, $payloadData, $extraMarketData = []) {
|
||||
$url = getenv('LOCATION_SOCKET_URL');
|
||||
$INTERNAL_KEY = function_exists('getInternalSocketKey') ? getInternalSocketKey() : '';
|
||||
$marketPayload = [
|
||||
$marketPayload = array_merge([
|
||||
'id' => (string)$rideId,
|
||||
'start_lat' => $lat,
|
||||
'start_lng' => $lng,
|
||||
'end_lat' => $payloadData[3],
|
||||
'end_lng' => $payloadData[4],
|
||||
'price' => $payloadData[2],
|
||||
'carType' => $payloadData[31],
|
||||
'startName' => $payloadData[29],
|
||||
'endName' => $payloadData[30],
|
||||
'distance' => $payloadData[11],
|
||||
'duration' => $payloadData[15],
|
||||
'passengerRate' => $payloadData[33],
|
||||
];
|
||||
'price' => $payloadData[2],
|
||||
'carType' => $payloadData[31],
|
||||
'startName' => $payloadData[29],
|
||||
'endName' => $payloadData[30],
|
||||
'distance' => $payloadData[11],
|
||||
'duration' => $payloadData[15],
|
||||
'passengerRate' => $payloadData[33],
|
||||
], $extraMarketData);
|
||||
|
||||
$postData = [
|
||||
'action' => 'market_new_ride',
|
||||
@@ -254,6 +254,36 @@ try {
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
$kazan = (float) $price - (float) $price_for_driver;
|
||||
$passengerFp = isset($_SERVER['HTTP_X_DEVICE_FP']) ? $_SERVER['HTTP_X_DEVICE_FP'] : '';
|
||||
|
||||
// ── 🆕 "أرباحك أعلى" — مقارنة أجرة السائق عندنا مع أقرب منافس ─────
|
||||
require_once __DIR__ . '/../pricing/pricing_helper.php';
|
||||
$extraDispatchData = [];
|
||||
try {
|
||||
$rideCountryCode = 'JO';
|
||||
$stmtCountry = $con->prepare("
|
||||
SELECT country_code FROM passenger_opening_locations
|
||||
WHERE passenger_id = :pid
|
||||
ORDER BY created_at DESC LIMIT 1
|
||||
");
|
||||
$stmtCountry->execute([':pid' => $passenger_id]);
|
||||
$foundCountry = $stmtCountry->fetchColumn();
|
||||
if ($foundCountry) $rideCountryCode = strtoupper($foundCountry);
|
||||
|
||||
$durationMinForEarnings = is_numeric($duration_text) ? (float)$duration_text : 0.0;
|
||||
$earnings = estimateDriverEarningsAdvantage(
|
||||
$rideCountryCode,
|
||||
(float)$distance,
|
||||
$durationMinForEarnings,
|
||||
(float)$price_for_driver,
|
||||
$redis ?? null
|
||||
);
|
||||
if ($earnings) {
|
||||
$extraDispatchData['driver_earnings_extra'] = (string)$earnings['extra'];
|
||||
$extraDispatchData['driver_earnings_currency'] = $earnings['currency'];
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
error_log("[add_ride] Driver earnings estimate failed: " . $e->getMessage());
|
||||
}
|
||||
$payload = [
|
||||
(string) $startLat,
|
||||
(string) $startLng,
|
||||
@@ -290,6 +320,10 @@ try {
|
||||
number_format($kazan, 2, '.', ''),
|
||||
(string) $passenger_rating,
|
||||
(string) $is_prime, // 👑 Index 34: Prime Status
|
||||
// 🆕 Index 35/36: "أرباحك أعلى" — تُقرأ من نافذة الـ Overlay (order_over_lay.dart)
|
||||
// اللي بتوصلها البيانات كـ List مش كـ Map مسمّى مثل FCM
|
||||
isset($extraDispatchData['driver_earnings_extra']) ? $extraDispatchData['driver_earnings_extra'] : '',
|
||||
isset($extraDispatchData['driver_earnings_currency']) ? $extraDispatchData['driver_earnings_currency'] : '',
|
||||
];
|
||||
|
||||
// Direct dispatch للسائقين القريبين
|
||||
@@ -369,14 +403,14 @@ try {
|
||||
}
|
||||
|
||||
if (!empty($driversData)) {
|
||||
dispatchRideToDrivers($driversData, $insertedId, $payload, $start_name_loc, $encryptionHelper);
|
||||
dispatchRideToDrivers($driversData, $insertedId, $payload, $start_name_loc, $encryptionHelper, $extraDispatchData);
|
||||
error_log("[add_ride] Dispatched RideID=$insertedId to " . count($driversData) . " drivers.");
|
||||
} else {
|
||||
error_log("[add_ride] No direct drivers found for RideID=$insertedId — market only.");
|
||||
}
|
||||
|
||||
// Broadcast للـ marketplace دائماً
|
||||
broadcastRideToMarket($insertedId, $startLat, $startLng, $payload);
|
||||
broadcastRideToMarket($insertedId, $startLat, $startLng, $payload, $extraDispatchData);
|
||||
|
||||
// رد النجاح للتطبيق
|
||||
printSuccess($insertedId);
|
||||
|
||||
Reference in New Issue
Block a user