نسخة كاملة من مستودع سيرو عند ecfe7568 لتكون أساس تطبيق «انطلق». نُسخ المتعقَّب في git فقط (12,509 ملفاً / 302 م.ب) بـ git archive، لا `cp -r` — فاستُثنيت تلقائياً مخلفات البناء (build · node_modules · .dart_tool · .gradle · Pods ≈ 10.7 غ.ب) وكل ما يستثنيه .gitignore. هذا الكوميت **بلا أي تعديل عمداً** حتى يكون كل ما يليه فرقاً مقروءاً مقابل سيرو الأصلي. سيرو نفسه لم يُمسّ. ⚠️ لا يبني بعد: `.env` و`lib/env/env.g.dart` غير متعقَّبين في سيرو (وهذا صحيح — أسرار لكل مستأجر). كل تطبيق فلاتر هنا يحتاج .env خاصاً بانطلق ثم توليد env.g.dart عبر build_runner. لا تُنسخ أسرار سيرو. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
177 lines
5.7 KiB
PHP
177 lines
5.7 KiB
PHP
<?php
|
|
/**
|
|
* ai_formula_solver.php
|
|
* مكتشف خوارزميات المنافسين (AI Competitor Formula Solver)
|
|
* يستخدم الانحدار الخطي المتعدد (Multiple Linear Regression) لاكتشاف
|
|
* أجرة فتح العداد، وسعر الكيلومتر، وسعر الدقيقة لكل تطبيق منافس.
|
|
*/
|
|
|
|
require_once __DIR__ . '/../core/bootstrap.php';
|
|
require_once __DIR__ . '/../functions.php';
|
|
|
|
try {
|
|
$con = Database::get('main');
|
|
} catch (Exception $e) {
|
|
die("Database connection failed: " . $e->getMessage() . "\n");
|
|
}
|
|
|
|
echo "Starting AI Formula Discovery Engine...\n";
|
|
|
|
// نجلب التطبيقات التي لديها بيانات (مسافة ووقت وسعر)
|
|
$sqlApps = "SELECT DISTINCT competitor_name, country_code
|
|
FROM scraped_competitor_prices
|
|
WHERE distance_km > 0 AND duration_min > 0 AND price_amount > 0";
|
|
$stmtApps = $con->query($sqlApps);
|
|
$apps = $stmtApps->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
if (empty($apps)) {
|
|
echo "No sufficient data (Distance/Duration) found to perform regression.\n";
|
|
exit;
|
|
}
|
|
|
|
// دالة لحل نظام معادلات خطية (Gaussian Elimination)
|
|
function solveLinearSystem($A, $B) {
|
|
$n = count($A);
|
|
for ($i = 0; $i < $n; $i++) {
|
|
// Search for maximum in this column
|
|
$maxEl = abs($A[$i][$i]);
|
|
$maxRow = $i;
|
|
for ($k = $i + 1; $k < $n; $k++) {
|
|
if (abs($A[$k][$i]) > $maxEl) {
|
|
$maxEl = abs($A[$k][$i]);
|
|
$maxRow = $k;
|
|
}
|
|
}
|
|
|
|
// Swap maximum row with current row
|
|
for ($k = $i; $k < $n; $k++) {
|
|
$tmp = $A[$maxRow][$k];
|
|
$A[$maxRow][$k] = $A[$i][$k];
|
|
$A[$i][$k] = $tmp;
|
|
}
|
|
$tmp = $B[$maxRow];
|
|
$B[$maxRow] = $B[$i];
|
|
$B[$i] = $tmp;
|
|
|
|
// Make all rows below this one 0 in current column
|
|
for ($k = $i + 1; $k < $n; $k++) {
|
|
if ($A[$i][$i] == 0) continue;
|
|
$c = -$A[$k][$i] / $A[$i][$i];
|
|
for ($j = $i; $j < $n; $j++) {
|
|
if ($i == $j) {
|
|
$A[$k][$j] = 0;
|
|
} else {
|
|
$A[$k][$j] += $c * $A[$i][$j];
|
|
}
|
|
}
|
|
$B[$k] += $c * $B[$i];
|
|
}
|
|
}
|
|
|
|
// Solve equation Ax=b for an upper triangular matrix A
|
|
$x = array_fill(0, $n, 0);
|
|
for ($i = $n - 1; $i >= 0; $i--) {
|
|
if ($A[$i][$i] == 0) continue;
|
|
$x[$i] = $B[$i] / $A[$i][$i];
|
|
for ($k = $i - 1; $k >= 0; $k--) {
|
|
$B[$k] -= $A[$k][$i] * $x[$i];
|
|
}
|
|
}
|
|
return $x;
|
|
}
|
|
|
|
foreach ($apps as $app) {
|
|
$competitor = $app['competitor_name'];
|
|
$countryCode = $app['country_code'];
|
|
|
|
echo "Analyzing: $competitor ($countryCode)...\n";
|
|
|
|
// سحب أحدث 5000 رحلة لتكوين نموذج رياضي دقيق
|
|
$sqlData = "SELECT distance_km, duration_min, price_amount
|
|
FROM scraped_competitor_prices
|
|
WHERE competitor_name = :comp
|
|
AND country_code = :country
|
|
AND distance_km > 0 AND duration_min > 0 AND price_amount > 0
|
|
ORDER BY id DESC LIMIT 5000";
|
|
$stmtData = $con->prepare($sqlData);
|
|
$stmtData->execute([':comp' => $competitor, ':country' => $countryCode]);
|
|
$samples = $stmtData->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
$N = count($samples);
|
|
if ($N < 10) {
|
|
echo " -> Not enough samples ($N). Skipping.\n";
|
|
continue;
|
|
}
|
|
|
|
// بناء مصفوفات Least Squares (X^T X) * Beta = (X^T Y)
|
|
// Beta = [Base_Fare, KM_Price, Min_Price]
|
|
$sum_x1 = 0; $sum_x2 = 0; $sum_y = 0;
|
|
$sum_x1_sq = 0; $sum_x2_sq = 0; $sum_x1_x2 = 0;
|
|
$sum_x1_y = 0; $sum_x2_y = 0;
|
|
|
|
foreach ($samples as $s) {
|
|
$x1 = (float)$s['distance_km'];
|
|
$x2 = (float)$s['duration_min'];
|
|
$y = (float)$s['price_amount'];
|
|
|
|
$sum_x1 += $x1;
|
|
$sum_x2 += $x2;
|
|
$sum_y += $y;
|
|
|
|
$sum_x1_sq += ($x1 * $x1);
|
|
$sum_x2_sq += ($x2 * $x2);
|
|
$sum_x1_x2 += ($x1 * $x2);
|
|
|
|
$sum_x1_y += ($x1 * $y);
|
|
$sum_x2_y += ($x2 * $y);
|
|
}
|
|
|
|
$matrixA = [
|
|
[$N, $sum_x1, $sum_x2],
|
|
[$sum_x1, $sum_x1_sq, $sum_x1_x2],
|
|
[$sum_x2, $sum_x1_x2, $sum_x2_sq]
|
|
];
|
|
|
|
$matrixB = [
|
|
$sum_y,
|
|
$sum_x1_y,
|
|
$sum_x2_y
|
|
];
|
|
|
|
// حل المصفوفة
|
|
try {
|
|
$beta = solveLinearSystem($matrixA, $matrixB);
|
|
|
|
$baseFare = round(max(0, $beta[0]), 3); // Base fare cannot be negative
|
|
$kmPrice = round(max(0, $beta[1]), 3);
|
|
$minPrice = round(max(0, $beta[2]), 3);
|
|
|
|
echo " -> [DISCOVERED] Base Fare: $baseFare, KM: $kmPrice, Min: $minPrice\n";
|
|
|
|
// حفظ في جدول المعادلات السرية
|
|
$sqlUpsert = "INSERT INTO competitor_secret_formulas
|
|
(competitor_name, country_code, base_fare, price_per_km, price_per_min, sample_size)
|
|
VALUES (:comp, :country, :base, :km, :min, :size)
|
|
ON DUPLICATE KEY UPDATE
|
|
base_fare = :base, price_per_km = :km, price_per_min = :min, sample_size = :size, last_updated = NOW()";
|
|
|
|
$stmtUp = $con->prepare($sqlUpsert);
|
|
$stmtUp->execute([
|
|
':comp' => $competitor,
|
|
':country' => $countryCode,
|
|
':base' => $baseFare,
|
|
':km' => $kmPrice,
|
|
':min' => $minPrice,
|
|
':size' => $N
|
|
]);
|
|
|
|
echo " -> Saved successfully.\n";
|
|
|
|
} catch (Exception $e) {
|
|
echo " -> Error solving matrix: " . $e->getMessage() . "\n";
|
|
}
|
|
}
|
|
|
|
echo "Done.\n";
|
|
?>
|