Compare commits

7 Commits
Author SHA1 Message Date
Hamza-Ayed c24e921bac Update: 2026-07-06 01:47:37 2026-07-06 01:47:37 +03:00
Hamza-Ayed e7453cb6f9 Update: 2026-07-06 01:31:52 2026-07-06 01:31:53 +03:00
Hamza-Ayed bec8f61d70 Update: 2026-07-06 01:28:23 2026-07-06 01:28:23 +03:00
Hamza-Ayed 4d16fbda48 Update: 2026-07-06 01:20:19 2026-07-06 01:20:19 +03:00
Hamza-Ayed ccf7dc99ee Update: 2026-07-06 01:18:35 2026-07-06 01:18:36 +03:00
Hamza-Ayed 1b67c5e8fc Update: 2026-07-06 01:01:29 2026-07-06 01:01:30 +03:00
Hamza-Ayed d00502769a Update: 2026-07-06 00:53:15 2026-07-06 00:53:15 +03:00
8 changed files with 242 additions and 36 deletions
+6
View File
@@ -73,9 +73,15 @@ try {
$newAwfarPrice = round($newSpeedPrice * 0.85, 3); $newAwfarPrice = round($newSpeedPrice * 0.85, 3);
// أسعار الدقائق // أسعار الدقائق
if ($country === 'JO') {
$newNormalMin = 0.06;
$newPeakMin = 0.07;
$newLateMin = 0.08;
} else {
$newNormalMin = round($newSpeedPrice / 4, 3); $newNormalMin = round($newSpeedPrice / 4, 3);
$newPeakMin = round($newNormalMin * 1.15, 3); $newPeakMin = round($newNormalMin * 1.15, 3);
$newLateMin = round($newNormalMin * 1.25, 3); $newLateMin = round($newNormalMin * 1.25, 3);
}
$updateSql = "UPDATE kazan $updateSql = "UPDATE kazan
SET speedPrice = :speedPrice, SET speedPrice = :speedPrice,
+15 -3
View File
@@ -85,15 +85,27 @@ foreach ($data as $row) {
} }
$distanceKm = (float)($resultData['distance_km'] ?? 1); $distanceKm = (float)($resultData['distance_km'] ?? 1);
if ($distanceKm <= 0) $distanceKm = 1; // Prevent division by zero
$pricePerKm = $amount / $distanceKm;
$durationMin = isset($resultData['duration_min']) ? (int)$resultData['duration_min'] : null; $durationMin = isset($resultData['duration_min']) ? (int)$resultData['duration_min'] : null;
$startLat = $resultData['start_lat'] ?? null; $startLat = $resultData['start_lat'] ?? null;
$startLng = $resultData['start_lng'] ?? null; $startLng = $resultData['start_lng'] ?? null;
$endLat = $resultData['end_lat'] ?? null; $endLat = $resultData['end_lat'] ?? null;
$endLng = $resultData['end_lng'] ?? null; $endLng = $resultData['end_lng'] ?? null;
$countryCode = $row['country_code'] ?? 'JO'; // Default
// --- OSRM Integration (Dynamic Distance & Duration) ---
require_once __DIR__ . '/../core/osrm_routing.php';
if ($startLat && $endLat && (!$durationMin || $durationMin <= 0)) {
$osrmResult = getOsrmRouteDetails($startLat, $startLng, $endLat, $endLng, $countryCode);
if ($osrmResult) {
$distanceKm = $osrmResult['distance_km']; // Override with accurate OSRM distance
$durationMin = $osrmResult['duration_min'];
}
}
// ------------------------------------------------------
if ($distanceKm <= 0) $distanceKm = 1; // Prevent division by zero
$pricePerKm = $amount / $distanceKm;
$countryCode = $row['country_code'] ?? 'JO'; // Default $countryCode = $row['country_code'] ?? 'JO'; // Default
+74
View File
@@ -0,0 +1,74 @@
<?php
// =========================================================
// backend/bot/fill_durations.php
// One-off script to fill missing duration_min values
// in the scraped_competitor_prices table using Intaleq Maps.
// =========================================================
require_once __DIR__ . '/../core/bootstrap.php';
require_once __DIR__ . '/../functions.php';
require_once __DIR__ . '/../core/osrm_routing.php';
try {
$con = Database::get('main');
} catch (Exception $e) {
die("Database connection failed: " . $e->getMessage() . "\n");
}
echo "Fetching rows where duration_min IS NULL...\n";
// Get rows needing update
$stmt = $con->prepare("
SELECT id, start_lat, start_lng, end_lat, end_lng, country_code
FROM scraped_competitor_prices
WHERE duration_min IS NULL
AND start_lat IS NOT NULL
AND start_lng IS NOT NULL
");
$stmt->execute();
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
if (!$rows || count($rows) === 0) {
echo "No rows found needing duration updates.\n";
exit(0);
}
echo "Found " . count($rows) . " rows to update. Processing...\n";
$updateStmt = $con->prepare("
UPDATE scraped_competitor_prices
SET duration_min = :duration_min, distance_km = :distance_km
WHERE id = :id
");
$successCount = 0;
$failCount = 0;
foreach ($rows as $index => $row) {
$id = $row['id'];
$countryCode = $row['country_code'];
// Call GraphHopper / Intaleq Maps
$routeInfo = getOsrmRouteDetails($row['start_lat'], $row['start_lng'], $row['end_lat'], $row['end_lng'], $countryCode);
if ($routeInfo && isset($routeInfo['duration_min']) && isset($routeInfo['distance_km'])) {
$updateStmt->execute([
':duration_min' => (int) round($routeInfo['duration_min']),
':distance_km' => $routeInfo['distance_km'],
':id' => $id
]);
$successCount++;
echo "Row $id: Distance {$routeInfo['distance_km']}km, Duration " . round($routeInfo['duration_min']) . "min [SUCCESS]\n";
} else {
$failCount++;
echo "Row $id: Failed to fetch route details.\n";
}
// Small sleep to avoid hitting API rate limits too hard
usleep(100000); // 100ms
}
echo "======================================\n";
echo "Completed processing " . count($rows) . " rows.\n";
echo "Success: $successCount\n";
echo "Failed: $failCount\n";
+36 -1
View File
@@ -100,6 +100,37 @@ $countriesConfig = [
['name' => 'Um Uthaina', 'lat' => 31.9610, 'lng' => 35.8770], ['name' => 'Um Uthaina', 'lat' => 31.9610, 'lng' => 35.8770],
['name' => 'Jabal Al-Weibdeh', 'lat' => 31.9560, 'lng' => 35.9220], ['name' => 'Jabal Al-Weibdeh', 'lat' => 31.9560, 'lng' => 35.9220],
['name' => 'Marj Al-Hamam', 'lat' => 31.9000, 'lng' => 35.8500], ['name' => 'Marj Al-Hamam', 'lat' => 31.9000, 'lng' => 35.8500],
['name' => '7th Circle', 'lat' => 31.9525, 'lng' => 35.8600],
['name' => 'Zarqa New', 'lat' => 32.0833, 'lng' => 36.1000],
['name' => 'Zarqa Camp', 'lat' => 32.0667, 'lng' => 36.0833],
['name' => 'Russeifa', 'lat' => 32.0167, 'lng' => 36.0500],
['name' => 'Marka', 'lat' => 31.9833, 'lng' => 35.9833],
['name' => 'Taj Mall', 'lat' => 31.9423, 'lng' => 35.9038],
['name' => 'City Mall', 'lat' => 31.9806, 'lng' => 35.8353],
['name' => 'Mecca Mall', 'lat' => 31.9792, 'lng' => 35.8464],
['name' => 'Abdali Mall', 'lat' => 31.9644, 'lng' => 35.9117],
['name' => 'Alrabieh', 'lat' => 31.9722, 'lng' => 35.8814],
['name' => 'Dabouq', 'lat' => 31.9989, 'lng' => 35.8153],
['name' => 'Al-Fuheis', 'lat' => 32.0125, 'lng' => 35.7761],
['name' => 'Wadi Al-Seer', 'lat' => 31.9533, 'lng' => 35.8197],
['name' => 'Bayader', 'lat' => 31.9472, 'lng' => 35.8236],
['name' => 'Tariq', 'lat' => 32.0083, 'lng' => 35.9417],
['name' => 'Hashmi Shamali', 'lat' => 31.9800, 'lng' => 35.9556],
['name' => 'Jabal Al-Hussein', 'lat' => 31.9639, 'lng' => 35.9231],
['name' => 'Downtown Amman', 'lat' => 31.9517, 'lng' => 35.9333],
['name' => 'Al-Ashrafiyeh', 'lat' => 31.9361, 'lng' => 35.9417],
['name' => 'Al-Muqabalain', 'lat' => 31.9011, 'lng' => 35.9228],
['name' => 'Al-Wehdat', 'lat' => 31.9250, 'lng' => 35.9417],
['name' => 'Abu Nuseir', 'lat' => 32.0469, 'lng' => 35.8750],
['name' => 'Shafa Badran', 'lat' => 32.0417, 'lng' => 35.9083],
['name' => 'Al-Zaytoonah Uni', 'lat' => 31.8319, 'lng' => 35.9281],
['name' => 'German Jordan Uni','lat' => 31.7778, 'lng' => 35.8114],
['name' => 'Queen Alia Airport','lat'=> 31.7225, 'lng' => 35.9933],
['name' => 'Irbid Center', 'lat' => 32.5514, 'lng' => 35.8500],
['name' => 'Jerash', 'lat' => 32.2817, 'lng' => 35.8917],
['name' => 'Salt', 'lat' => 32.0333, 'lng' => 35.7333],
['name' => 'Madaba', 'lat' => 31.7167, 'lng' => 35.7950],
['name' => 'Dead Sea Hotels', 'lat' => 31.7161, 'lng' => 35.5878]
] ]
] ]
]; ];
@@ -130,7 +161,11 @@ foreach ($countriesConfig as $countryCode => $config) {
$competitors = $config['competitors']; $competitors = $config['competitors'];
$regions = $config['regions']; $regions = $config['regions'];
foreach ($regions as $region) { // Randomly select 20 regions (or all if less than 20)
shuffle($regions);
$selectedRegions = array_slice($regions, 0, 20);
foreach ($selectedRegions as $region) {
// A. Generate Start Point (within 2km of region center) // A. Generate Start Point (within 2km of region center)
$start = generateRandomPoint($region['lat'], $region['lng'], 2); $start = generateRandomPoint($region['lat'], $region['lng'], 2);
+16 -3
View File
@@ -131,15 +131,28 @@ if ($method === 'GET') {
$pricePerKm = $distance_km > 0 ? ($price / $distance_km) : 0.0; $pricePerKm = $distance_km > 0 ? ($price / $distance_km) : 0.0;
$country_code = $result_data['country_code'] ?? 'SY'; $country_code = $result_data['country_code'] ?? 'SY';
// --- OSRM Integration (Dynamic Distance & Duration) ---
require_once __DIR__ . '/../core/osrm_routing.php';
$duration_min = 0;
if ($start_lat != 0 && $end_lat != 0) {
$osrmResult = getOsrmRouteDetails($start_lat, $start_lng, $end_lat, $end_lng, $country_code);
if ($osrmResult) {
$distance_km = $osrmResult['distance_km']; // Override with accurate OSRM distance
$duration_min = $osrmResult['duration_min'];
$pricePerKm = $distance_km > 0 ? ($price / $distance_km) : 0.0; // Recalculate based on OSRM
}
}
// ------------------------------------------------------
// 1. Save to MySQL // 1. Save to MySQL
$stmt = $con->prepare(" $stmt = $con->prepare("
INSERT INTO scraped_competitor_prices 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) (task_id, app_name, competitor_name, start_location, end_location, start_lat, start_lng, end_lat, end_lng, price_amount, price_per_km, distance_km, duration_min, currency, country_code)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
"); ");
$start_loc = "Lat: $start_lat, Lng: $start_lng"; $start_loc = "Lat: $start_lat, Lng: $start_lng";
$end_loc = "Lat: $end_lat, Lng: $end_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]); $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, $distance_km, $duration_min, '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) {
+60
View File
@@ -0,0 +1,60 @@
<?php
// ============================================================
// osrm_routing.php
// Helper functions for OpenStreetMap Routing Machine (OSRM)
// Used to estimate accurate distance (km) and duration (mins)
// ============================================================
/**
* Get Distance and Duration between two coordinates using public OSRM server.
* Note: If you host your own OSRM, replace the $osrmBaseUrl.
*
* @param float $startLat
* @param float $startLng
* @param float $endLat
* @param float $endLng
* @param string $countryCode (e.g. 'JO', 'SY', 'EG')
* @return array|null Returns ['distance_km' => float, 'duration_min' => float] or null on failure.
*/
function getOsrmRouteDetails($startLat, $startLng, $endLat, $endLng, $countryCode = 'JO') {
// Intaleq Maps SaaS server handles all countries (Jordan, Syria, Egypt)
$baseUrl = "https://map-saas.intaleqapp.com/api/maps/route";
$queryParams = http_build_query([
'fromLat' => $startLat,
'fromLng' => $startLng,
'toLat' => $endLat,
'toLng' => $endLng
]);
$url = "{$baseUrl}?{$queryParams}";
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_TIMEOUT, 5); // 5 seconds timeout
// Add Intaleq API Key Header
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"x-api-key: in_9478b32836d19cff73db3063"
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode === 200 && $response) {
$data = json_decode($response, true);
if (isset($data['distance']) && isset($data['duration'])) {
$distanceMeters = (float)$data['distance'];
$durationSeconds = isset($data['trafficAwareDuration']) ? (float)$data['trafficAwareDuration'] : (float)$data['duration'];
return [
'distance_km' => round($distanceMeters / 1000, 2),
'duration_min' => round($durationSeconds / 60, 2)
];
}
}
return null;
}
+17 -2
View File
@@ -185,11 +185,26 @@ function calculateDynamicPrice($country, $minFare, $distance, $duration, $kazanR
} }
$longSpeedThresholdKm = 40.0; $longSpeedThresholdKm = 40.0;
$longSpeedPerKm = 26.0;
// Country specific overrides for long distance pricing
// Damascus bounds to roughly estimate if we are in Syria vs Jordan/Egypt
$isSyria = ($passengerLat > 32.0 && $passengerLat < 37.0 && $passengerLng > 35.0 && $passengerLng < 42.0 && $kazanRow['country'] === 'Syria');
$isJordan = ($kazanRow['country'] === 'Jordan' || $kazanRow['country'] === 'JO');
$isEgypt = ($kazanRow['country'] === 'Egypt' || $kazanRow['country'] === 'EG');
if ($isJordan) {
$longSpeedPerKm = 0.30; // Reasonable long distance speed price for Jordan
$longTripPerMin = 0.06; // Reasonable long distance minute price for Jordan
} else if ($isEgypt) {
$longSpeedPerKm = 18.0; // Reasonable long distance speed price for Egypt
$longTripPerMin = 0.80; // 80 Piasters for long distance minute price for Egypt
} else {
$longSpeedPerKm = 26.0; // Syrian Pound
$longTripPerMin = 6.0; // Syrian Pound
}
$mediumDistThresholdKm = 25.0; $mediumDistThresholdKm = 25.0;
$longDistThresholdKm = 35.0; $longDistThresholdKm = 35.0;
$longTripPerMin = 6.0;
$minuteCapMedium = 60; $minuteCapMedium = 60;
$minuteCapLong = 80; $minuteCapLong = 80;
$freeMinutesLong = 10; $freeMinutesLong = 10;
@@ -2198,30 +2198,22 @@ class RideLifecycleController extends GetxController {
Future<void> calculateDriverToPassengerRoute( Future<void> calculateDriverToPassengerRoute(
LatLng driverPos, LatLng passengerPos, LatLng driverPos, LatLng passengerPos,
{bool isBeginPhase = false}) async { {bool isBeginPhase = false}) async {
final Map<String, String> queryParams = {
'fromLat': driverPos.latitude.toString(),
'fromLng': driverPos.longitude.toString(),
'toLat': passengerPos.latitude.toString(),
'toLng': passengerPos.longitude.toString(),
};
final uri =
Uri.parse(AppLink.mapSaasRoute).replace(queryParameters: queryParams);
Log.print('📍 Calculating Driver Route: $uri'); if (mapController == null) {
Log.print('⚠️ mapController is null, cannot calculate route via intaleq_maps');
return;
}
Log.print('📍 Calculating Driver Route via IntaleqMapController...');
try { try {
final response = await http.get(uri, headers: { final responseData = await mapController!.getDirections(driverPos, passengerPos);
'x-api-key': Env.mapSaasKey,
}).timeout(const Duration(seconds: 20));
if (response.statusCode == 200) {
final responseData = json.decode(response.body);
var routeData = responseData['routes'] != null var routeData = responseData['routes'] != null
? responseData['routes'][0] ? responseData['routes'][0]
: responseData; : responseData;
double durationSecondsRaw = (routeData['duration'] as num).toDouble(); double durationSecondsRaw = (routeData['trafficAwareDuration'] ?? routeData['duration'] as num).toDouble();
int finalDurationSeconds = int finalDurationSeconds =
(durationSecondsRaw * kDurationScalar).toInt(); (durationSecondsRaw * kDurationScalar).toInt();
double distanceMeters = (routeData['distance'] as num).toDouble(); double distanceMeters = (routeData['distance'] as num).toDouble();
@@ -2299,7 +2291,6 @@ class RideLifecycleController extends GetxController {
mapEngine.fitCameraToPoints(driverPos, passengerPos); mapEngine.fitCameraToPoints(driverPos, passengerPos);
_updatePassengerWalkLine(); _updatePassengerWalkLine();
update(); update();
}
} catch (e) { } catch (e) {
Log.print('❌ Error calculating driver route: $e'); Log.print('❌ Error calculating driver route: $e');
} }