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"); }