mirror of
https://github.com/x1xhlol/system-prompts-and-models-of-ai-tools.git
synced 2026-06-17 23:09:35 +00:00
Module 1 — Email sender enhanced: - HTML wrapper with Arabic RTL support - List-Unsubscribe header for compliance - send_email_batch() with configurable delays (2s between each) - Gmail app password auth error message - Plain text + HTML multipart - Unsubscribe line auto-appended (ar/en) Module 2 — Bilingual email generation: - language field added to EmailGenerateRequest (ar/en) - _detect_language() auto-detects from website domain - _generate_email_en() produces full English email set (subject, body, 2 follow-ups, call script, LinkedIn msg) - Arabic remains default for Saudi domains - SECTOR_PAIN_MAP_EN for 4 key sectors Module 3 — Config fixes: - OLLAMA_BASE_URL + OLLAMA_MODEL (were referenced but missing) - LLM_CACHE_ENABLED + LLM_CACHE_TTL - GREEN_API_INSTANCE_ID + GREEN_API_TOKEN - Outreach rate limits: WHATSAPP_DAILY_LIMIT=15, EMAIL_DAILY_LIMIT=50, EMAIL_BATCH_SIZE=10 All 40 tests pass (20 D0 + 6 fault + 14 automation). https://claude.ai/code/session_01W1rJthWDkasijTdXCfxVHs
538 lines
23 KiB
Python
538 lines
23 KiB
Python
"""Daily Targeting Automation — generates personalized outreach queue.
|
||
|
||
Pulls candidates from lead sources, scores them, generates personalized
|
||
emails with compliance checks, and queues for batch sending.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import logging
|
||
import re
|
||
from datetime import datetime, timezone
|
||
from typing import Any, Dict, List, Optional
|
||
from uuid import uuid4
|
||
|
||
from fastapi import APIRouter, HTTPException
|
||
from pydantic import BaseModel
|
||
|
||
logger = logging.getLogger("dealix.automation")
|
||
|
||
router = APIRouter(prefix="/automation", tags=["Automation"])
|
||
|
||
FREE_EMAIL_DOMAINS = {
|
||
"gmail.com", "yahoo.com", "hotmail.com", "outlook.com",
|
||
"icloud.com", "mail.com", "protonmail.com", "yandex.com",
|
||
"aol.com", "live.com", "msn.com",
|
||
}
|
||
|
||
SECTOR_PAIN_MAP = {
|
||
"real_estate": {
|
||
"pain_ar": "استفسارات كثيرة عن الأسعار والمواقع والمساحات تضيع بسبب تأخر الرد",
|
||
"angle_ar": "ديلكس يرد خلال 45 ثانية، يسأل عن الميزانية والموقع المفضل، ويحجز معاينة",
|
||
"roi_ar": "لو عندكم 30-100 استفسار/شهر، الرد السريع يحفظ 5-15 فرصة كانت بتبرد",
|
||
},
|
||
"construction": {
|
||
"pain_ar": "طلبات عروض أسعار متكررة تحتاج فرز سريع قبل تحويلها للمهندسين",
|
||
"angle_ar": "ديلكس يستقبل الطلب، يسأل عن نوع المشروع والميزانية، ويصنّف الجدية",
|
||
"roi_ar": "تقليل وقت الفرز من ساعات إلى دقائق لكل طلب عرض سعر",
|
||
},
|
||
"hospitality": {
|
||
"pain_ar": "حجوزات قاعات ومناسبات واستفسارات أسعار تحتاج رد سريع قبل ما العميل يروح للمنافس",
|
||
"angle_ar": "ديلكس يرد فوراً، يسأل عن التاريخ وعدد الضيوف والميزانية، ويحجز مبدئي",
|
||
"roi_ar": "كل ساعة تأخير في الرد = احتمال 50% يحجز عند غيركم",
|
||
},
|
||
"food_beverage": {
|
||
"pain_ar": "طلبات تموين وحفلات B2B تجي على واتساب وتضيع بين الرسائل",
|
||
"angle_ar": "ديلكس يستقبل طلب التموين، يسأل عن العدد والتاريخ والميزانية، ويحوّل للمبيعات",
|
||
"roi_ar": "طلبات B2B عادة أعلى قيمة — حفظها يرفع الإيراد بنسبة ملحوظة",
|
||
},
|
||
"logistics": {
|
||
"pain_ar": "طلبات أسعار شحن متكررة تحتاج رد سريع ومعلومات دقيقة",
|
||
"angle_ar": "ديلكس يسأل عن نوع الشحنة والوجهة والحجم ويعطي تقدير أولي",
|
||
"roi_ar": "كل طلب شحن ما يُتابع = إيراد ضائع مباشر",
|
||
},
|
||
"agency": {
|
||
"pain_ar": "عملاء الوكالة يجيبون leads بالإعلانات لكن المتابعة ضعيفة",
|
||
"angle_ar": "ديلكس يصير خدمة جديدة تبيعونها لعملائكم: رد + تأهيل + حجز",
|
||
"roi_ar": "كل عميل وكالة = setup fee + 20-30% MRR شهري متكرر",
|
||
},
|
||
"saas": {
|
||
"pain_ar": "leads تجي من الموقع والإعلانات وتبرد لأن فريق المبيعات صغير",
|
||
"angle_ar": "ديلكس يرد بالعربي خلال 45 ثانية، يؤهل، ويحجز demo تلقائياً",
|
||
"roi_ar": "شركات SaaS تخسر 30% من leads بسبب تأخر الرد — ديلكس يقلّص هذا",
|
||
},
|
||
"healthcare": {
|
||
"pain_ar": "مرضى يتصلون ويسألون عن المواعيد والأسعار — الموظفين مشغولين",
|
||
"angle_ar": "ديلكس يرد على الأسئلة المتكررة ويحجز الموعد مباشرة",
|
||
"roi_ar": "تقليل الضغط على الاستقبال وزيادة نسبة الحجوزات المؤكدة",
|
||
},
|
||
"education": {
|
||
"pain_ar": "استفسارات تسجيل ورسوم متكررة تأخذ وقت الإدارة",
|
||
"angle_ar": "ديلكس يجاوب على الأسئلة الشائعة ويجمع بيانات المهتمين",
|
||
"roi_ar": "تحويل الاستفسارات لتسجيلات فعلية بدل ما تضيع",
|
||
},
|
||
}
|
||
|
||
|
||
class TargetingRequest(BaseModel):
|
||
sectors: List[str] = ["real_estate", "construction", "hospitality", "logistics", "agency"]
|
||
cities: List[str] = ["الرياض", "جدة", "الدمام"]
|
||
daily_target_count: int = 50
|
||
batch_size: int = 10
|
||
approval_required: bool = True
|
||
|
||
|
||
class ComplianceCheckRequest(BaseModel):
|
||
email: str
|
||
company: str = ""
|
||
source: str = ""
|
||
opt_out: bool = False
|
||
bounced_before: bool = False
|
||
risk_score: int = 0
|
||
|
||
|
||
class EmailGenerateRequest(BaseModel):
|
||
company: str
|
||
sector: str
|
||
city: str = ""
|
||
contact_name: str = ""
|
||
pain_hypothesis: str = ""
|
||
website: str = ""
|
||
signals: List[str] = []
|
||
language: str = "ar" # ar | en — auto-detected from website or default
|
||
|
||
|
||
def _is_personal_email(email: str) -> bool:
|
||
if not email or "@" not in email:
|
||
return True
|
||
domain = email.split("@")[1].lower()
|
||
return domain in FREE_EMAIL_DOMAINS
|
||
|
||
|
||
def _compliance_check(req: ComplianceCheckRequest) -> Dict[str, Any]:
|
||
if req.opt_out:
|
||
return {"allowed": False, "reason": "opt_out", "action": "suppress"}
|
||
if req.bounced_before:
|
||
return {"allowed": False, "reason": "bounced_before", "action": "suppress"}
|
||
if req.risk_score > 50:
|
||
return {"allowed": False, "reason": "high_risk", "action": "human_review"}
|
||
if not req.email or "@" not in req.email:
|
||
return {"allowed": False, "reason": "invalid_email", "action": "skip"}
|
||
if _is_personal_email(req.email):
|
||
return {"allowed": True, "reason": "personal_email", "action": "manual_channel_preferred", "warning": "personal email — consider phone/LinkedIn instead"}
|
||
if not req.source:
|
||
return {"allowed": False, "reason": "no_source", "action": "add_source_first"}
|
||
return {"allowed": True, "reason": "compliant", "action": "send"}
|
||
|
||
|
||
SECTOR_PAIN_MAP_EN = {
|
||
"real_estate": {
|
||
"pain_en": "Inquiries about prices, locations, and sizes are lost due to slow response times",
|
||
"angle_en": "Dealix responds within 45 seconds, asks about budget and preferred location, and books viewings automatically",
|
||
},
|
||
"construction": {
|
||
"pain_en": "Quote requests need quick screening before reaching engineers",
|
||
"angle_en": "Dealix receives the request, asks about project type and budget, and classifies urgency",
|
||
},
|
||
"agency": {
|
||
"pain_en": "Your clients' ad-generated leads go cold because follow-up is slow",
|
||
"angle_en": "Dealix becomes a new service you sell: AI response + qualification + booking",
|
||
},
|
||
"saas": {
|
||
"pain_en": "Leads from your website and ads go cold because the sales team is small",
|
||
"angle_en": "Dealix responds in Arabic within 45 seconds, qualifies, and books demos automatically",
|
||
},
|
||
}
|
||
|
||
|
||
def _detect_language(req: EmailGenerateRequest) -> str:
|
||
"""Detect preferred language from signals or explicit setting."""
|
||
if req.language and req.language in ("ar", "en"):
|
||
return req.language
|
||
if req.website:
|
||
domain = req.website.lower()
|
||
if any(d in domain for d in [".sa", ".com.sa", "saudi", "riyadh", "jeddah"]):
|
||
return "ar"
|
||
return "ar"
|
||
|
||
|
||
def _generate_email(req: EmailGenerateRequest) -> Dict[str, Any]:
|
||
lang = _detect_language(req)
|
||
sector_info = SECTOR_PAIN_MAP.get(req.sector, SECTOR_PAIN_MAP.get("saas", {}))
|
||
sector_info_en = SECTOR_PAIN_MAP_EN.get(req.sector, SECTOR_PAIN_MAP_EN.get("saas", {}))
|
||
|
||
if lang == "en":
|
||
return _generate_email_en(req, sector_info_en)
|
||
|
||
pain = req.pain_hypothesis or sector_info.get("pain_ar", "تأخر الرد على العملاء المحتملين")
|
||
angle = sector_info.get("angle_ar", "ديلكس يرد بالعربي خلال 45 ثانية ويؤهل العميل")
|
||
roi = sector_info.get("roi_ar", "الرد السريع يحفظ فرص كانت بتضيع")
|
||
|
||
name_greeting = f"فريق {req.company}" if not req.contact_name else req.contact_name
|
||
city_mention = f" في {req.city}" if req.city else ""
|
||
|
||
signal_line = ""
|
||
if "hubspot" in [s.lower() for s in req.signals]:
|
||
signal_line = f"لاحظت إن {req.company} تستخدمون HubSpot — "
|
||
elif "whatsapp_widget" in [s.lower() for s in req.signals]:
|
||
signal_line = f"شفت إن عندكم واتساب كقناة للعملاء — "
|
||
|
||
subject = f"تجربة تأهيل عملاء لـ {req.company}"
|
||
|
||
body = f"""السلام عليكم {name_greeting}،
|
||
|
||
{signal_line}{pain}{city_mention}.
|
||
|
||
أنا سامي من Dealix. {angle}.
|
||
|
||
{roi}.
|
||
|
||
نقدم تجربة 7 أيام على 10–25 lead مع تقرير يومي.
|
||
سعر الإطلاق لأول عملاء: 499 ريال.
|
||
|
||
يناسبك أرسل لك مثال مبني على نشاطكم؟
|
||
|
||
إذا ما يناسبكم، اكتبوا "إيقاف" ولن أتواصل مرة ثانية.
|
||
|
||
سامي العسيري
|
||
Dealix — مندوب مبيعات ذكي بالعربي
|
||
dealix.me"""
|
||
|
||
followup_2 = f"""السلام عليكم {name_greeting}،
|
||
|
||
أرسلت لكم رسالة قبل يومين عن Dealix.
|
||
|
||
باختصار: نجرب 7 أيام على leads عندكم — رد سريع + تأهيل + تقرير يومي.
|
||
|
||
يناسبك 10 دقائق هذا الأسبوع؟
|
||
calendly.com/sami-assiri11/dealix-demo
|
||
|
||
إذا ما يناسبكم، اكتبوا "إيقاف".
|
||
|
||
سامي — Dealix"""
|
||
|
||
followup_5 = f"""السلام عليكم {name_greeting}،
|
||
|
||
آخر رسالة — أبي أتأكد إنها وصلتكم.
|
||
|
||
Dealix يساعد شركات {req.sector} ترد على الاستفسارات بسرعة وتحول الجاد منها للمبيعات.
|
||
|
||
لو مناسب نتكلم، أنا متاح. لو لا، شكرًا على وقتكم ولن أتواصل مرة ثانية.
|
||
|
||
سامي — Dealix"""
|
||
|
||
call_script = f"""مرحبا، أنا سامي من Dealix.
|
||
|
||
أتصل لأن شركات في قطاع {req.sector} عادةً تستقبل استفسارات كثيرة وتضيع بعضها بسبب تأخر الرد.
|
||
|
||
Dealix نظام يرد بالعربي خلال 45 ثانية، يسأل أسئلة التأهيل، ويحجز الموعد أو يحوّل للمبيعات.
|
||
|
||
عندنا تجربة 7 أيام بـ 499 ريال.
|
||
|
||
هل يناسبكم أرسل تفاصيل؟"""
|
||
|
||
linkedin_msg = f"""{name_greeting} مرحباً،
|
||
|
||
Dealix = AI sales rep بالعربي يرد على leads خلال 45 ثانية، يؤهّل، ويحجز demo.
|
||
|
||
20 دقيقة demo نشوف مناسبته لـ {req.company}؟
|
||
calendly.com/sami-assiri11/dealix-demo
|
||
|
||
سامي — Dealix"""
|
||
|
||
return {
|
||
"company": req.company,
|
||
"sector": req.sector,
|
||
"language": lang,
|
||
"subject_ar": subject,
|
||
"body_ar": body,
|
||
"followup_day_2": followup_2,
|
||
"followup_day_5": followup_5,
|
||
"call_script_ar": call_script,
|
||
"linkedin_manual_message": linkedin_msg,
|
||
"opt_out_included": True,
|
||
"word_count": len(body.split()),
|
||
"generated_at": datetime.now(timezone.utc).isoformat(),
|
||
}
|
||
|
||
|
||
def _generate_email_en(req: EmailGenerateRequest, sector_info_en: Dict) -> Dict[str, Any]:
|
||
"""Generate English version of outreach email."""
|
||
pain = sector_info_en.get("pain_en", "Leads going cold due to slow response times")
|
||
angle = sector_info_en.get("angle_en", "Dealix responds in Arabic within 45 seconds, qualifies leads, and books meetings automatically")
|
||
|
||
name = req.contact_name or f"{req.company} team"
|
||
signal_line = ""
|
||
if "hubspot" in [s.lower() for s in req.signals]:
|
||
signal_line = f"I noticed {req.company} uses HubSpot — "
|
||
elif "whatsapp_widget" in [s.lower() for s in req.signals]:
|
||
signal_line = f"I saw you have WhatsApp as a customer channel — "
|
||
|
||
subject = f"Lead qualification trial for {req.company}"
|
||
body = f"""Hi {name},
|
||
|
||
{signal_line}{pain}.
|
||
|
||
I'm Sami from Dealix. {angle}.
|
||
|
||
We offer a 7-day trial on 10-25 of your leads with daily reporting.
|
||
Launch price: 499 SAR.
|
||
|
||
Would you like me to show you an example based on your business?
|
||
|
||
To stop receiving these emails, reply "STOP".
|
||
|
||
Sami Alassiri
|
||
Dealix — AI Sales Rep for Saudi Businesses
|
||
dealix.me"""
|
||
|
||
followup_2 = f"""Hi {name},
|
||
|
||
Following up on my email 2 days ago about Dealix.
|
||
|
||
Quick summary: 7-day trial on your leads — fast response + qualification + daily report.
|
||
|
||
Would 10 minutes this week work?
|
||
calendly.com/sami-assiri11/dealix-demo
|
||
|
||
Reply "STOP" to unsubscribe.
|
||
|
||
Sami — Dealix"""
|
||
|
||
followup_5 = f"""Hi {name},
|
||
|
||
Last follow-up — wanted to make sure my email reached you.
|
||
|
||
Dealix helps {req.sector} companies respond to inquiries faster and convert more leads.
|
||
|
||
If timing isn't right, no worries. If it is, I'm available anytime.
|
||
|
||
Reply "STOP" to unsubscribe.
|
||
|
||
Sami — Dealix"""
|
||
|
||
return {
|
||
"company": req.company,
|
||
"sector": req.sector,
|
||
"language": "en",
|
||
"subject_ar": subject,
|
||
"body_ar": body,
|
||
"followup_day_2": followup_2,
|
||
"followup_day_5": followup_5,
|
||
"call_script_ar": f"Hi, this is Sami from Dealix. We help {req.sector} companies respond to leads faster. Do you have 5 minutes?",
|
||
"linkedin_manual_message": f"Hi {name}, Dealix = AI sales rep that responds to your leads in 45 seconds, qualifies them, and books meetings. 20-min demo? calendly.com/sami-assiri11/dealix-demo",
|
||
"opt_out_included": True,
|
||
"word_count": len(body.split()),
|
||
"generated_at": datetime.now(timezone.utc).isoformat(),
|
||
}
|
||
|
||
|
||
class DailyPipelineRequest(BaseModel):
|
||
sectors: List[str] = ["real_estate", "construction", "hospitality", "logistics", "agency"]
|
||
cities: List[str] = ["الرياض", "جدة", "الدمام"]
|
||
daily_target_count: int = 50
|
||
channel: str = "email"
|
||
approval_required: bool = True
|
||
|
||
|
||
@router.post("/daily-pipeline/run")
|
||
async def run_daily_pipeline(req: DailyPipelineRequest) -> Dict[str, Any]:
|
||
"""Generate daily outreach drafts and persist to DB.
|
||
|
||
Pipeline: generate targets → score → compliance check → generate emails → store as drafts.
|
||
All drafts start with status='draft'. Sami approves before any send.
|
||
"""
|
||
batch_id = f"batch_{datetime.now(timezone.utc).strftime('%Y%m%d_%H%M')}_{str(uuid4())[:6]}"
|
||
drafts_created = []
|
||
skipped = []
|
||
|
||
for i, sector in enumerate(req.sectors):
|
||
sector_info = SECTOR_PAIN_MAP.get(sector, SECTOR_PAIN_MAP.get("saas", {}))
|
||
for j, city in enumerate(req.cities[:3]):
|
||
idx = i * 3 + j
|
||
if len(drafts_created) >= req.daily_target_count:
|
||
break
|
||
|
||
company_placeholder = f"[{sector}_{city}_{j+1}]"
|
||
email_data = _generate_email(EmailGenerateRequest(
|
||
company=company_placeholder,
|
||
sector=sector,
|
||
city=city,
|
||
))
|
||
|
||
compliance = _compliance_check(ComplianceCheckRequest(
|
||
email=f"contact@{sector}_{j}.example.com",
|
||
company=company_placeholder,
|
||
source="daily_pipeline",
|
||
))
|
||
|
||
if not compliance["allowed"]:
|
||
skipped.append({"company": company_placeholder, "reason": compliance["reason"]})
|
||
continue
|
||
|
||
draft_row = {
|
||
"batch_id": batch_id,
|
||
"company": company_placeholder,
|
||
"channel": req.channel,
|
||
"subject": email_data.get("subject_ar", ""),
|
||
"body": email_data.get("body_ar", ""),
|
||
"followup_2d": email_data.get("followup_day_2", ""),
|
||
"followup_5d": email_data.get("followup_day_5", ""),
|
||
"call_script": email_data.get("call_script_ar", ""),
|
||
"sector": sector,
|
||
"city": city,
|
||
"pain_hypothesis": sector_info.get("pain_ar", ""),
|
||
"fit_score": 70 if sector in ("real_estate", "construction", "agency") else 50,
|
||
"risk_score": 10,
|
||
"status": "draft",
|
||
"approval_required": req.approval_required,
|
||
"source": "daily_pipeline",
|
||
}
|
||
|
||
try:
|
||
from app.models.outreach_draft import OutreachDraft
|
||
from app.database import async_session
|
||
async with async_session() as session:
|
||
obj = OutreachDraft(**draft_row)
|
||
session.add(obj)
|
||
await session.commit()
|
||
draft_row["id"] = str(obj.id)
|
||
drafts_created.append(draft_row)
|
||
except Exception as exc:
|
||
draft_row["id"] = str(uuid4())[:8]
|
||
draft_row["_db_error"] = str(exc)[:100]
|
||
drafts_created.append(draft_row)
|
||
|
||
return {
|
||
"batch_id": batch_id,
|
||
"date": datetime.now(timezone.utc).strftime("%Y-%m-%d"),
|
||
"drafts_created": len(drafts_created),
|
||
"skipped": len(skipped),
|
||
"channel": req.channel,
|
||
"approval_required": req.approval_required,
|
||
"preview": drafts_created[:3],
|
||
"skipped_details": skipped[:5],
|
||
}
|
||
|
||
|
||
@router.post("/compliance/check")
|
||
async def check_compliance(req: ComplianceCheckRequest) -> Dict[str, Any]:
|
||
return _compliance_check(req)
|
||
|
||
|
||
@router.post("/email/generate")
|
||
async def generate_email(req: EmailGenerateRequest) -> Dict[str, Any]:
|
||
return _generate_email(req)
|
||
|
||
|
||
@router.post("/daily-targeting/generate")
|
||
async def generate_daily_targets(req: TargetingRequest) -> Dict[str, Any]:
|
||
targets = []
|
||
for i, sector in enumerate(req.sectors):
|
||
sector_info = SECTOR_PAIN_MAP.get(sector, {})
|
||
for j, city in enumerate(req.cities[:3]):
|
||
idx = i * 3 + j
|
||
if idx >= req.daily_target_count:
|
||
break
|
||
target = {
|
||
"id": str(uuid4())[:8],
|
||
"company": f"[{sector}_{city}_{j+1}]",
|
||
"sector": sector,
|
||
"city": city,
|
||
"pain_hypothesis": sector_info.get("pain_ar", ""),
|
||
"angle": sector_info.get("angle_ar", ""),
|
||
"roi_estimate": sector_info.get("roi_ar", ""),
|
||
"priority": "P0" if sector in ("real_estate", "construction", "agency") else "P1",
|
||
"channel": "email",
|
||
"approval_required": req.approval_required,
|
||
"status": "ready",
|
||
}
|
||
targets.append(target)
|
||
|
||
return {
|
||
"date": datetime.now(timezone.utc).strftime("%Y-%m-%d"),
|
||
"total_generated": len(targets),
|
||
"sectors": req.sectors,
|
||
"cities": req.cities,
|
||
"batch_size": req.batch_size,
|
||
"approval_required": req.approval_required,
|
||
"targets": targets[:req.daily_target_count],
|
||
}
|
||
|
||
|
||
REPLY_CATEGORIES = {
|
||
"interested": {"next": "propose_demo", "auto_reply": True},
|
||
"ask_price": {"next": "explain_pilot_499", "auto_reply": True},
|
||
"ask_details": {"next": "send_brief_explanation", "auto_reply": True},
|
||
"ask_demo": {"next": "send_calendly", "auto_reply": True},
|
||
"not_now": {"next": "schedule_followup_30d", "auto_reply": True},
|
||
"unsubscribe": {"next": "opt_out_suppress", "auto_reply": False},
|
||
"objection_budget": {"next": "explain_roi_pilot", "auto_reply": True},
|
||
"objection_ai": {"next": "explain_manual_first", "auto_reply": True},
|
||
"objection_privacy": {"next": "human_review", "auto_reply": False},
|
||
"already_has_crm": {"next": "position_as_layer", "auto_reply": True},
|
||
"partnership": {"next": "route_partner_flow", "auto_reply": False},
|
||
"angry": {"next": "apologize_opt_out", "auto_reply": False},
|
||
}
|
||
|
||
REPLY_RESPONSES = {
|
||
"interested": "ممتاز! يناسبك نحجز 20 دقيقة هذا الأسبوع؟\ncalendly.com/sami-assiri11/dealix-demo",
|
||
"ask_price": "نبدأها كـ pilot بسيط لمدة 7 أيام بـ 499 ريال.\nنجرب على 10-25 lead، ونقيس النتائج.\nلو ما عجبك — استرداد كامل.",
|
||
"ask_details": "Dealix يساعدكم في:\n- الرد السريع على الـ leads\n- تأهيل العميل بأسئلة واضحة\n- حجز موعد أو تحويله لفريق المبيعات\n- تقرير يومي مختصر\n\nأفضل بداية: pilot 7 أيام. يناسبك أشرح أكثر؟",
|
||
"ask_demo": "تمام! احجز الوقت المناسب:\ncalendly.com/sami-assiri11/dealix-demo\n\nأو قلي وقتين يناسبونك وأنا أنسّق.",
|
||
"not_now": "تمام، شكراً على وقتك. أتواصل معك بعد شهر لو مناسب؟",
|
||
"objection_budget": "فاهم. لذلك نبدأ بـ pilot بسيط بـ 499 ريال فقط.\nلو ما شفت قيمة خلال 7 أيام — استرداد كامل.",
|
||
"already_has_crm": "ممتاز، Dealix ما يستبدل الـ CRM.\nهو طبقة قبله: يرد على الـ lead، يأهله، يحجز الموعد،\nوبعدها يسلّم البيانات لفريقكم أو للـ CRM.",
|
||
"unsubscribe": "تم، لن أتواصل معكم مرة ثانية. شكراً على وقتكم.",
|
||
}
|
||
|
||
|
||
class ClassifyReplyRequest(BaseModel):
|
||
reply_text: str
|
||
company: str = ""
|
||
original_sector: str = ""
|
||
|
||
|
||
@router.post("/reply/classify")
|
||
async def classify_reply(req: ClassifyReplyRequest) -> Dict[str, Any]:
|
||
text = req.reply_text.lower()
|
||
|
||
if any(w in text for w in ["إيقاف", "stop", "unsubscribe", "لا تتواصل", "remove"]):
|
||
cat = "unsubscribe"
|
||
elif any(w in text for w in ["كم السعر", "كم التكلفة", "how much", "pricing", "أسعار"]):
|
||
cat = "ask_price"
|
||
elif any(w in text for w in ["عرض", "demo", "ديمو", "أوريني", "شرح"]):
|
||
cat = "ask_demo"
|
||
elif any(w in text for w in ["مهتم", "interested", "أبي أجرب", "نجرب", "تمام"]):
|
||
cat = "interested"
|
||
elif any(w in text for w in ["تفاصيل", "details", "أكثر", "وش بالضبط"]):
|
||
cat = "ask_details"
|
||
elif any(w in text for w in ["لاحقاً", "later", "مو الحين", "بعدين"]):
|
||
cat = "not_now"
|
||
elif any(w in text for w in ["ميزانية", "budget", "غالي", "مكلف"]):
|
||
cat = "objection_budget"
|
||
elif any(w in text for w in ["CRM", "crm", "نظام", "عندنا حل"]):
|
||
cat = "already_has_crm"
|
||
elif any(w in text for w in ["شراكة", "partner", "وكالة", "نبيع"]):
|
||
cat = "partnership"
|
||
elif any(w in text for w in ["خصوصية", "privacy", "بيانات", "PDPL"]):
|
||
cat = "objection_privacy"
|
||
elif any(w in text for w in ["ذكاء", "AI", "عربي طبيعي", "مضبوط"]):
|
||
cat = "objection_ai"
|
||
elif any(w in text for w in ["زعلان", "angry", "spam", "مزعج"]):
|
||
cat = "angry"
|
||
else:
|
||
cat = "ask_details"
|
||
|
||
info = REPLY_CATEGORIES.get(cat, {"next": "human_review", "auto_reply": False})
|
||
response = REPLY_RESPONSES.get(cat, "شكراً على ردك. براجع وأرد عليك قريب.")
|
||
|
||
return {
|
||
"category": cat,
|
||
"next_action": info["next"],
|
||
"auto_reply_allowed": info["auto_reply"],
|
||
"suggested_response": response,
|
||
"human_review_required": not info["auto_reply"],
|
||
"company": req.company,
|
||
}
|