Update: 2026-06-30 21:12:26

This commit is contained in:
Hamza-Ayed
2026-06-30 21:12:26 +03:00
parent c2eab19045
commit 1ae8acad7a
14 changed files with 90 additions and 55 deletions

View File

@@ -37,7 +37,7 @@ try {
$anomalies = $stmt->fetchAll(PDO::FETCH_ASSOC); $anomalies = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Fetch some recent competitor prices for context // Fetch some recent competitor prices for context
$sqlPrices = "SELECT * FROM competitor_prices"; $sqlPrices = "SELECT * FROM scraped_competitor_prices";
$paramsPrices = []; $paramsPrices = [];
if ($countryCode) { if ($countryCode) {
$sqlPrices .= " WHERE country_code = :country"; $sqlPrices .= " WHERE country_code = :country";

View File

@@ -15,7 +15,7 @@ try {
DATE_FORMAT(created_at, '%Y-%m-%d %H:00:00') AS hour_bucket, DATE_FORMAT(created_at, '%Y-%m-%d %H:00:00') AS hour_bucket,
AVG(price_per_km) AS avg_price_per_km, AVG(price_per_km) AS avg_price_per_km,
COUNT(*) AS sample_count COUNT(*) AS sample_count
FROM competitor_prices FROM scraped_competitor_prices
WHERE created_at >= DATE_SUB(NOW(), INTERVAL 24 HOUR)"; WHERE created_at >= DATE_SUB(NOW(), INTERVAL 24 HOUR)";
$compParams = []; $compParams = [];
if ($countryCode) { if ($countryCode) {
@@ -33,12 +33,12 @@ try {
// 2. PCI by region — group competitor prices by ~0.02° grid cells // 2. PCI by region — group competitor prices by ~0.02° grid cells
$pciSql = "SELECT $pciSql = "SELECT
ROUND(from_latitude * 50, 0) / 50 AS lat_group, ROUND(start_lat * 50, 0) / 50 AS lat_group,
ROUND(from_longitude * 50, 0) / 50 AS lng_group, ROUND(start_lng * 50, 0) / 50 AS lng_group,
competitor_name, competitor_name,
AVG(price_per_km) AS avg_price_per_km, AVG(price_per_km) AS avg_price_per_km,
COUNT(*) AS samples COUNT(*) AS samples
FROM competitor_prices FROM scraped_competitor_prices
WHERE created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY)"; WHERE created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY)";
$pciParams = []; $pciParams = [];
if ($countryCode) { if ($countryCode) {

View File

@@ -35,13 +35,13 @@ try {
// Aggregate competitor data by geographical grid (approx 1.5km x 1.5km) // Aggregate competitor data by geographical grid (approx 1.5km x 1.5km)
$sql = "SELECT $sql = "SELECT
ROUND(from_latitude * 74, 0) / 74 AS lat_group, ROUND(start_lat * 74, 0) / 74 AS lat_group,
ROUND(from_longitude * 74, 0) / 74 AS lng_group, ROUND(start_lng * 74, 0) / 74 AS lng_group,
AVG(price_per_km) as avg_competitor_price_per_km, AVG(price_per_km) as avg_competitor_price_per_km,
COUNT(*) as trip_count COUNT(*) as trip_count
FROM competitor_prices FROM scraped_competitor_prices
WHERE country_code = :country WHERE country_code = :country
AND distance_km > 0 AND price_per_km > 0
AND created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY) AND created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY)
GROUP BY lat_group, lng_group GROUP BY lat_group, lng_group
HAVING trip_count >= 3"; // Require at least 3 trips for a reliable heatmap point HAVING trip_count >= 3"; // Require at least 3 trips for a reliable heatmap point

View File

@@ -32,8 +32,8 @@ try {
// 1. حساب الـ baseline (آخر 7 أيام، بدون آخر 6 ساعات) // 1. حساب الـ baseline (آخر 7 أيام، بدون آخر 6 ساعات)
// و current (آخر ساعتين) لكل منافس في كل خلية grid // و current (آخر ساعتين) لكل منافس في كل خلية grid
$sql = "SELECT $sql = "SELECT
ROUND(cp.from_latitude * 74, 0) / 74 AS lat_group, ROUND(cp.start_lat * 74, 0) / 74 AS lat_group,
ROUND(cp.from_longitude * 74, 0) / 74 AS lng_group, ROUND(cp.start_lng * 74, 0) / 74 AS lng_group,
cp.competitor_name, cp.competitor_name,
cp.country_code, cp.country_code,
AVG(CASE WHEN cp.created_at < DATE_SUB(NOW(), INTERVAL 6 HOUR) AVG(CASE WHEN cp.created_at < DATE_SUB(NOW(), INTERVAL 6 HOUR)
@@ -42,7 +42,7 @@ try {
THEN cp.price_per_km END) AS current_avg, THEN cp.price_per_km END) AS current_avg,
COUNT(*) AS total_samples, COUNT(*) AS total_samples,
SUM(CASE WHEN cp.created_at >= DATE_SUB(NOW(), INTERVAL 2 HOUR) THEN 1 ELSE 0 END) AS recent_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 FROM scraped_competitor_prices cp
WHERE cp.created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY) WHERE cp.created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY)
AND cp.price_per_km > 0 AND cp.price_per_km > 0
$where $where

View File

@@ -21,9 +21,9 @@ $siroBasePrice = filterRequest('siro_base_price', 'float') ?? 10000.0;
try { try {
// 3. Fetch recent competitor prices for this region to supply context to Gemini // 3. Fetch recent competitor prices for this region to supply context to Gemini
$sqlPrices = "SELECT competitor_name, total_price, distance_km $sqlPrices = "SELECT competitor_name, price_amount AS total_price, (price_amount / price_per_km) AS distance_km
FROM competitor_prices FROM scraped_competitor_prices
WHERE country_code = :country WHERE country_code = :country AND price_per_km > 0
ORDER BY created_at DESC LIMIT 10"; ORDER BY created_at DESC LIMIT 10";
$stmtPrices = $con->prepare($sqlPrices); $stmtPrices = $con->prepare($sqlPrices);
$stmtPrices->execute([':country' => strtoupper($countryCode)]); $stmtPrices->execute([':country' => strtoupper($countryCode)]);

View File

@@ -22,10 +22,10 @@ try {
} }
// 1. Fetch recent competitor trips (last 7 days, limit 500 for fast simulation) // 1. Fetch recent competitor trips (last 7 days, limit 500 for fast simulation)
$sql = "SELECT distance_km, total_price, competitor_name $sql = "SELECT (price_amount / price_per_km) AS distance_km, price_amount AS total_price, competitor_name
FROM competitor_prices FROM scraped_competitor_prices
WHERE country_code = :country WHERE country_code = :country
AND distance_km > 0 AND price_per_km > 0
AND created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY) AND created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY)
ORDER BY created_at DESC ORDER BY created_at DESC
LIMIT 500"; LIMIT 500";

View File

@@ -35,7 +35,7 @@ if (empty($data) || !is_array($data)) {
} }
$insertedCount = 0; $insertedCount = 0;
$stmt = $con->prepare("INSERT INTO scraped_competitor_prices (task_id, app_name, start_location, end_location, price_amount, currency) VALUES (?, ?, ?, ?, ?, ?)"); $stmt = $con->prepare("INSERT INTO scraped_competitor_prices (task_id, app_name, competitor_name, start_location, end_location, start_lat, start_lng, end_lat, end_lng, price_amount, price_per_km, currency, country_code) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
foreach ($data as $row) { foreach ($data as $row) {
if (isset($row['status']) && $row['status'] !== 'success') { if (isset($row['status']) && $row['status'] !== 'success') {
@@ -83,7 +83,21 @@ foreach ($data as $row) {
continue; continue;
} }
if ($stmt->execute([$taskId, $appName, $startLoc, $endLoc, $amount, $currency])) { $distanceKm = (float)($resultData['distance_km'] ?? 1);
if ($distanceKm <= 0) $distanceKm = 1;
$pricePerKm = $amount / $distanceKm;
$startLat = $resultData['start_lat'] ?? null;
$startLng = $resultData['start_lng'] ?? null;
$endLat = $resultData['end_lat'] ?? null;
$endLng = $resultData['end_lng'] ?? null;
$countryCode = 'JO'; // Default for now, as scraping is in Jordan
if ($stmt->execute([
$taskId, $appName, $appName, $startLoc, $endLoc,
$startLat, $startLng, $endLat, $endLng,
$amount, $pricePerKm, $currency, $countryCode
])) {
$insertedCount++; $insertedCount++;
} else { } else {
echo "Failed to insert task_id: $taskId. Error: " . implode(" ", $stmt->errorInfo()) . "\n"; echo "Failed to insert task_id: $taskId. Error: " . implode(" ", $stmt->errorInfo()) . "\n";

View File

@@ -30,8 +30,8 @@ echo "[".date('Y-m-d H:i:s')."] Starting cron_surge_opportunity...\n";
try { try {
// 1. حساب الـ baseline و current // 1. حساب الـ baseline و current
$sql = "SELECT $sql = "SELECT
ROUND(cp.from_latitude * 74, 0) / 74 AS lat_group, ROUND(cp.start_lat * 74, 0) / 74 AS lat_group,
ROUND(cp.from_longitude * 74, 0) / 74 AS lng_group, ROUND(cp.start_lng * 74, 0) / 74 AS lng_group,
cp.competitor_name, cp.competitor_name,
cp.country_code, cp.country_code,
AVG(CASE WHEN cp.created_at < DATE_SUB(NOW(), INTERVAL 6 HOUR) AVG(CASE WHEN cp.created_at < DATE_SUB(NOW(), INTERVAL 6 HOUR)
@@ -40,7 +40,7 @@ try {
THEN cp.price_per_km END) AS current_avg, THEN cp.price_per_km END) AS current_avg,
COUNT(*) AS total_samples, COUNT(*) AS total_samples,
SUM(CASE WHEN cp.created_at >= DATE_SUB(NOW(), INTERVAL 2 HOUR) THEN 1 ELSE 0 END) AS recent_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 FROM scraped_competitor_prices cp
WHERE cp.created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY) WHERE cp.created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY)
AND cp.price_per_km > 0 AND cp.price_per_km > 0
GROUP BY lat_group, lng_group, cp.competitor_name, cp.country_code GROUP BY lat_group, lng_group, cp.competitor_name, cp.country_code

View File

@@ -26,10 +26,10 @@ try {
foreach ($countries as $countryCode) { foreach ($countries as $countryCode) {
// 1. Calculate Average PCI and Market Share // 1. Calculate Average PCI and Market Share
$sql = "SELECT distance_km, total_price $sql = "SELECT (price_amount / price_per_km) AS distance_km, price_amount AS total_price
FROM competitor_prices FROM scraped_competitor_prices
WHERE country_code = :country WHERE country_code = :country
AND distance_km > 0 AND price_per_km > 0
AND created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY)"; AND created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY)";
$stmt = $con->prepare($sql); $stmt = $con->prepare($sql);
$stmt->execute([':country' => $countryCode]); $stmt->execute([':country' => $countryCode]);

View File

@@ -17,19 +17,27 @@ try {
// 1. Ensure Table Exists // 1. Ensure Table Exists
$sql = " $sql = "
CREATE TABLE IF NOT EXISTS competitor_prices ( CREATE TABLE IF NOT EXISTS `scraped_competitor_prices` (
id INT AUTO_INCREMENT PRIMARY KEY, `id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
competitor_name VARCHAR(50) NOT NULL, `task_id` varchar(100) DEFAULT NULL,
from_latitude VARCHAR(30) NOT NULL, `app_name` varchar(100) NOT NULL,
from_longitude VARCHAR(30) NOT NULL, `competitor_name` varchar(100) NOT NULL,
to_latitude VARCHAR(30) NOT NULL, `start_location` varchar(255) NOT NULL,
to_longitude VARCHAR(30) NOT NULL, `end_location` varchar(255) NOT NULL,
distance_km DECIMAL(8,2) NOT NULL, `start_lat` decimal(10,7) DEFAULT NULL,
total_price DECIMAL(10,2) NOT NULL, `start_lng` decimal(10,7) DEFAULT NULL,
price_per_km DECIMAL(8,2) NOT NULL, `end_lat` decimal(10,7) DEFAULT NULL,
country_code VARCHAR(5) NOT NULL DEFAULT 'SY', `end_lng` decimal(10,7) DEFAULT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, `price_amount` decimal(8,2) NOT NULL,
INDEX idx_competitor_country (competitor_name, country_code) `price_per_km` decimal(8,2) NOT NULL,
`currency` varchar(10) NOT NULL DEFAULT 'JOD',
`country_code` varchar(10) NOT NULL DEFAULT 'JO',
`scraped_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
KEY `idx_app_name` (`app_name`),
KEY `idx_competitor_name` (`competitor_name`),
KEY `idx_start_location` (`start_location`),
KEY `idx_country_code` (`country_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
"; ";
$con->exec($sql); $con->exec($sql);

View File

@@ -133,11 +133,13 @@ if ($method === 'GET') {
// 1. Save to MySQL // 1. Save to MySQL
$stmt = $con->prepare(" $stmt = $con->prepare("
INSERT INTO competitor_prices INSERT INTO scraped_competitor_prices
(competitor_name, from_latitude, from_longitude, to_latitude, to_longitude, distance_km, total_price, price_per_km, country_code) (task_id, app_name, competitor_name, start_location, end_location, start_lat, start_lng, end_lat, end_lng, price_amount, price_per_km, currency, country_code)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
"); ");
$stmt->execute([$app_name, (string)$start_lat, (string)$start_lng, (string)$end_lat, (string)$end_lng, $distance_km, $price, $pricePerKm, $country_code]); $start_loc = "Lat: $start_lat, Lng: $start_lng";
$end_loc = "Lat: $end_lat, Lng: $end_lng";
$stmt->execute([$task_id, $app_name, $app_name, $start_loc, $end_loc, (string)$start_lat, (string)$start_lng, (string)$end_lat, (string)$end_lng, $price, $pricePerKm, 'JOD', $country_code]);
// 2. Save to Redis (Calculate Price Per KM) // 2. Save to Redis (Calculate Price Per KM)
if ($distance_km > 0 && $price > 0) { if ($distance_km > 0 && $price > 0) {

View File

@@ -41,7 +41,7 @@ $logEntries = [];
foreach ($countries as $country => $cc) { foreach ($countries as $country => $cc) {
// 1. متوسط سعر الكيلو لكل منافس (آخر 24 ساعة) // 1. متوسط سعر الكيلو لكل منافس (آخر 24 ساعة)
$sql = "SELECT competitor_name, AVG(price_per_km) AS avg_ppm $sql = "SELECT competitor_name, AVG(price_per_km) AS avg_ppm
FROM competitor_prices FROM scraped_competitor_prices
WHERE country_code = :cc WHERE country_code = :cc
AND created_at >= DATE_SUB(NOW(), INTERVAL 24 HOUR) AND created_at >= DATE_SUB(NOW(), INTERVAL 24 HOUR)
AND price_per_km > 0 AND price_per_km > 0

View File

@@ -288,13 +288,14 @@ function calculateDynamicPrice($country, $minFare, $distance, $duration, $kazanR
$maxTlng = $destLng + $lngDelta; $maxTlng = $destLng + $lngDelta;
// Layer 1: Start and End match within bounding box // Layer 1: Start and End match within bounding box
$sqlComp = "SELECT total_price, distance_km $sqlComp = "SELECT price_amount AS total_price, (price_amount / price_per_km) AS distance_km
FROM competitor_prices FROM scraped_competitor_prices
WHERE country_code = :country_code WHERE country_code = :country_code
AND (from_latitude + 0.0) BETWEEN :min_flat AND :max_flat AND (start_lat + 0.0) BETWEEN :min_flat AND :max_flat
AND (from_longitude + 0.0) BETWEEN :min_flng AND :max_flng AND (start_lng + 0.0) BETWEEN :min_flng AND :max_flng
AND (to_latitude + 0.0) BETWEEN :min_tlat AND :max_tlat AND (end_lat + 0.0) BETWEEN :min_tlat AND :max_tlat
AND (to_longitude + 0.0) BETWEEN :min_tlng AND :max_tlng AND (end_lng + 0.0) BETWEEN :min_tlng AND :max_tlng
AND price_per_km > 0
AND created_at >= DATE_SUB(NOW(), INTERVAL 24 HOUR) AND created_at >= DATE_SUB(NOW(), INTERVAL 24 HOUR)
ORDER BY created_at DESC LIMIT 5"; ORDER BY created_at DESC LIMIT 5";
$stmtComp = $con->prepare($sqlComp); $stmtComp = $con->prepare($sqlComp);
@@ -313,11 +314,12 @@ function calculateDynamicPrice($country, $minFare, $distance, $duration, $kazanR
if (empty($matches)) { if (empty($matches)) {
// Layer 2 Fallback: Start match only within bounding box // Layer 2 Fallback: Start match only within bounding box
$sqlFallback = "SELECT total_price, distance_km $sqlFallback = "SELECT price_amount AS total_price, (price_amount / price_per_km) AS distance_km
FROM competitor_prices FROM scraped_competitor_prices
WHERE country_code = :country_code WHERE country_code = :country_code
AND (from_latitude + 0.0) BETWEEN :min_flat AND :max_flat AND (start_lat + 0.0) BETWEEN :min_flat AND :max_flat
AND (from_longitude + 0.0) BETWEEN :min_flng AND :max_flng AND (start_lng + 0.0) BETWEEN :min_flng AND :max_flng
AND price_per_km > 0
AND created_at >= DATE_SUB(NOW(), INTERVAL 24 HOUR) AND created_at >= DATE_SUB(NOW(), INTERVAL 24 HOUR)
ORDER BY created_at DESC LIMIT 10"; ORDER BY created_at DESC LIMIT 10";
$stmtFallback = $con->prepare($sqlFallback); $stmtFallback = $con->prepare($sqlFallback);

View File

@@ -1954,12 +1954,21 @@ CREATE TABLE IF NOT EXISTS `scraped_competitor_prices` (
`id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, `id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`task_id` varchar(100) DEFAULT NULL, `task_id` varchar(100) DEFAULT NULL,
`app_name` varchar(100) NOT NULL, `app_name` varchar(100) NOT NULL,
`competitor_name` varchar(100) NOT NULL,
`start_location` varchar(255) NOT NULL, `start_location` varchar(255) NOT NULL,
`end_location` varchar(255) NOT NULL, `end_location` varchar(255) NOT NULL,
`start_lat` decimal(10,7) DEFAULT NULL,
`start_lng` decimal(10,7) DEFAULT NULL,
`end_lat` decimal(10,7) DEFAULT NULL,
`end_lng` decimal(10,7) DEFAULT NULL,
`price_amount` decimal(8,2) NOT NULL, `price_amount` decimal(8,2) NOT NULL,
`price_per_km` decimal(8,2) NOT NULL,
`currency` varchar(10) NOT NULL DEFAULT 'JOD', `currency` varchar(10) NOT NULL DEFAULT 'JOD',
`country_code` varchar(10) NOT NULL DEFAULT 'JO',
`scraped_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `scraped_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
KEY `idx_app_name` (`app_name`), KEY `idx_app_name` (`app_name`),
KEY `idx_start_location` (`start_location`) KEY `idx_competitor_name` (`competitor_name`),
KEY `idx_start_location` (`start_location`),
KEY `idx_country_code` (`country_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;