| from engine.states import (
|
| START,
|
| WAITING_MAIN_MENU,
|
| WAITING_USER_TYPE,
|
| WAITING_AUDIENCE,
|
| WAITING_PRIOR_STUDY,
|
| WAITING_BEGINNER_SCHEDULE_CHOICE,
|
| WAITING_PLACEMENT_TEST_CONFIRMATION,
|
| WAITING_CURRENT_STUDENT_ACTION,
|
| WAITING_SUPPORT_QUESTION,
|
| WAITING_LEVEL_SELECTION,
|
| WAITING_PAYMENT_METHOD,
|
| WAITING_COMPLAINT_FORM,
|
| HANDOFF_DONE,
|
| )
|
|
|
| from knowledge.replies import (
|
| greeting_main_menu_reply,
|
| ask_new_or_current_reply,
|
| ask_audience_reply,
|
| ask_prior_study_reply,
|
| beginner_schedule_reply,
|
| placement_test_reply,
|
| current_student_menu_reply,
|
| support_question_reply,
|
| ask_level_selection_reply,
|
| payment_methods_reply,
|
| unknown_reply,
|
| )
|
|
|
| from handoff.sales import create_sales_handoff_result
|
| from handoff.support import create_support_handoff_result
|
| from handoff.complaints import create_complaint_flow_result
|
|
|
|
|
| def normalize_text(text: str) -> str:
|
| return (text or "").strip().lower()
|
|
|
|
|
| def contains_any(text: str, keywords: list) -> bool:
|
| return any(k in text for k in keywords)
|
|
|
|
|
| def is_yes(text: str) -> bool:
|
| t = normalize_text(text)
|
| return t in [
|
| "نعم", "اه", "أه", "ايوه", "أيوه", "yes", "y",
|
| "درست", "اه درست", "أيوه درست"
|
| ]
|
|
|
|
|
| def is_no(text: str) -> bool:
|
| t = normalize_text(text)
|
| return t in [
|
| "لا", "لأ", "لاا", "no", "n",
|
| "مدرستش", "ما درستش", "لا مدرستش"
|
| ]
|
|
|
|
|
| def is_new_student(text: str) -> bool:
|
| t = normalize_text(text)
|
| return contains_any(t, [
|
| "طالب جديد", "جديد", "عميل جديد", "اول مرة", "أول مرة",
|
| "لسه جديد", "مشترك جديد"
|
| ])
|
|
|
|
|
| def is_current_student(text: str) -> bool:
|
| t = normalize_text(text)
|
| return contains_any(t, [
|
| "طالب حالي", "حالي", "عميل حالي", "مشترك", "مشترك حالي",
|
| "أنا طالب", "انا طالب عندكم", "انا مشترك"
|
| ])
|
|
|
|
|
| def is_adults(text: str) -> bool:
|
| t = normalize_text(text)
|
| return contains_any(t, [
|
| "كبار", "adult", "adults", "الكبار"
|
| ])
|
|
|
|
|
| def is_children(text: str) -> bool:
|
| t = normalize_text(text)
|
| return contains_any(t, [
|
| "اطفال", "أطفال", "طفل", "children", "kids"
|
| ])
|
|
|
|
|
| def is_support_request(text: str) -> bool:
|
| t = normalize_text(text)
|
| return contains_any(t, [
|
| "استفسار", "سؤال", "عندي سؤال", "مشكلة", "مش فاهم",
|
| "عايز اسأل", "عايزة اسأل", "محتاج مساعدة", "محتاجه مساعدة",
|
| "support", "خدمة العملاء"
|
| ])
|
|
|
|
|
| def is_next_level_booking(text: str) -> bool:
|
| t = normalize_text(text)
|
| return contains_any(t, [
|
| "حجز", "احجز", "المستوى التالي", "مستوى تالي",
|
| "next level", "احجز المستوى", "حجز مستوى"
|
| ])
|
|
|
|
|
| def detect_level(text: str):
|
| t = normalize_text(text)
|
|
|
| if contains_any(t, ["1a", "a1", "a1.1", "1 a"]):
|
| return "1A"
|
|
|
| if contains_any(t, ["2a", "a2", "a1.2", "2 a"]):
|
| return "2A"
|
|
|
| if contains_any(t, ["1b", "b1", "b1.1", "1 b"]):
|
| return "1B"
|
|
|
| if contains_any(t, ["1c", "2b", "b2", "1c2/b", "1 c", "2 b"]):
|
| return "1C2/B"
|
|
|
| return None
|
|
|
|
|
| def detect_payment_method(text: str):
|
| t = normalize_text(text)
|
|
|
| if contains_any(t, ["فرع", "فروع", "كاش", "cash"]):
|
| return "branch_or_cash"
|
|
|
| if contains_any(t, ["تحويل", "بنكي", "bank", "transfer"]):
|
| return "bank_transfer"
|
|
|
| if contains_any(t, ["فودافون", "vodafone", "vodafone cash"]):
|
| return "vodafone_cash"
|
|
|
| if contains_any(t, ["فيزا", "visa", "ماستر", "master", "credit card", "card"]):
|
| return "card"
|
|
|
| if contains_any(t, ["تقسيط", "value", "فاليو"]):
|
| return "installments"
|
|
|
| return None
|
|
|
|
|
| def is_complaint(text: str) -> bool:
|
| t = normalize_text(text)
|
| return contains_any(t, [
|
| "شكوى", "اشتكي", "اشتك", "مشكلة كبيرة", "complaint"
|
| ])
|
|
|
|
|
| def wants_direct_support(text: str) -> bool:
|
| t = normalize_text(text)
|
| return contains_any(t, [
|
| "تواصل", "اكلم", "عايز حد يكلمني", "عايزة حد يكلمني",
|
| "عايز اكلم خدمة العملاء", "عايزة اكلم خدمة العملاء"
|
| ])
|
|
|
|
|
| def wants_start(text: str) -> bool:
|
| t = normalize_text(text)
|
| return contains_any(t, [
|
| "ابدأ", "ابدا", "مساعدة", "مساعده", "start", "menu", "القائمة"
|
| ])
|
|
|
|
|
| def route_message(state: str, text: str, session: dict):
|
| t = normalize_text(text)
|
| flow_data = session.get("flow_data", {}) or {}
|
|
|
|
|
|
|
|
|
| if state == START:
|
| return greeting_main_menu_reply(flow_data)
|
|
|
|
|
|
|
|
|
| if state == WAITING_MAIN_MENU:
|
| if is_complaint(t):
|
| return create_complaint_flow_result(flow_data)
|
|
|
| if wants_direct_support(t):
|
| return create_support_handoff_result(
|
| flow_data=flow_data,
|
| summary="طلب تواصل مباشر مع خدمة العملاء"
|
| )
|
|
|
| if wants_start(t):
|
| return ask_new_or_current_reply(flow_data)
|
|
|
|
|
| if is_new_student(t):
|
| flow_data["customer_type"] = "new"
|
| return ask_audience_reply(flow_data)
|
|
|
| if is_current_student(t):
|
| flow_data["customer_type"] = "current"
|
| return current_student_menu_reply(flow_data)
|
|
|
| return ask_new_or_current_reply(flow_data)
|
|
|
|
|
|
|
|
|
| if state == WAITING_USER_TYPE:
|
| if is_new_student(t):
|
| flow_data["customer_type"] = "new"
|
| return ask_audience_reply(flow_data)
|
|
|
| if is_current_student(t):
|
| flow_data["customer_type"] = "current"
|
| return current_student_menu_reply(flow_data)
|
|
|
| if is_complaint(t):
|
| return create_complaint_flow_result(flow_data)
|
|
|
| return {
|
| "next_state": WAITING_USER_TYPE,
|
| "flow_data": flow_data,
|
| "reply": "تمام، محتاج أعرف الأول: إنت طالب جديد ولا طالب حالي؟"
|
| }
|
|
|
|
|
|
|
|
|
| if state == WAITING_AUDIENCE:
|
| if is_adults(t):
|
| flow_data["audience"] = "adults"
|
| return ask_prior_study_reply(flow_data)
|
|
|
| if is_children(t):
|
| flow_data["audience"] = "children"
|
| return ask_prior_study_reply(flow_data)
|
|
|
| return {
|
| "next_state": WAITING_AUDIENCE,
|
| "flow_data": flow_data,
|
| "reply": "تمام، الكورس للكبار ولا للأطفال؟"
|
| }
|
|
|
|
|
|
|
|
|
| if state == WAITING_PRIOR_STUDY:
|
| if is_no(t):
|
| flow_data["prior_study"] = False
|
| return beginner_schedule_reply(flow_data)
|
|
|
| if is_yes(t):
|
| flow_data["prior_study"] = True
|
| return placement_test_reply(flow_data)
|
|
|
| return {
|
| "next_state": WAITING_PRIOR_STUDY,
|
| "flow_data": flow_data,
|
| "reply": "هل درست اللغة الألمانية قبل كده؟ رد بنعم أو لا."
|
| }
|
|
|
|
|
|
|
|
|
| if state == WAITING_BEGINNER_SCHEDULE_CHOICE:
|
| if contains_any(t, ["تم", "اخترت", "اختارت", "جاهز", "جاهزة"]):
|
| return payment_methods_reply(flow_data)
|
|
|
| if contains_any(t, ["عايز احجز", "عايزة احجز", "احجز", "حجز", "اشترك", "اشتراك"]):
|
| return payment_methods_reply(flow_data)
|
|
|
| if contains_any(t, ["محتاج مساعدة", "محتاجه مساعدة", "استفسار", "سؤال"]):
|
| return create_support_handoff_result(
|
| flow_data=flow_data,
|
| summary="استفسار عن مواعيد كورسات المبتدئين"
|
| )
|
|
|
| return {
|
| "next_state": WAITING_BEGINNER_SCHEDULE_CHOICE,
|
| "flow_data": flow_data,
|
| "reply": "بعد ما تختار المعاد المناسب، ابعتلي تم أو قولّي تحب نكمّل الحجز."
|
| }
|
|
|
|
|
|
|
|
|
| if state == WAITING_PLACEMENT_TEST_CONFIRMATION:
|
| if contains_any(t, ["تم", "اخترت", "اختارت", "جاهز", "جاهزة"]):
|
| return create_sales_handoff_result(
|
| flow_data=flow_data,
|
| summary="عميل اختار أو مهتم باختبار تحديد مستوى"
|
| )
|
|
|
| if contains_any(t, ["تفاصيل", "اعرف اكتر", "محتاج مساعدة", "مساعدة", "استفسار"]):
|
| return create_support_handoff_result(
|
| flow_data=flow_data,
|
| summary="استفسار عن اختبار تحديد المستوى"
|
| )
|
|
|
| return {
|
| "next_state": WAITING_PLACEMENT_TEST_CONFIRMATION,
|
| "flow_data": flow_data,
|
| "reply": "بعد ما تراجع مواعيد اختبار تحديد المستوى، ابعتلي تم أو قولّي لو محتاج مساعدة."
|
| }
|
|
|
|
|
|
|
|
|
| if state == WAITING_CURRENT_STUDENT_ACTION:
|
| if is_support_request(t):
|
| return support_question_reply(flow_data)
|
|
|
| if is_next_level_booking(t):
|
| return ask_level_selection_reply(flow_data)
|
|
|
| if is_complaint(t):
|
| return create_complaint_flow_result(flow_data)
|
|
|
| return {
|
| "next_state": WAITING_CURRENT_STUDENT_ACTION,
|
| "flow_data": flow_data,
|
| "reply": "تحب استفسار بخصوص الكورس ولا حجز المستوى التالي؟"
|
| }
|
|
|
|
|
|
|
|
|
| if state == WAITING_SUPPORT_QUESTION:
|
| flow_data["support_question"] = text
|
|
|
| return create_support_handoff_result(
|
| flow_data=flow_data,
|
| summary=f"استفسار طالب حالي: {text}"
|
| )
|
|
|
|
|
|
|
|
|
| if state == WAITING_LEVEL_SELECTION:
|
| level = detect_level(t)
|
|
|
| if level:
|
| flow_data["selected_level"] = level
|
| return payment_methods_reply(flow_data)
|
|
|
| if contains_any(t, ["مش عارف", "مش متأكد", "مش متاكدة", "محتاج مساعدة", "استفسار"]):
|
| return create_support_handoff_result(
|
| flow_data=flow_data,
|
| summary="طالب حالي محتاج مساعدة في اختيار المستوى التالي"
|
| )
|
|
|
| return {
|
| "next_state": WAITING_LEVEL_SELECTION,
|
| "flow_data": flow_data,
|
| "reply": "قولّي المستوى اللي تحب تحجزه: 1A أو 2A أو 1B أو 1C2/B."
|
| }
|
|
|
|
|
|
|
|
|
| if state == WAITING_PAYMENT_METHOD:
|
| payment_method = detect_payment_method(t)
|
|
|
| if payment_method:
|
| flow_data["payment_method"] = payment_method
|
|
|
| payment_summaries = {
|
| "branch_or_cash": "عميل جاهز للحجز واختار الدفع كاش أو في الفرع",
|
| "bank_transfer": "عميل جاهز للحجز واختار تحويل بنكي",
|
| "vodafone_cash": "عميل جاهز للحجز واختار Vodafone Cash",
|
| "card": "عميل جاهز للحجز واختار الدفع بالبطاقة",
|
| "installments": "عميل مهتم بالتقسيط",
|
| }
|
|
|
| return create_sales_handoff_result(
|
| flow_data=flow_data,
|
| summary=payment_summaries[payment_method]
|
| )
|
|
|
| if contains_any(t, ["استفسار", "سؤال", "محتاج مساعدة", "محتاجه مساعدة"]):
|
| return create_support_handoff_result(
|
| flow_data=flow_data,
|
| summary="استفسار عن طرق الدفع أو إتمام الحجز"
|
| )
|
|
|
| return {
|
| "next_state": WAITING_PAYMENT_METHOD,
|
| "flow_data": flow_data,
|
| "reply": "اختار طريقة الدفع المناسبة: الفرع / تحويل بنكي / Vodafone Cash / Visa / تقسيط Value."
|
| }
|
|
|
|
|
|
|
|
|
| if state == WAITING_COMPLAINT_FORM:
|
| if contains_any(t, ["تم", "خلصت", "سجلت", "قدمت", "بعت"]):
|
| return {
|
| "next_state": WAITING_COMPLAINT_FORM,
|
| "flow_data": flow_data,
|
| "reply": "تمام، بعد مراجعة الشكوى هيتم التواصل معاك. ولو حابب تكتب أي تفاصيل إضافية ابعتها هنا."
|
| }
|
|
|
| return {
|
| "next_state": WAITING_COMPLAINT_FORM,
|
| "flow_data": flow_data,
|
| "reply": "سجّل الشكوى من الفورم أولًا، ولو احتجت مساعدة ابعتلي."
|
| }
|
|
|
|
|
|
|
|
|
| if state == HANDOFF_DONE:
|
| if is_complaint(t):
|
| return create_complaint_flow_result(flow_data)
|
|
|
| if is_support_request(t):
|
| return create_support_handoff_result(
|
| flow_data=flow_data,
|
| summary=f"استفسار إضافي بعد handoff: {text}"
|
| )
|
|
|
| if contains_any(t, ["شكرا", "متشكر", "تسلم", "ميرسي"]):
|
| return {
|
| "next_state": HANDOFF_DONE,
|
| "flow_data": flow_data,
|
| "reply": "العفو، تحت أمرك في أي وقت."
|
| }
|
|
|
| return {
|
| "next_state": HANDOFF_DONE,
|
| "flow_data": flow_data,
|
| "reply": "تم تسجيل طلبك بالفعل، وفريقنا هيتواصل معاك قريب. لو عندك استفسار إضافي ابعته هنا."
|
| }
|
|
|
| return unknown_reply(flow_data) |