19 KiB
19 KiB
Siro Pricing Engine — Architecture & Deployment Guide
1. نظرة عامة على المنظومة
┌─────────────────────────────────────────────────────────────────────┐
│ Siro PRICING ECOSYSTEM │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────────┐ ┌──────────────────────────────┐ │
│ │ Android Bot Scraper │ │ Node.js Pricing Engine │ │
│ │ (Java/Kotlin) │─────▶│ (TypeScript) │ │
│ │ يرسخن أسعار │ │ تحليل إحصائي متقدم │ │
│ │ TaxiF, Careem, Uber │ │ ┌────────────────────────┐ │ │
│ │ Jeeny... │ │ │ MAD Outlier Detection │ │ │
│ └──────────┬───────────┘ │ │ K-Means Tier Clustering│ │ │
│ │ │ │ Ridge Regression (MLR) │ │ │
│ ▼ │ │ Min Fare Detection │ │ │
│ ┌──────────────────────┐ │ │ Surge Analysis │ │ │
│ │ MySQL: │ │ │ Zone Pricing │ │ │
│ │ scraped_competitor_ │◀─────│ └────────────────────────┘ │ │
│ │ prices │ └──────────────┬───────────────┘ │
│ └──────────────────────┘ │ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────────────┐ ┌──────────────────────────────┐ │
│ │ MySQL: │ │ MySQL: │ │
│ │ competitor_secret_ │ │ competitor_surge_insights │ │
│ │ formulas │ │ (ساعات الذروة + المضاعف) │ │
│ │ (معادلات المنافسين) │ └──────────────┬───────────────┘ │
│ └──────────┬───────────┘ │ │
│ │ │ │
│ ▼ ▼ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ PHP Cron Jobs (Backend) │ │
│ │ │ │
│ │ cron_ai_engine.php: يقرأ المعادلات ويحدث kazan │ │
│ │ cron_kazan_adjuster: يقرأ surge ويضبط العمولة │ │
│ │ cron_gemini_advisor: يرسل المعادلات لـ Gemini لتقارير │ │
│ └────────────────────────┬───────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ Redis Cache Layer │ │
│ │ │ │
│ │ surge:opportunities → مضاعف Surge المقترح │ │
│ │ surge:opportunities:{JO} → لكل دولة │ │
│ │ siro:cache:pricing:grids → أسعار حسب Grid 2.5km │ │
│ └────────────────────────┬───────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ Real-time APIs (Rider & Driver Apps) │ │
│ │ │ │
│ │ ride/pricing/get.php: حساب السعر الفوري للراكب │ │
│ │ ride/heatmap/: خريطة حرارية للسائق │ │
│ │ api/ride/competitor: مقارنة أسعار المنافسين │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
2. Deployment: Node.js إلى جانب PHP
المشكلة
النظام الحالي PHP على Apache/Nginx. نحتاج Node.js للتشغيل جنباً إلى جنب.
الحل: PM2 Process Manager
# 1. تثبيت Node.js على السيرفر (مرة واحدة)
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt-get install -y nodejs
# 2. رفع مجلد pricing-engine إلى السيرفر
# (scp أو git pull)
# 3. تثبيت PM2 (مدير عمليات Node.js)
npm install -g pm2
# 4. تثبيت dependencies
cd /var/www/siro/backend/pricing-engine
npm install
cp .env.example .env
# عدّل .env ببيانات MySQL + Redis
# 5. تشغيل الخدمات مع PM2
pm2 start ecosystem.config.js
pm2 save
pm2 startup # عشان يشتغل تلقائياً بعد reboot
ملف PM2 Ecosystem
// backend/pricing-engine/ecosystem.config.js
module.exports = {
apps: [
{
name: 'siro-pricing-hourly',
script: 'dist/index.js',
args: '--mode=surge --hours=3',
cron_restart: '0 * * * *', // كل ساعة
autorestart: false,
time: true,
},
{
name: 'siro-pricing-daily',
script: 'dist/index.js',
args: '--mode=full --hours=72',
cron_restart: '0 6 * * *', // كل يوم 6 صباحاً
autorestart: false,
time: true,
},
{
name: 'siro-pricing-weekly',
script: 'dist/index.js',
args: '--mode=report --hours=168',
cron_restart: '0 8 * * 1', // كل أسبوع الإثنين 8 صباحاً
autorestart: false,
time: true,
},
],
};
PM2 يتولى تشغيل cron jobs بدون الحاجة إلى
crontabنظامي. لكنهautorestart: falseلأنها عمليات لمرة واحدة، مو servers.
بديل: Crontab عادي (أبسط)
# crontab -e
0 * * * * cd /var/www/siro/backend/pricing-engine && node dist/index.js --mode=surge --hours=3 >> /var/log/siro-pricing.log 2>&1
0 6 * * * cd /var/www/siro/backend/pricing-engine && node dist/index.js --mode=full --hours=72 >> /var/log/siro-pricing.log 2>&1
0 8 * * 1 cd /var/www/siro/backend/pricing-engine && node dist/index.js --mode=report --hours=168 >> /var/log/siro-pricing.log 2>&1
3. دفق التسعير الكامل (Full Pricing Flow)
3.1 تحليل المنافسين ← حفظ المعادلات
Pricing Engine (Node.js)
│
├─ 1. يسحب بيانات من scraped_competitor_prices
├─ 2. ينظف الشواذ (MAD)
├─ 3. يصنّف الفئات (K-Means) → Economy / Standard / Premium
├─ 4. يحسب الانحدار لكل فئة:
│ price = baseFare + kmRate×dist + minRate×duration
├─ 5. يكتشف Minimum Fare
├─ 6. يحلل Surge حسب الساعة
└─ 7. يحفظ في:
├─ competitor_secret_formulas (معادلات لكل tier)
└─ competitor_surge_insights (ساعات الذروة)
3.2 PHP يقرأ ويطبّق التسعير
cron_ai_engine.php (PHP, كل 30-60 دقيقة)
│
├─ 1. يقرأ competitor_secret_formulas
├─ 2. يختار Economy tier (الأرخص)
├─ 3. يطبّق خصم 6.5%:
│ Siro_kmRate = competitor_kmRate × 0.935
├─ 4. يحدّث جدول kazan
└─ 5. يحفظ surge في Redis
3.3 حساب السعر للتطبيقات
ride/pricing/get.php (API, يتم استدعاؤه عند طلب رحلة)
│
├─ 1. يقرأ kazan table (آخر تحديث من cron_ai_engine)
├─ 2. يحسب:
│ basePrice = kazan.baseFare
│ + kazan.speedPrice × distance
│ + kazan.normalMinPrice × duration
├─ 3. يقرأ Redis surge:opportunities
├─ 4. يطبّق surge multiplier إذا كانت ساعة ذروة
└─ 5. يرجع السعر النهائي للتطبيق
4. تقسيم المناطق (Zone-Based Pricing)
4.1 تصنيف المناطق
Amman مقسمة حسب البعد عن المركز (31.95, 35.90):
Centre (مركز البلد) → نصف قطر < 2.5km → PPK 0.25-0.33
Mid (وسط) → نصف قطر < 5km → PPK 0.35-0.45
Suburb (ضواحي) → نصف قطر < 10km → PPK 0.40-0.50
Outskirts (أطراف) → > 10km → PPK 0.40-0.60
4.2 كيف نطبّق Zone-Based Pricing؟
بدلاً من معادلة تسعير واحدة لكل البلد، يصبح:
-- جدول zone_pricing (جديد)
CREATE TABLE IF NOT EXISTS `zone_pricing` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`country_code` VARCHAR(5) NOT NULL,
`zone_type` VARCHAR(20) NOT NULL, -- centre, mid, suburb, outskirts
`km_rate` DECIMAL(8,3) NOT NULL,
`min_rate` DECIMAL(8,3) NOT NULL DEFAULT 0,
`base_fare` DECIMAL(8,3) NOT NULL DEFAULT 0,
`min_fare` DECIMAL(8,3) NOT NULL DEFAULT 0,
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY `idx_country_zone` (`country_code`, `zone_type`)
);
-- يتم ملؤها من تحليل Pricing Engine
ثم في ride/pricing/get.php:
// 1. تحديد منطقة البداية
$zoneType = classifyZone($startLat, $startLng); // centre | mid | suburb | outskirts
// 2. استخدام تسعير المنطقة
$zoneRate = getZonePricing($country, $zoneType);
$price = $zoneRate['base_fare']
+ $zoneRate['km_rate'] * $distance
+ $zoneRate['min_rate'] * $duration;
// 3. تطبيق Surge حسب المنطقة
$surge = getSurgeForZone($country, $zoneType);
$finalPrice = $price * $surge;
4.3 إشعارات المناطق للسائقين
عند دخول سائق إلى منطقة ذات Surge عالي:
// cron_notify_drivers_zones.php (جديد - كل 5 دقائق)
$hotZones = getHotZones(); // من Redis surge:opportunities أو تحليل الـ Pricing Engine
foreach ($hotZones as $zone) {
// إرسال FCM notification للسائقين القريبين
sendPushToNearbyDrivers($zone['lat'], $zone['lng'], [
'title' => '⚠️ منطقة طلب مرتفع',
'body' => "منطقة {$zone['name']}: الطلب مرتفع، الأسعار مرتفعة {$zone['surge']}x"
]);
}
وللراكب عند فتح التطبيق في منطقة Surge:
// في ride/pricing/get.php
if ($surgeMultiplier > 1.0) {
$response['surge_warning'] = "⚠️ هذه المنطقة تشهد طلباً مرتفعاً، الأسعار أعلى بنسبة "
. round(($surgeMultiplier - 1) * 100) . "%";
}
5. دعم تطبيقات منافسة متعددة
5.1 لكل منافس معادلاته الخاصة
-- competitor_secret_formulas يدعم:
-- competitor_name = 'com.taxif.passenger' | 'com.careem.ae' | 'com.ubercab' | 'com.jeeny.app'
-- لكل منافس 3 tiers (Economy/Standard/Premium)
-- لكل tier معادلة مستقلة
5.2 مقارنة الأسعار في التطبيق
// api/ride/get_competitor_context.php
$competitors = ['com.taxif.passenger', 'com.careem.ae', 'com.ubercab', 'com.jeeny.app'];
$prices = [];
foreach ($competitors as $comp) {
$formula = getLatestFormula($comp, $country, 'economy');
$estimatedPrice = $formula['base_fare']
+ $formula['price_per_km'] * $requestedDistance
+ $formula['price_per_min'] * $requestedDuration;
$prices[$comp] = [
'name' => getCompetitorDisplayName($comp),
'price' => $estimatedPrice,
'currency' => 'JOD',
];
}
// Siro price (already 6.5% less)
$siroPrice = calculateSiroPrice($request);
$prices['siro'] = [
'name' => 'Siro',
'price' => $siroPrice,
'currency' => 'JOD',
'is_cheapest' => $siroPrice < min(array_column($prices, 'price')),
];
5.3 تسعير Siro بناءً على المنافس الأقوى
في cron_ai_engine.php:
// 1. اجلب معادلات جميع المنافسين للدولة
$competitors = getCompetitorFormulas($country, 'economy');
// 2. احسب السعر المتوقع لكل منافس لرحلة نموذجية (10km, 15min)
$sampleDist = 10;
$sampleDur = 15;
$competitorPrices = [];
foreach ($competitors as $comp) {
$competitorPrices[$comp['competitor_name']] =
$comp['base_fare'] + $comp['price_per_km'] * $sampleDist + $comp['price_per_min'] * $sampleDur;
}
// 3. المنافس الأرخص هو المستهدف
$cheapestCompetitor = array_keys($competitorPrices, min($competitorPrices))[0];
$cheapestPrice = min($competitorPrices);
// 4. سعر Siro = أرخص منافس - 6.5%
$targetSiroPrice = $cheapestPrice * 0.935;
// 5. هندسة عكسية لمعاملات Siro
$ourKmRate = $competitors[$cheapestCompetitor]['price_per_km'] * 0.935;
$ourMinRate = $competitors[$cheapestCompetitor]['price_per_min'] * 0.935;
$ourBaseFare = $competitors[$cheapestCompetitor]['base_fare'] * 0.935;
6. تدفق البيانات من التحليل حتى يشوفها المستخدم
الوقت T0: Pricing Engine يشتغل
↓
الوقت T0+5s: يكتب competitor_secret_formulas + competitor_surge_insights
↓
الوقت T0+30m: cron_ai_engine.php (PHP) يقرأ المعادلات ويحدّث kazan
↓
الوقت T0+31m: kazan محدّث بأسعار جديدة (أقل 6.5% من المنافس)
↓
الوقت T0+31m+: rider يطلب رحلة
→ ride/pricing/get.php يقرأ kazan + Redis surge
→ يحسب السعر ← يرجع للراكب
→ السائق يشوف سعر الرحلة
المدة الكاملة من التحليل للمستخدم: ~31 دقيقة (يمكن تقليلها بتشغيل cron_ai_engine بعد Pricing Engine مباشرة).
7. متطلبات السيرفر
| المكون | المتطلب |
|---|---|
| Node.js | v18+ (نوصي v20 LTS) |
| PM2 | لإدارة العمليات (اختياري) |
| MySQL | موجود مسبقاً |
| Redis | موجود مسبقاً |
| RAM إضافي | 256MB كافية (التطبيق خفيف) |
| مساحة | 50MB للملفات + node_modules |
أمان: Node.js ما اله Port
Pricing Engine هو CLI cron job، مش Web Server. ما اله Port مفتوح. يتصل فقط بـ MySQL و Redis داخلياً. لا يحتاج تعديل Nginx/Apache.
8. خطة الرفع (Deployment Checklist)
□ 1. git pull أحدث كود على السيرفر
□ 2. cd backend/pricing-engine && npm install
□ 3. cp .env.example .env # عدّل بيانات MySQL + Redis
□ 4. mysql -u root siro < migrations/001_add_columns.sql
□ 5. npm run build # compile TypeScript
□ 6. npm run analyze:taxif # اختبار يدوي
□ 7. pm2 start ecosystem.config.js # أو crontab
□ 8. pm2 save && pm2 startup
□ 9. تحقق من cron_ai_engine.php يقرأ المعادلات الجديدة
□ 10. اختبر ride/pricing/get.php مع الراكب
9. إضافة تطبيق منافس جديد
□ 1. أضف اسم الحزمة إلى generate_price_tasks.php
(مثلاً: com.newcompetitor.app)
□ 2. انتظر تجميع بيانات كافية (أسبوع scraping)
□ 3. شغّل: npm run analyze -- --competitor=com.newcompetitor.app
□ 4. Pricing Engine سيكتشف الـ Tiers تلقائياً
□ 5. cron_ai_engine.php سيقرأ المعادلات ويطبّق التسعير
□ 6. تلقائياً: مقارنة الأسعار في تطبيق الراكب
10. الخلاصة
| الميزة | الحالة |
|---|---|
| تحليل إحصائي (MAD + Ridge Regression + K-Means) | ✅ تم |
| اكتشاف 3 Tiers تسعيرية | ✅ تم |
| Minimum Fare | ✅ تم |
| اكتشاف Surge حسب ساعة اليوم | ✅ تم |
| تحليل Zone | ✅ تم |
| خصم 6.5% من المنافس | ✅ في cron_ai_engine.php |
| Redis surge للـ get.php | ✅ surge:opportunities |
| PK/FK متوافقة | ✅ تم تحديث schema |
| دعم دول متعددة (JO/SY/EG/IQ) | ✅ Currency-aware |
| دعم منافسين متعددين | ✅ Arrays + foreach |
| Zone-Based Pricing | ⬜ يحتاج إنشاء جدول zone_pricing |
| إشعارات للسائقين بالمناطق الساخنة | ⬜ يحتاج cron_notify_drivers |
| مقارنة أسعار المنافسين في التطبيق | ⬜ يحتاج ربط ride/pricing مع competitor_formulas |