chore: استيراد أولي من سيرو (ecfe7568) — بلا أي تعديل

نسخة كاملة من مستودع سيرو عند ecfe7568 لتكون أساس تطبيق «انطلق».
نُسخ المتعقَّب في git فقط (12,509 ملفاً / 302 م.ب) بـ git archive، لا
`cp -r` — فاستُثنيت تلقائياً مخلفات البناء (build · node_modules ·
.dart_tool · .gradle · Pods ≈ 10.7 غ.ب) وكل ما يستثنيه .gitignore.

هذا الكوميت **بلا أي تعديل عمداً** حتى يكون كل ما يليه فرقاً مقروءاً
مقابل سيرو الأصلي. سيرو نفسه لم يُمسّ.

⚠️ لا يبني بعد: `.env` و`lib/env/env.g.dart` غير متعقَّبين في سيرو (وهذا
صحيح — أسرار لكل مستأجر). كل تطبيق فلاتر هنا يحتاج .env خاصاً بانطلق ثم
توليد env.g.dart عبر build_runner. لا تُنسخ أسرار سيرو.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Hamza-Ayed
2026-07-27 05:10:29 +03:00
co-authored by Claude Opus 5
commit 92dc6b3641
3654 changed files with 896478 additions and 0 deletions
+336
View File
@@ -0,0 +1,336 @@
<?php
/**
* cron_ai_engine.php - AI Pricing Engine v2
*
* يقرأ معادلات المنافسين من Node.js Pricing Engine (competitor_secret_formulas)
* ويطبّق تسعيراً ذكياً بناءً على النتائج الإحصائية بدلاً من الحسابات البدائية.
*
* لم يعُد هذا الملف يحسب بنفسه - بل يعتمد على التحليل الإحصائي من pricing-engine.
*/
require_once __DIR__ . '/../core/bootstrap.php';
require_once __DIR__ . '/../functions.php';
require_once __DIR__ . '/../ride/pricing/pricing_helper.php';
try {
$con = Database::get('main');
$redis = getRedisConnection();
} catch (Exception $e) {
die("Connection failed: " . $e->getMessage() . "\n");
}
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. التسعير الديناميكي بناءً على المعادلات الإحصائية
// ==========================================
echo "1. Reading competitor formulas from Pricing Engine...\n";
try {
// قراءة آخر المعادلات من التحليل الإحصائي
$sql = "SELECT * FROM competitor_secret_formulas
WHERE last_updated >= DATE_SUB(NOW(), INTERVAL 24 HOUR)
ORDER BY last_updated DESC";
$stmt = $con->query($sql);
$formulas = $stmt->fetchAll(PDO::FETCH_ASSOC);
if (empty($formulas)) {
echo " ⚠️ No recent formulas found. Run pricing-engine first.\n";
} else {
// تجميع المعادلات حسب الدولة
$byCountry = [];
foreach ($formulas as $f) {
$country = $f['country_code'];
if (!isset($byCountry[$country])) $byCountry[$country] = [];
$byCountry[$country][] = $f;
}
foreach ($byCountry as $country => $countryFormulas) {
$countryNameMap = ['JO' => 'Jordan', 'SY' => 'Syria', 'EG' => 'Egypt', 'IQ' => 'Iraq'];
$countryName = $countryNameMap[$country] ?? $country;
// اختيار أفضل معادلة للتسعير حسب الأولوية:
// 1. Economy tier (الأكثر تنافسية)
// 2. Standard tier (إذا ما في Economy)
// 3. أي tier ثاني بأعلى R²
$tierPriority = ['economy', 'standard', 'premium'];
$bestFormula = null;
$bestTierIdx = 999;
foreach ($countryFormulas as $f) {
$tier = $f['tier'] ?? 'standard';
$tierIdx = array_search($tier, $tierPriority);
if ($tierIdx === false) $tierIdx = 999;
$currentRSq = (float)($f['r_squared'] ?? 0);
$currentKm = (float)($f['price_per_km'] ?? 0);
// أفضلية: Tier أعلى أولوية، ثم R² أعلى
if ($currentKm > 0 && (
$bestFormula === null ||
$tierIdx < $bestTierIdx ||
($tierIdx === $bestTierIdx && $currentRSq > (float)($bestFormula['r_squared'] ?? 0))
)) {
$bestTierIdx = $tierIdx;
$bestFormula = $f;
}
}
$bestTier = null;
if ($bestFormula) {
$bestKmRate = (float)$bestFormula['price_per_km'];
$bestMinRate = (float)$bestFormula['price_per_min'];
$bestBaseFare = (float)$bestFormula['base_fare'];
$bestMinFare = (float)$bestFormula['min_fare'];
$bestRSq = (float)($bestFormula['r_squared'] ?? 0);
$bestTier = $bestFormula['tier'] ?? 'standard';
}
if ($bestKmRate === null) {
echo " ⚠️ No valid formula for $countryName. Skipping.\n";
continue;
}
// تسعير Siro: أرخص بنسبة 6.5% من المنافس مع الحفاظ على هيكل التسعير
$discountFactor = 1 - 0.065;
$ourKmRate = round($bestKmRate * $discountFactor, 3);
$ourMinRate = round($bestMinRate * $discountFactor, 3);
$ourBase = round($bestBaseFare * $discountFactor, 3);
echo " 📊 $countryName: Using formula [tier=$bestTier] (R²=$bestRSq)\n";
echo " Competitor: KM=$bestKmRate, MIN=$bestMinRate, Base=$bestBaseFare, MinFare=$bestMinFare\n";
echo " Siro (6.5% less): KM=$ourKmRate, MIN=$ourMinRate, Base=$ourBase\n";
// تحديث جدول kazan
$speedPrice = $ourKmRate;
$comfortPrice = round($ourKmRate * 1.30, 3);
$ladyPrice = round($ourKmRate * 1.10, 3);
$electricPrice = round($ourKmRate * 1.20, 3);
$vanPrice = round($ourKmRate * 1.50, 3);
$deliveryPrice = round($ourKmRate * 0.90, 3);
$mishwarVipPrice = round($ourKmRate * 1.40, 3);
$fixedPrice = $speedPrice;
$awfarPrice = round($ourKmRate * 0.85, 3);
// أسعار الدقائق
if ($country === 'JO') {
$normalMin = $ourMinRate > 0 ? $ourMinRate : 0.05;
$peakMin = round($normalMin * 1.15, 3);
$lateMin = $normalMin;
} else {
$normalMin = $ourMinRate > 0 ? $ourMinRate : round($speedPrice / 4, 3);
$peakMin = round($normalMin * 1.15, 3);
$lateMin = round($normalMin * 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,
startPrice = :startPrice
WHERE country = :countryName";
$upStmt = $con->prepare($updateSql);
$upStmt->execute([
':speedPrice' => $speedPrice,
':comfortPrice' => $comfortPrice,
':ladyPrice' => $ladyPrice,
':electricPrice' => $electricPrice,
':vanPrice' => $vanPrice,
':deliveryPrice' => $deliveryPrice,
':mishwarVipPrice' => $mishwarVipPrice,
':fixedPrice' => $fixedPrice,
':awfarPrice' => $awfarPrice,
':normalMin' => $normalMin,
':peakMin' => $peakMin,
':lateMin' => $lateMin,
':startPrice' => $ourBase,
':countryName' => $countryName
]);
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) {
echo " ❌ Error in Pricing Module: " . $e->getMessage() . "\n";
}
// ==========================================
// 2. قراءة Surge Insights من الـ Pricing Engine
// ==========================================
echo "2. Reading surge insights from Pricing Engine...\n";
try {
$sql = "SELECT * FROM competitor_surge_insights
WHERE detected_at >= DATE_SUB(NOW(), INTERVAL 12 HOUR)
ORDER BY surge_multiplier DESC
LIMIT 20";
$stmt = $con->query($sql);
$surgeRecords = $stmt->fetchAll(PDO::FETCH_ASSOC);
if (!empty($surgeRecords)) {
$surgeByCountry = [];
foreach ($surgeRecords as $sr) {
$country = $sr['country_code'];
if (!isset($surgeByCountry[$country])) {
$surgeByCountry[$country] = [
'avg_multiplier' => 0,
'count' => 0,
'peak_hours' => []
];
}
$surgeByCountry[$country]['avg_multiplier'] += $sr['surge_multiplier'];
$surgeByCountry[$country]['count']++;
$surgeByCountry[$country]['peak_hours'][] = "{$sr['peak_start_hour']}:00-{$sr['peak_end_hour']}:00";
}
foreach ($surgeByCountry as $country => $data) {
$avgMult = round($data['avg_multiplier'] / $data['count'], 3);
$peakHoursStr = implode(', ', array_unique($data['peak_hours']));
// تخزين ملخص إحصائي (object) على مفتاح خاص ومستقل — لا نكتب على
// surge:opportunities / surge:opportunities:{country} لأنها خرائط
// grid_id → multiplier يملأها ويقرأها نظام آخر بالكامل
// (cron_surge_opportunity.php / get.php / get_surge_heatmap.php /
// winback_hotspot_targets.php / cron_kazan_adjuster.php). كتابة
// object مسطّح على نفس المفتاح كانت تبهدل تلك الخريطة كل 3 ساعات.
$surgeData = [
'avg_competitor_surge' => $avgMult,
'suggested_multiplier' => round(1.0 + ($avgMult - 1.0) * 0.6, 3),
'peak_hours' => $data['peak_hours'],
'updated_at' => date('Y-m-d H:i:s')
];
$redis->setex("pricing_engine:competitor_surge_summary:{$country}", 7200, json_encode($surgeData));
echo " ⚡ $country: Avg competitor surge = {$avgMult}x, peak hours: {$peakHoursStr}\n";
}
} else {
echo " ℹ️ No recent surge insights found.\n";
}
} catch (Exception $e) {
echo " ❌ Error in Surge Module: " . $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";
}
// ==========================================
// 4. Export AI Hotzones to Redis for Captains
// ==========================================
echo "4. Exporting AI Hotzones to Redis...\n";
try {
$sql = "SELECT latitude, longitude, competitor_name AS top_competitor, avg_ppk AS avg_price
FROM competitor_surge_zones
WHERE detected_at >= DATE_SUB(NOW(), INTERVAL 6 HOUR)
AND surge_multiplier > 1.1";
$stmt = $con->query($sql);
$zones = $stmt->fetchAll(PDO::FETCH_ASSOC);
if (!empty($zones)) {
$redisData = [
'status' => 'success',
'data' => $zones
];
$redis->setex('siro:cache:ai:hotzones', 3600, json_encode($redisData));
echo " 🗺️ Exported " . count($zones) . " hot zones to Redis (siro:cache:ai:hotzones).\n";
} else {
// Clear if no surge to avoid stale data
$redis->del('siro:cache:ai:hotzones');
echo " ℹ️ No active hot zones to export.\n";
}
} catch (Exception $e) {
echo " ❌ Error in Hotzones Export: " . $e->getMessage() . "\n";
}
echo "AI Engine v2 finished successfully.\n";