Files
Siro/backend/sms/parser.php
T

156 lines
6.5 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?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,
];
}