Update codebase
This commit is contained in:
@@ -0,0 +1,155 @@
|
||||
<?php
|
||||
/**
|
||||
* sms/parser.php — تحليل نص الرسالة إلى نقطتين
|
||||
* ─────────────────────────────────────────────────────────────
|
||||
* الراكب يكتب بلغته لا بصيغة نموذج. لا يمكن فرض قالب صارم على من
|
||||
* لجأ إلى الرسائل أصلاً لأنه لا يستطيع استعمال التطبيق — فالمحلّل
|
||||
* يقبل ما يقبله المنطق، ويرد بسؤال واضح حين يعجز.
|
||||
*
|
||||
* الصيغ المدعومة:
|
||||
* من الجامعة الاردنية الى المستشفى التخصصي
|
||||
* من الجامعة للمستشفى
|
||||
* الجامعة - المستشفى
|
||||
* الجامعة > المستشفى
|
||||
* من 31.95,35.91 الى المستشفى (إحداثيات مباشرة)
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/helpers.php';
|
||||
|
||||
/**
|
||||
* هل هذه الرسالة طلب رحلة أصلاً؟
|
||||
*
|
||||
* الويبهوك نفسه يستقبل إشعارات الدفع من البنوك، فلا بد من فرز قبل
|
||||
* التحليل. طلب الرحلة يحوي فاصلاً بين نقطتين؛ إشعار الدفع يحوي مبلغاً
|
||||
* وكلمات مالية.
|
||||
*/
|
||||
function smsLooksLikeRideRequest(string $body): bool
|
||||
{
|
||||
$b = smsNormalizeArabic($body);
|
||||
|
||||
// كلمات الإشعارات المالية — وجودها يرجّح أنها ليست طلب رحلة.
|
||||
if (preg_match('/(حواله|حوالة|رصيد|محفظتك|دينار|جنيه|ليره|مزود الخدمه|Orange Money)/ui', $b)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// الأنماط على النص الموحَّد: «الى/إلى» صارتا «الي» بعد التوحيد.
|
||||
return (bool) preg_match('/(^|\s)من\s+.+\s+(الي|علي|ل)\s*\S/u', $b)
|
||||
|| (bool) preg_match('/\S\s+[-]\s+\S/u', $b)
|
||||
|| (bool) preg_match('/\S\s*[<>→]\s*\S/u', $b);
|
||||
}
|
||||
|
||||
/**
|
||||
* يستخرج نصّي النقطتين. لا يلمس قاعدة البيانات — الفصل يجعل اختباره
|
||||
* ممكناً بلا بيئة كاملة، وهذا المكوّن هو الأكثر عرضة للتعديل.
|
||||
*
|
||||
* @return array{start:string,end:string}|null
|
||||
*/
|
||||
function smsExtractEndpoints(string $body): ?array
|
||||
{
|
||||
$b = smsNormalizeArabic($body);
|
||||
|
||||
// ١) «من … إلى …» — الصيغة الأوضح، ونجرّبها أولاً.
|
||||
//
|
||||
// تنبيه: النص هنا **بعد** التوحيد، والتوحيد يحوّل ى إلى ي —
|
||||
// فـ«الى» و«إلى» كلتاهما صارتا «الي». مطابقة «الى» الأصلية هنا
|
||||
// تفشل دائماً، وهو ما كان يُسقط أشيع صيغة يكتبها الناس.
|
||||
//
|
||||
// و«ل» الملتصقة تُعالَج على حدة: «للمستشفى» = ل + المستشفى،
|
||||
// فابتلاع اللامين معاً يترك «مستشفى» بلا تعريف ويُفشل المطابقة.
|
||||
if (preg_match('/من\s+(.+?)\s+(الي|علي|ل)\s*(.+)$/u', $b, $m)) {
|
||||
$start = trim($m[1]);
|
||||
$end = trim($m[3]);
|
||||
|
||||
// «لل…» وصلت مقسومة: الفاصل «ل» والباقي يبدأ بـ«ل». نعيد
|
||||
// بناء أداة التعريف بدل تركها مبتورة.
|
||||
if ($m[2] === 'ل' && str_starts_with($end, 'ل')) {
|
||||
$end = 'ا' . $end;
|
||||
}
|
||||
|
||||
// «… لل الصويفية» بمسافة: يبقى «ال» معلّقاً وحده فنسقطه.
|
||||
$end = preg_replace('/^ال\s+/u', '', $end) ?? $end;
|
||||
|
||||
if ($start !== '' && $end !== '') {
|
||||
return ['start' => $start, 'end' => $end];
|
||||
}
|
||||
}
|
||||
|
||||
// ٢) فاصل رمزي: «الجامعة - المستشفى».
|
||||
// ننتبه ألا نقطع إحداثيات سالبة أو تاريخاً — لذلك نشترط مسافة
|
||||
// حول الشرطة، بخلاف < و > اللذين لا يلتبسان بشيء.
|
||||
if (preg_match('/^(.+?)\s+[-]\s+(.+)$/u', $b, $m)
|
||||
|| preg_match('/^(.+?)\s*[<>→]\s*(.+)$/u', $b, $m)) {
|
||||
$start = trim($m[1]);
|
||||
$end = trim($m[2]);
|
||||
if ($start !== '' && $end !== '') {
|
||||
return ['start' => $start, 'end' => $end];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* يحوّل نصاً واحداً إلى نقطة: إمّا إحداثيات مكتوبة صراحةً، أو معلم
|
||||
* نبحث عنه في جدول المعالم.
|
||||
*
|
||||
* @return array{lat:float,lng:float,name:string}|null
|
||||
*/
|
||||
function smsResolvePoint(PDO $con, string $text): ?array
|
||||
{
|
||||
$t = trim($text);
|
||||
|
||||
// إحداثيات صريحة — يرسلها من نسخ موقعه من تطبيق آخر.
|
||||
if (preg_match('/^(-?\d{1,3}\.\d+)\s*,\s*(-?\d{1,3}\.\d+)$/', $t, $m)) {
|
||||
$lat = (float) $m[1];
|
||||
$lng = (float) $m[2];
|
||||
if ($lat >= -90 && $lat <= 90 && $lng >= -180 && $lng <= 180) {
|
||||
return ['lat' => $lat, 'lng' => $lng, 'name' => $t];
|
||||
}
|
||||
}
|
||||
|
||||
// كلمات حشو شائعة في بداية اسم المكان — إزالتها ترفع نسبة المطابقة.
|
||||
$t = preg_replace('/^(هنا|موقعي|عند|قرب|جنب|حد|في)\s+/ui', '', $t) ?? $t;
|
||||
|
||||
return smsFindPlace($con, $t);
|
||||
}
|
||||
|
||||
/**
|
||||
* التحليل الكامل: نص خام ← نقطتان محلولتان.
|
||||
*
|
||||
* @return array{
|
||||
* ok: bool, reason?: string,
|
||||
* raw_start?: string, raw_end?: string,
|
||||
* start?: array, end?: array
|
||||
* }
|
||||
*/
|
||||
function smsParseRideRequest(PDO $con, string $body): array
|
||||
{
|
||||
$ends = smsExtractEndpoints($body);
|
||||
if (!$ends) {
|
||||
return ['ok' => false, 'reason' => 'format'];
|
||||
}
|
||||
|
||||
$start = smsResolvePoint($con, $ends['start']);
|
||||
$end = smsResolvePoint($con, $ends['end']);
|
||||
|
||||
// نفرّق بين «لم أفهم الصيغة» و«لم أجد المكان»: الرسالتان الراجعتان
|
||||
// للراكب مختلفتان تماماً، والخلط بينهما يجعله يعيد نفس الخطأ.
|
||||
if (!$start || !$end) {
|
||||
return [
|
||||
'ok' => false,
|
||||
'reason' => 'place',
|
||||
'raw_start' => $ends['start'],
|
||||
'raw_end' => $ends['end'],
|
||||
'missing' => !$start ? $ends['start'] : $ends['end'],
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'ok' => true,
|
||||
'raw_start' => $ends['start'],
|
||||
'raw_end' => $ends['end'],
|
||||
'start' => $start,
|
||||
'end' => $end,
|
||||
];
|
||||
}
|
||||
Reference in New Issue
Block a user