Files
intaleq/docs/03_pricing/PRICING_ENGINE_ARCHITECTURE.md
Hamza-AyedandClaude Opus 5 92dc6b3641 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>
2026-07-27 05:10:29 +03:00

435 lines
19 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Siro Pricing Engine — Architecture & Deployment Guide
<div dir="rtl">
## 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
```bash
# 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
```javascript
// 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 عادي (أبسط)
```bash
# 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؟
بدلاً من معادلة تسعير واحدة لكل البلد، يصبح:
```sql
-- جدول 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`:
```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 عالي:
```php
// 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:
```php
// في ride/pricing/get.php
if ($surgeMultiplier > 1.0) {
$response['surge_warning'] = "⚠️ هذه المنطقة تشهد طلباً مرتفعاً، الأسعار أعلى بنسبة "
. round(($surgeMultiplier - 1) * 100) . "%";
}
```
---
## 5. دعم تطبيقات منافسة متعددة
### 5.1 لكل منافس معادلاته الخاصة
```sql
-- competitor_secret_formulas يدعم:
-- competitor_name = 'com.taxif.passenger' | 'com.careem.ae' | 'com.ubercab' | 'com.jeeny.app'
-- لكل منافس 3 tiers (Economy/Standard/Premium)
-- لكل tier معادلة مستقلة
```
### 5.2 مقارنة الأسعار في التطبيق
```php
// 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`:
```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)
```bash
□ 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 |
</div>