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
+9 -3
View File
@@ -73,9 +73,15 @@ try {
$newAwfarPrice = round($newSpeedPrice * 0.85, 3);
// أسعار الدقائق
$newNormalMin = round($newSpeedPrice / 4, 3);
$newPeakMin = round($newNormalMin * 1.15, 3);
$newLateMin = round($newNormalMin * 1.25, 3);
if ($country === 'JO') {
$newNormalMin = 0.06;
$newPeakMin = 0.07;
$newLateMin = 0.08;
} else {
$newNormalMin = round($newSpeedPrice / 4, 3);
$newPeakMin = round($newNormalMin * 1.15, 3);
$newLateMin = round($newNormalMin * 1.25, 3);
}
$updateSql = "UPDATE kazan
SET speedPrice = :speedPrice,
+15 -3
View File
@@ -85,16 +85,28 @@ foreach ($data as $row) {
}
$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;
$startLat = $resultData['start_lat'] ?? null;
$startLng = $resultData['start_lng'] ?? null;
$endLat = $resultData['end_lat'] ?? 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
if ($stmt->execute([
+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";
+37 -2
View File
@@ -94,12 +94,43 @@ $countriesConfig = [
['name' => 'Jabal Amman', 'lat' => 31.9511, 'lng' => 35.9189],
['name' => 'Sweileh', 'lat' => 32.0167, 'lng' => 35.8333],
['name' => 'Khalda', 'lat' => 31.9861, 'lng' => 35.8450],
['name' => 'Al-Jubaiha', 'lat' => 32.0194, 'lng' => 35.8753],
['name' => 'Al-Jubaiha', 'lat' => 32.0194, 'lng' => 35.8753],
['name' => 'Tla Al-Ali', 'lat' => 31.9961, 'lng' => 35.8647],
['name' => 'Shmeisani', 'lat' => 31.9680, 'lng' => 35.9020],
['name' => 'Um Uthaina', 'lat' => 31.9610, 'lng' => 35.8770],
['name' => 'Jabal Al-Weibdeh', 'lat' => 31.9560, 'lng' => 35.9220],
['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]
]
]
];
@@ -129,8 +160,12 @@ $tasksCreated = 0;
foreach ($countriesConfig as $countryCode => $config) {
$competitors = $config['competitors'];
$regions = $config['regions'];
// Randomly select 20 regions (or all if less than 20)
shuffle($regions);
$selectedRegions = array_slice($regions, 0, 20);
foreach ($regions as $region) {
foreach ($selectedRegions as $region) {
// A. Generate Start Point (within 2km of region center)
$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;
$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
$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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
(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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
");
$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]);
$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)
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;
}
+18 -3
View File
@@ -183,13 +183,28 @@ function calculateDynamicPrice($country, $minFare, $distance, $duration, $kazanR
$damascusAirportBoundAddon = 1400.0;
break;
}
$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;
$longDistThresholdKm = 35.0;
$longTripPerMin = 6.0;
$minuteCapMedium = 60;
$minuteCapLong = 80;
$freeMinutesLong = 10;
@@ -2198,32 +2198,24 @@ class RideLifecycleController extends GetxController {
Future<void> calculateDriverToPassengerRoute(
LatLng driverPos, LatLng passengerPos,
{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);
if (mapController == null) {
Log.print('⚠️ mapController is null, cannot calculate route via intaleq_maps');
return;
}
Log.print('📍 Calculating Driver Route: $uri');
Log.print('📍 Calculating Driver Route via IntaleqMapController...');
try {
final response = await http.get(uri, headers: {
'x-api-key': Env.mapSaasKey,
}).timeout(const Duration(seconds: 20));
final responseData = await mapController!.getDirections(driverPos, passengerPos);
if (response.statusCode == 200) {
final responseData = json.decode(response.body);
var routeData = responseData['routes'] != null
? responseData['routes'][0]
: responseData;
var routeData = responseData['routes'] != null
? responseData['routes'][0]
: responseData;
double durationSecondsRaw = (routeData['duration'] as num).toDouble();
int finalDurationSeconds =
(durationSecondsRaw * kDurationScalar).toInt();
double durationSecondsRaw = (routeData['trafficAwareDuration'] ?? routeData['duration'] as num).toDouble();
int finalDurationSeconds =
(durationSecondsRaw * kDurationScalar).toInt();
double distanceMeters = (routeData['distance'] as num).toDouble();
updateDriverRouteMetrics(
@@ -2299,7 +2291,6 @@ class RideLifecycleController extends GetxController {
mapEngine.fitCameraToPoints(driverPos, passengerPos);
_updatePassengerWalkLine();
update();
}
} catch (e) {
Log.print('❌ Error calculating driver route: $e');
}