Files
Siro/backend/core/Services/SiroGeminiService.php
T

419 lines
20 KiB
PHP

<?php
// ============================================================
// core/Services/SiroGeminiService.php
// Siro AI Market Analysis & Marketing Content Generation Service
// ============================================================
declare(strict_types=1);
class SiroGeminiService {
private ?string $apiKey;
private string $baseUrl;
public function __construct() {
$this->apiKey = getenv('GEMINI_API_KEY') ?: null;
$this->baseUrl = "https://generativelanguage.googleapis.com/v1beta/models/";
}
/**
* Analyze market prices and generate target promotion response
*
* @param array $competitorPrices Array of competitor pricing information
* @param float $siroBasePrice Current Siro base pricing for this region/country
* @param string $regionName Name of the region/city
* @param string $countryCode Country code (e.g. SY, JO, EG, IQ)
* @param string $model Override AI model to use (default: gemini-1.5-flash)
* @return array|null Decoded JSON response from Gemini or null on failure
*/
public function analyzeMarketAndDraftCampaign(
array $competitorPrices,
float $siroBasePrice,
string $regionName,
string $countryCode,
string $model = 'gemini-flash-lite-latest'
): ?array {
if (!$this->apiKey) {
error_log("[SiroGeminiService] API Key is missing.");
return null;
}
$dialect = match (strtoupper($countryCode)) {
'SY' => 'السورية (الشامية)',
'JO' => 'الأردنية',
'EG' => 'المصرية',
'IQ' => 'العراقية',
default => 'العربية الفصحى البسيطة'
};
$prompt = "
أنت خبير تسويق ذكي ومحلل أسعار لتطبيق Siro لخدمات نقل الركاب.
قم بتحليل أسعار المنافسين في منطقة '$regionName' وصياغة حملة تسويقية وعرض ترويجي منافس.
بيانات الإدخال:
1. أسعار المنافسين الحالية: " . json_encode($competitorPrices, JSON_UNESCAPED_UNICODE) . "
2. سعر رحلة Siro الأساسي الحالي: $siroBasePrice
المطلوب:
1. دراسة الأسعار وتحديد هل توجد فرصة تسويقية واضحة لجذب الركاب (opportunity_detected: true/false).
2. تحديد معامل التخفيض المقترح أو قيمة خصم مناسبة (discount_value) والنسبة المئوية (discount_percentage).
3. كتابة رسالة تسويقية إعلانية جذابة وقصيرة جداً ومقنعة لإرسالها كإشعار (Push Notification) للركاب النشطين بالهجة $dialect.
4. كتابة رسالة استعادة جذابة ومغرية وقصيرة لإرسالها عبر SMS أو WhatsApp للركاب المنقطعين بالهجة $dialect مع ذكر كود الخصم المقترح.
5. اقتراح كود خصم مناسب للحملة (promo_code) ليكون سهل الحفظ ومناسباً للحدث.
الخرج المطلوب (يجب أن يكون JSON صالحاً تماماً وخالياً من أي شرح خارجي، باللغة العربية):
{
\"opportunity_detected\": true/false,
\"recommended_price\": 12000,
\"discount_percentage\": 15,
\"promo_code\": \"SIROGO15\",
\"push_title\": \"عنوان الإشعار (بحد أقصى 5 كلمات)\",
\"push_body\": \"محتوى الإشعار القصير والمثير للاهتمام (بحد أقصى 15 كلمة)\",
\"sms_body\": \"محتوى رسالة الاستعادة (بحد أقصى 20 كلمة)\"
}
";
return $this->callGemini($prompt, $model);
}
/**
* يُولّد إشعاراً مخصصاً لحدث دخول الجيوفينس بناءً على بيانات أسعار حقيقية.
*
* @param string $zoneName اسم منطقة السياج الجغرافي
* @param string $countryCode رمز الدولة (SY, JO, EG, IQ)
* @param float $savingsPct نسبة التوفير (مثال: 8.5)
* @param string $topCompetitor اسم أبرز منافس في المنطقة
* @param string $model
* @return array|null
*/
public function generateGeofenceMessage(
string $zoneName,
string $countryCode,
float $savingsPct,
string $topCompetitor = 'كريم',
string $model = 'gemini-flash-lite-latest'
): ?array {
if (!$this->apiKey) return null;
$dialect = match (strtoupper($countryCode)) {
'SY' => 'السورية الشامية',
'JO' => 'الأردنية',
'EG' => 'المصرية العامية',
'IQ' => 'العراقية',
default => 'العربية الفصحى'
};
$savingsFormatted = number_format($savingsPct, 1);
$prompt = "
أنت كاتب إشعارات تسويقية ذكية لتطبيق Siro لخدمات نقل الركاب.
المستخدم الآن موجود بالقرب من '$zoneName'.
سعر سيرو أقل بـ $savingsFormatted% من $topCompetitor وبقية التطبيقات في هذه المنطقة.
المطلوب: اكتب إشعاراً push قصيراً جداً (عنوان + جسم) باللهجة $dialect.
- العنوان: لا يتجاوز 5 كلمات، مثير للاهتمام
- الجسم: لا يتجاوز 12 كلمة، يذكر التوفير الفعلي ويحفّز على الطلب الآن
- اجعله طبيعياً كأنه يكتبه شخص حقيقي وليس روبوت
الخرج (JSON فقط، بدون أي شرح):
{
\"push_title\": \"...\",
\"push_body\": \"...\"
}
";
return $this->callGemini($prompt, $model);
}
/**
* تقرأ معادلات المنافسين المكتشفة أسبوعياً وتقدم استراتيجية تسويق (Weekly Advisor).
*
* @param array $formulas المصفوفة المستخرجة من competitor_secret_formulas
* @param string $model
* @return array|null
*/
public function analyzeCompetitorFormulas(
array $formulas,
string $model = 'gemini-flash-lite-latest'
): ?array {
if (!$this->apiKey) return null;
$formulasJson = json_encode($formulas, JSON_UNESCAPED_UNICODE);
$prompt = "
أنت المستشار التسويقي المالي لتطبيق 'سيرو' لنقل الركاب.
لقد قمنا بعمل هندسة عكسية لرحلات منافسينا واكتشفنا معادلات التسعير السرية الخاصة بهم لهذا الأسبوع.
البيانات المكتشفة:
$formulasJson
المطلوب منك (كمستشار أعمال خبير):
1. تحليل هذه الأرقام، وتوضيح أين تكمن نقاط قوة المنافسين وأين نقاط ضعفهم في التسعير (مثلاً من يركز على المسافات القصيرة برفع فتح العداد؟).
2. اقتراح 3 استراتيجيات تسويقية أو رسائل إعلانية (Push Notifications/Social Media) موجهة للركاب، تستغل نقاط ضعف المنافسين المكتشفة.
قم بصياغة تقريرك بتنسيق HTML مرتب وجاهز للعرض في لوحة الإدارة (بدون علامات ```html)، واستخدم ألوان خفيفة في العناوين <h3>.
تحدث بلغة عربية احترافية ومباشرة لصناع القرار.
";
// بما أن الإخراج سيكون نص HTML، سنستخدم API جيميناي العادي بدون تقييد JSON
$url = $this->baseUrl . "{$model}:generateContent?key={$this->apiKey}";
$postData = [
'contents' => [
[
'parts' => [
['text' => $prompt]
]
]
],
'generationConfig' => [
'temperature' => 0.7,
'maxOutputTokens' => 2000
]
];
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($postData));
curl_setopt($ch, CURLOPT_TIMEOUT, 60);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode === 200) {
$data = json_decode($response, true);
$text = $data['candidates'][0]['content']['parts'][0]['text'] ?? '';
return ['status' => 'success', 'html_report' => $text];
} else {
error_log("[SiroGeminiService] HTTP $httpCode in analyzeCompetitorFormulas: $response");
return null;
}
}
/**
* Helper: يُرسل prompt لـ Gemini ويُعيد JSON مُفكَّك
*/
public function callGemini(string $prompt, string $model): ?array {
$apiUrl = $this->baseUrl . $model . ":generateContent?key=" . $this->apiKey;
$payload = [
'contents' => [
[
'parts' => [
['text' => $prompt]
]
]
]
];
$ch = curl_init($apiUrl);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode($payload),
CURLOPT_TIMEOUT => 30,
CURLOPT_CONNECTTIMEOUT => 5
]);
$response = curl_exec($ch);
$curlErr = curl_error($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($curlErr) {
error_log("[SiroGeminiService] Curl Error: $curlErr");
return null;
}
if ($httpCode !== 200) {
error_log("[SiroGeminiService] HTTP Error $httpCode. Response: $response");
return null;
}
try {
$responseData = json_decode($response, true);
$rawText = $responseData['candidates'][0]['content']['parts'][0]['text'] ?? '';
// تنظيف أي علامات كود ماركداون محتملة من الموديل
$cleanJson = trim(preg_replace('/```json|```/', '', $rawText));
$decoded = json_decode($cleanJson, true);
if (json_last_error() !== JSON_ERROR_NONE) {
error_log("[SiroGeminiService] JSON Decode Error: " . json_last_error_msg() . " | Raw text: $rawText");
return null;
}
return $decoded;
} catch (Exception $e) {
error_log("[SiroGeminiService] Exception: " . $e->getMessage());
return null;
}
}
public function evaluatePostsForReporting(array $posts, string $model = 'gemini-flash-lite-latest'): ?string {
if (!$this->apiKey || empty($posts)) return null;
$postsJson = json_encode($posts, JSON_UNESCAPED_UNICODE);
$prompt = "
أنت محلل بيانات استخباراتية للسوق لتطبيق 'سيرو' لنقل الركاب.
إليك مجموعة من المنشورات والتعليقات التي جمعها الروبوت الخاص بنا من مجموعات فيسبوك اليوم:
$postsJson
المطلوب منك:
1. قراءة جميع هذه المنشورات واستخراج أي شكاوى، أسئلة، أو نقاشات تتعلق بـ (تطبيقات النقل الذكي، أسعار المحروقات، باقات الإنترنت، مشاكل السيارات).
ملاحظة هامة عن بنية البيانات:
- أي نص يبدأ بـ [FEED]: هو عبارة عن المنشور الأصلي (البوست).
- أي نص يبدأ بـ [COMMENT]: هو تعليق تابع للمنشور الذي يسبقه مباشرة.
2. تلخيص أهم هذه النقاشات في تقرير قصير ومفيد (News Report). يرجى التمييز بين المنشور الأصلي والتعليقات عليه لفهم السياق.
3. تجاهل المنشورات العشوائية أو الشخصية التي لا تفيد السوق.
هام جداً:
- يجب أن يكون التقرير باللغة العربية بالكامل.
- يجب أن تغلف التقرير بالكامل بوسم <div dir=\"rtl\" align=\"right\"> في البداية و </div> في النهاية لضمان القراءة من اليمين لليسار.
- استخدم <h3> للعناوين و <ul> للقوائم داخل التقرير.
- لا تقم بتضمين علامات ```html في المخرجات.
إذا لم يكن هناك أي شيء مفيد، اكتب فقط: <div dir=\"rtl\" align=\"right\"><p>لا توجد بيانات مفيدة في هذه الدفعة.</p></div>
";
$url = $this->baseUrl . "{$model}:generateContent?key={$this->apiKey}";
$postData = [
'contents' => [
[
'parts' => [
['text' => $prompt]
]
]
],
'generationConfig' => [
'temperature' => 0.5,
'maxOutputTokens' => 1500
]
];
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($postData));
curl_setopt($ch, CURLOPT_TIMEOUT, 60);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode === 200) {
$data = json_decode($response, true);
$text = $data['candidates'][0]['content']['parts'][0]['text'] ?? '';
return trim(preg_replace('/```html|```/', '', $text));
}
return null;
}
public function evaluateTelegramForReporting(array $messages, string $model = 'gemini-flash-lite-latest'): ?string {
if (!$this->apiKey || empty($messages)) return null;
$messagesJson = json_encode($messages, JSON_UNESCAPED_UNICODE);
$prompt = "
أنت محلل بيانات استخباراتية للسوق لتطبيق 'سيرو' لنقل الركاب.
إليك مجموعة من الرسائل التي جمعها الروبوت الخاص بنا من قنوات ومجموعات السائقين على تليغرام اليوم:
$messagesJson
المطلوب منك:
1. قراءة جميع هذه الرسائل واستخراج أي شكاوى، أسئلة، أو نقاشات تتعلق بـ (تطبيقات النقل الذكي مثل كريم، أوبر، جيني، يلاغو، تكسي إف، أسعار المحروقات، مشاكل التطبيقات، الإضرابات).
ملاحظة هامة عن بنية البيانات:
- كل رسالة تبدأ بـ [TELEGRAM]: هي عبارة عن رسالة مرسلة في مجموعة تليغرام.
2. تلخيص أهم هذه النقاشات في تقرير قصير ومفيد (News Report).
3. تجاهل رسائل الانضمام للمجموعات، الملصقات، الإعلانات العشوائية، أو الرسائل القصيرة جداً التي لا تحتوي على معنى مفيد للسوق.
هام جداً:
- يجب أن يكون التقرير باللغة العربية بالكامل.
- يجب أن تغلف التقرير بالكامل بوسم <div dir=\"rtl\" align=\"right\"> في البداية و </div> في النهاية لضمان القراءة من اليمين لليسار.
- استخدم <h3> للعناوين و <ul> للقوائم داخل التقرير.
- لا تقم بتضمين علامات ```html في المخرجات.
إذا لم يكن هناك أي شيء مفيد، اكتب فقط: <div dir=\"rtl\" align=\"right\"><p>لا توجد بيانات مفيدة في هذه الدفعة.</p></div>
";
$url = $this->baseUrl . "{$model}:generateContent?key={$this->apiKey}";
$postData = [
'contents' => [
[
'parts' => [
['text' => $prompt]
]
]
],
'generationConfig' => [
'temperature' => 0.5,
'maxOutputTokens' => 1500
]
];
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($postData));
curl_setopt($ch, CURLOPT_TIMEOUT, 60);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode !== 200) {
echo "\n[GEMINI API ERROR] HTTP Code: $httpCode\n";
echo "Response: $response\n\n";
return null;
}
$text = $data['candidates'][0]['content']['parts'][0]['text'] ?? '';
return trim(preg_replace('/```html|```/', '', $text));
}
/**
* Answers a driver support inquiry acting as Siro's smart assistant.
*/
public function answerSupportInquiry(
string $question,
array $driverContext,
string $model = 'gemini-flash-lite-latest'
): ?string {
if (!$this->apiKey) return null;
$contextJson = json_encode($driverContext, JSON_UNESCAPED_UNICODE);
$prompt = "
أنت مساعد الدعم الفني الذكي لتطبيق 'سيرو' (Siro) لنقل الركاب.
وظيفتك مساعدة السائقين (الكباتن) والإجابة على استفساراتهم باحترافية، ودقة، وبلهجة ودية ومهذبة جداً.
بيانات السائق الحالية (سياق مهم للإجابة على سؤاله):
$contextJson
سؤال السائق:
\"$question\"
المطلوب منك:
1. الإجابة مباشرة على سؤال السائق بناءً على السياق المعطى إذا كان يخص رحلاته أو رصيده المذكور في السياق.
2. إذا سأل عن إجراءات عامة، أجب كخبير في سيرو.
3. اجعل الإجابة ودية وواضحة جداً وقصيرة.
4. الرد يكون باللغة العربية حصراً.
5. لا تذكر تفاصيل تقنية (مثل json أو database)، بل تحدث كإنسان يقدم الدعم.
";
$prompt .= "\n\nالرجاء إرجاع الإجابة بصيغة JSON فقط كالتالي:\n{\n \"reply\": \"نص الإجابة هنا\"\n}";
$response = $this->callGemini($prompt, $model);
if ($response && isset($response['reply'])) {
return $response['reply'];
}
return null;
}
}
}