Files
Siro/backend/Admin/marketing/surge_opportunity_index.php
2026-06-22 00:31:29 +03:00

156 lines
5.8 KiB
PHP
Raw 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.
<?php
/**
* surge_opportunity_index.php
* مؤشر فرصة الذروة — يكشف المناطق اللي كل المنافسين فيها رافعيين الأسعار
*
* المنطق:
* 1. لكل منطقة grid (~1.5km)، لكل منافس
* 2. baseline = متوسط price_per_km آخر 7 أيام (بدون آخر 6 ساعات)
* 3. current = متوسط price_per_km آخر ساعتين
* 4. إذا current > baseline × 1.2 → المنافس في surge
* 5. إذا كل المنافسين النشطين في zone في surge → فرصة ذروة ✅
*/
require_once __DIR__ . '/../../connect.php';
if ($role !== 'admin' && $role !== 'super_admin') {
http_response_code(403);
echo json_encode(['status' => 'failure', 'message' => 'Unauthorized']);
exit;
}
try {
$countryCode = filterRequest('country_code');
$where = '';
$params = [];
if ($countryCode) {
$where = 'AND cp.country_code = :country';
$params[':country'] = strtoupper($countryCode);
}
// 1. حساب الـ baseline (آخر 7 أيام، بدون آخر 6 ساعات)
// و current (آخر ساعتين) لكل منافس في كل خلية grid
$sql = "SELECT
ROUND(cp.from_latitude * 74, 0) / 74 AS lat_group,
ROUND(cp.from_longitude * 74, 0) / 74 AS lng_group,
cp.competitor_name,
cp.country_code,
AVG(CASE WHEN cp.created_at < DATE_SUB(NOW(), INTERVAL 6 HOUR)
THEN cp.price_per_km END) AS baseline_avg,
AVG(CASE WHEN cp.created_at >= DATE_SUB(NOW(), INTERVAL 2 HOUR)
THEN cp.price_per_km END) AS current_avg,
COUNT(*) AS total_samples,
SUM(CASE WHEN cp.created_at >= DATE_SUB(NOW(), INTERVAL 2 HOUR) THEN 1 ELSE 0 END) AS recent_samples
FROM competitor_prices cp
WHERE cp.created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY)
AND cp.price_per_km > 0
$where
GROUP BY lat_group, lng_group, cp.competitor_name, cp.country_code
HAVING recent_samples >= 2
ORDER BY lat_group, lng_group, cp.competitor_name";
$stmt = $con->prepare($sql);
if ($countryCode) {
foreach ($params as $k => $v) {
$stmt->bindValue($k, $v);
}
}
$stmt->execute();
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
// 2. تجميع البيانات لكل zone
$zones = [];
foreach ($rows as $row) {
$zoneKey = $row['lat_group'] . '_' . $row['lng_group'];
$baseline = (float)$row['baseline_avg'];
$current = (float)$row['current_avg'];
$surgeRatio = ($baseline > 0) ? round($current / $baseline, 2) : 1.0;
$isSurging = $baseline > 0 && $surgeRatio >= 1.2;
if (!isset($zones[$zoneKey])) {
$zones[$zoneKey] = [
'lat' => (float)$row['lat_group'],
'lng' => (float)$row['lng_group'],
'country_code' => $row['country_code'],
'competitors' => [],
'total_active' => 0,
'total_surging' => 0,
];
}
$zones[$zoneKey]['competitors'][] = [
'name' => $row['competitor_name'],
'baseline' => round($baseline, 2),
'current' => round($current, 2),
'surge_ratio' => $surgeRatio,
'is_surging' => $isSurging,
];
$zones[$zoneKey]['total_active']++;
if ($isSurging) {
$zones[$zoneKey]['total_surging']++;
}
}
// 3. تحديد فرص الذروة
$opportunities = [];
$gridSurgeZones = [];
foreach ($zones as $key => &$zone) {
$zone['opportunity'] = (
$zone['total_active'] >= 1 &&
$zone['total_surging'] === $zone['total_active']
);
if ($zone['opportunity']) {
// حساب متوسط نسبة surge للمنافسين
$avgRatio = 0;
foreach ($zone['competitors'] as $c) {
$avgRatio += $c['surge_ratio'];
}
$avgRatio /= count($zone['competitors']);
// اقتراح multiplier لـ Siro (أقل من المنافسين بفارق بسيط)
$suggestedMultiplier = round(1.0 + ($avgRatio - 1.0) * 0.6, 2);
if ($suggestedMultiplier < 1.0) $suggestedMultiplier = 1.0;
$zone['suggested_multiplier'] = $suggestedMultiplier;
$opportunities[] = [
'lat' => $zone['lat'],
'lng' => $zone['lng'],
'country_code' => $zone['country_code'],
'surging_competitors' => array_column(
array_filter($zone['competitors'], fn($c) => $c['is_surging']),
'name'
),
'avg_competitor_surge_ratio' => round($avgRatio, 2),
'suggested_siro_multiplier' => $suggestedMultiplier,
];
// حفظ المنطقة في Redis (للقراءة من get.php بعدين)
$gridSurgeZones[$key] = $suggestedMultiplier;
}
}
unset($zone);
// 4. تخزين فرص الذروة في Redis بصلاحية 10 دقائق
if (!empty($gridSurgeZones) && isset($redis) && $redis !== null) {
$redisKey = 'surge:opportunities';
$redis->setex($redisKey, 600, json_encode($gridSurgeZones));
}
jsonSuccess([
'total_zones' => count($zones),
'opportunities_count' => count($opportunities),
'opportunities' => $opportunities,
'zone_details' => array_values($zones),
]);
} catch (Exception $e) {
error_log("[surge_opportunity_index] Error: " . $e->getMessage());
jsonError("Failed to calculate surge opportunity index");
}