قرار المالك 2026-07-27: باك إند سيرو PHP هو المعتمد، وتطبيقاته المجرّبة ميدانياً تحل محل إعادة البناء المؤرشفة. سيرو نفسه لم يُمسّ. الخريطة: backend · payment_server · loction_server · ride_server · passenger_server · docker · dashboard · stress_test → الجذر siro_rider → apps/rider siro_driver → apps/driver siro_admin → dashboards/admin siro_service → dashboards/service android_bot → apps/android_bot socialBot → apps/socialBot نُسخ المتعقَّب في git سيرو فقط عبر `git archive` (3,198 ملفاً / ~169 م.ب) لا `cp -r` — فاستُثنيت مخلفات البناء تلقائياً. بلا أي تعديل محتوى عمداً: كل ما يلي يصير فرقاً مقروءاً مقابل المصدر. لم يُستورد وسببه: siromove.com (الموقع التسويقي يبقى marketing/ في تريبز، سيرو فيه 8 ملفات) · docs و planning (تريبز له docs/ الخاص) · deploy.sh (ليس نشراً على سيرفر بل `git add . && git push origin --all` — فخّ في مستودع آخر) · transit_dashboard (بانتظار قرار مصير backend-transit و dashboards/transit-web). ⚠️ لا يبني بعد — ثلاثة نواقص متوقعة ومقصودة: 1. `.env` و `lib/env/env.g.dart` غير متعقَّبين في سيرو (أسرار لكل مستأجر): كل تطبيق فلاتر يحتاج .env خاصاً ثم توليد env.g.dart بـ build_runner. 2. إعدادات Firebase (9 ملفات google-services.json و GoogleService-Info.plist) يستبعدها .gitignore تريبز — ولكل مستأجر مشروع Firebase خاص أصلاً. 3. apps/driver في سيرو يشير إلى `../../Intaleq/packages/get` خارج المستودع → يجب ضمّ الحزم داخله أسوة بـ apps/rider. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
120 lines
4.2 KiB
PHP
120 lines
4.2 KiB
PHP
<?php
|
|
/**
|
|
* cron_generate_pricing_cache.php
|
|
* يجمع أسعار المنافسين لكل مربع جغرافي (2.5km) ويحفظ النتيجة في Redis
|
|
* يتم تشغيله كـ Cron Job (CLI) لتخفيف الضغط تماماً عن الاستعلام المباشر للركاب.
|
|
*/
|
|
|
|
require_once __DIR__ . '/../core/bootstrap.php';
|
|
require_once __DIR__ . '/../functions.php';
|
|
|
|
try {
|
|
$con = Database::get('main');
|
|
$redis = getRedisConnection();
|
|
} catch (Exception $e) {
|
|
die("Connection failed: " . $e->getMessage() . "\n");
|
|
}
|
|
|
|
echo "Starting Pricing Cache Generation (Redis)...\n";
|
|
|
|
try {
|
|
// 1. جلب متوسط الأسعار من المنافسين في آخر ساعة للشبكة
|
|
$sql = "SELECT country_code,
|
|
ROUND(latitude / 0.025) * 0.025 AS grid_lat,
|
|
ROUND(longitude / 0.025) * 0.025 AS grid_lng,
|
|
competitor_name,
|
|
AVG(price) as avg_price,
|
|
COUNT(*) as requests_count
|
|
FROM competitor_prices
|
|
WHERE created_at >= DATE_SUB(NOW(), INTERVAL 1 HOUR)
|
|
GROUP BY country_code, grid_lat, grid_lng, competitor_name";
|
|
|
|
$stmt = $con->query($sql);
|
|
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
// بناء مصفوفة الشبكات
|
|
$grids = [];
|
|
foreach ($results as $row) {
|
|
$cc = strtoupper($row['country_code']);
|
|
$gLat = number_format((float)$row['grid_lat'], 3);
|
|
$gLng = number_format((float)$row['grid_lng'], 3);
|
|
|
|
$key = "{$cc}_{$gLat}_{$gLng}";
|
|
|
|
if (!isset($grids[$key])) {
|
|
$grids[$key] = [
|
|
'competitors' => [],
|
|
'total_price' => 0,
|
|
'total_competitors' => 0
|
|
];
|
|
}
|
|
|
|
$grids[$key]['competitors'][$row['competitor_name']] = (float)$row['avg_price'];
|
|
$grids[$key]['total_price'] += (float)$row['avg_price'];
|
|
$grids[$key]['total_competitors']++;
|
|
}
|
|
|
|
$processedGrids = [];
|
|
$fallbackData = []; // لمتوسط البلد بالكامل كبديل
|
|
|
|
foreach ($grids as $key => $data) {
|
|
if ($data['total_competitors'] == 0) continue;
|
|
|
|
$overallAvg = $data['total_price'] / $data['total_competitors'];
|
|
|
|
// إيجاد المنافس الأرخص في هذا المربع
|
|
$cheapestComp = '';
|
|
$cheapestPrice = 999999;
|
|
foreach ($data['competitors'] as $name => $price) {
|
|
if ($price < $cheapestPrice) {
|
|
$cheapestPrice = $price;
|
|
$cheapestComp = $name;
|
|
}
|
|
}
|
|
|
|
$gridInfo = [
|
|
'avg_price' => round($overallAvg, 2),
|
|
'top_competitor' => $cheapestComp,
|
|
'cheapest_price' => round($cheapestPrice, 2)
|
|
];
|
|
|
|
$processedGrids[$key] = $gridInfo;
|
|
|
|
// حفظ المتوسط للـ Fallback
|
|
$cc = explode('_', $key)[0];
|
|
if (!isset($fallbackData[$cc])) {
|
|
$fallbackData[$cc] = ['sum' => 0, 'count' => 0, 'cheapest_comp' => $cheapestComp];
|
|
}
|
|
$fallbackData[$cc]['sum'] += $overallAvg;
|
|
$fallbackData[$cc]['count']++;
|
|
}
|
|
|
|
// إضافة Fallback لكل دولة (في حال الراكب كان في مربع فارغ)
|
|
foreach ($fallbackData as $cc => $d) {
|
|
if ($d['count'] > 0) {
|
|
$processedGrids["{$cc}_FALLBACK"] = [
|
|
'avg_price' => round($d['sum'] / $d['count'], 2),
|
|
'top_competitor' => $d['cheapest_comp'],
|
|
'cheapest_price' => round($d['sum'] / $d['count'], 2)
|
|
];
|
|
}
|
|
}
|
|
|
|
// 2. الحفظ في Redis
|
|
// نحفظ المصفوفة بالكامل كـ JSON String داخل مفتاح رئيسي واحد للسرعة العالية في القراءة
|
|
// مفتاح: siro:cache:pricing:grids
|
|
$redisData = [
|
|
'last_updated' => date('Y-m-d H:i:s'),
|
|
'grids' => $processedGrids
|
|
];
|
|
|
|
$redis->set('siro:cache:pricing:grids', json_encode($redisData, JSON_UNESCAPED_UNICODE));
|
|
|
|
echo "Pricing Cache Generated Successfully in Redis. Grids: " . count($processedGrids) . "\n";
|
|
|
|
} catch (Exception $e) {
|
|
error_log("Error generating pricing cache (Redis): " . $e->getMessage());
|
|
echo "Error: " . $e->getMessage();
|
|
}
|
|
?>
|