Mr-Help commited on
Commit
02fbdc7
·
verified ·
1 Parent(s): 16ce4cf

Upload 6 files

Browse files
engine/conversation_engine.py ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from engine.flow_router import route_message
2
+ from sessions import get_session, upsert_session
3
+
4
+
5
+ def process_message(bot_number: str, customer_phone: str, text: str):
6
+ session = get_session(customer_phone, bot_number)
7
+
8
+ if not session:
9
+ session = {
10
+ "current_state": "START",
11
+ "flow_type": None,
12
+ "flow_data": {
13
+ "uid": f"{bot_number}__{customer_phone}"
14
+ }
15
+ }
16
+
17
+ state = session.get("current_state", "START")
18
+ result = route_message(state, text, session)
19
+
20
+ next_state = result["next_state"]
21
+ flow_data = result.get("flow_data", session.get("flow_data", {}))
22
+ flow_type = flow_data.get("customer_type")
23
+
24
+ upsert_session(
25
+ customer_phone=customer_phone,
26
+ bot_number=bot_number,
27
+ current_state=next_state,
28
+ flow_type=flow_type,
29
+ flow_data=flow_data,
30
+ last_message=text,
31
+ )
32
+
33
+ return result
engine/flow_router.py ADDED
@@ -0,0 +1,435 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from engine.states import (
2
+ START,
3
+ WAITING_MAIN_MENU,
4
+ WAITING_USER_TYPE,
5
+ WAITING_AUDIENCE,
6
+ WAITING_PRIOR_STUDY,
7
+ WAITING_BEGINNER_SCHEDULE_CHOICE,
8
+ WAITING_PLACEMENT_TEST_CONFIRMATION,
9
+ WAITING_CURRENT_STUDENT_ACTION,
10
+ WAITING_SUPPORT_QUESTION,
11
+ WAITING_LEVEL_SELECTION,
12
+ WAITING_PAYMENT_METHOD,
13
+ WAITING_COMPLAINT_FORM,
14
+ HANDOFF_DONE,
15
+ )
16
+
17
+ from knowledge.replies import (
18
+ greeting_main_menu_reply,
19
+ ask_new_or_current_reply,
20
+ ask_audience_reply,
21
+ ask_prior_study_reply,
22
+ beginner_schedule_reply,
23
+ placement_test_reply,
24
+ current_student_menu_reply,
25
+ support_question_reply,
26
+ ask_level_selection_reply,
27
+ payment_methods_reply,
28
+ unknown_reply,
29
+ )
30
+
31
+ from handoff.sales import create_sales_handoff_result
32
+ from handoff.support import create_support_handoff_result
33
+ from handoff.complaints import create_complaint_flow_result
34
+
35
+
36
+ def normalize_text(text: str) -> str:
37
+ return (text or "").strip().lower()
38
+
39
+
40
+ def contains_any(text: str, keywords: list) -> bool:
41
+ return any(k in text for k in keywords)
42
+
43
+
44
+ def is_yes(text: str) -> bool:
45
+ t = normalize_text(text)
46
+ return t in [
47
+ "نعم", "اه", "أه", "ايوه", "أيوه", "yes", "y",
48
+ "درست", "اه درست", "أيوه درست"
49
+ ]
50
+
51
+
52
+ def is_no(text: str) -> bool:
53
+ t = normalize_text(text)
54
+ return t in [
55
+ "لا", "لأ", "لاا", "no", "n",
56
+ "مدرستش", "ما درستش", "لا مدرستش"
57
+ ]
58
+
59
+
60
+ def is_new_student(text: str) -> bool:
61
+ t = normalize_text(text)
62
+ return contains_any(t, [
63
+ "طالب جديد", "جديد", "عميل جديد", "اول مرة", "أول مرة",
64
+ "لسه جديد", "مشترك جديد"
65
+ ])
66
+
67
+
68
+ def is_current_student(text: str) -> bool:
69
+ t = normalize_text(text)
70
+ return contains_any(t, [
71
+ "طالب حالي", "حالي", "عميل حالي", "مشترك", "مشترك حالي",
72
+ "أنا طالب", "انا طالب عندكم", "انا مشترك"
73
+ ])
74
+
75
+
76
+ def is_adults(text: str) -> bool:
77
+ t = normalize_text(text)
78
+ return contains_any(t, [
79
+ "كبار", "adult", "adults", "الكبار"
80
+ ])
81
+
82
+
83
+ def is_children(text: str) -> bool:
84
+ t = normalize_text(text)
85
+ return contains_any(t, [
86
+ "اطفال", "أطفال", "طفل", "children", "kids"
87
+ ])
88
+
89
+
90
+ def is_support_request(text: str) -> bool:
91
+ t = normalize_text(text)
92
+ return contains_any(t, [
93
+ "استفسار", "سؤال", "عندي سؤال", "مشكلة", "مش فاهم",
94
+ "عايز اسأل", "عايزة اسأل", "محتاج مساعدة", "محتاجه مساعدة",
95
+ "support", "خدمة العملاء"
96
+ ])
97
+
98
+
99
+ def is_next_level_booking(text: str) -> bool:
100
+ t = normalize_text(text)
101
+ return contains_any(t, [
102
+ "حجز", "احجز", "المستوى التالي", "مستوى تالي",
103
+ "next level", "احجز المستوى", "حجز مستوى"
104
+ ])
105
+
106
+
107
+ def detect_level(text: str):
108
+ t = normalize_text(text)
109
+
110
+ if contains_any(t, ["1a", "a1", "a1.1", "1 a"]):
111
+ return "1A"
112
+
113
+ if contains_any(t, ["2a", "a2", "a1.2", "2 a"]):
114
+ return "2A"
115
+
116
+ if contains_any(t, ["1b", "b1", "b1.1", "1 b"]):
117
+ return "1B"
118
+
119
+ if contains_any(t, ["1c", "2b", "b2", "1c2/b", "1 c", "2 b"]):
120
+ return "1C2/B"
121
+
122
+ return None
123
+
124
+
125
+ def detect_payment_method(text: str):
126
+ t = normalize_text(text)
127
+
128
+ if contains_any(t, ["فرع", "فروع", "كاش", "cash"]):
129
+ return "branch_or_cash"
130
+
131
+ if contains_any(t, ["تحويل", "بنكي", "bank", "transfer"]):
132
+ return "bank_transfer"
133
+
134
+ if contains_any(t, ["فودافون", "vodafone", "vodafone cash"]):
135
+ return "vodafone_cash"
136
+
137
+ if contains_any(t, ["فيزا", "visa", "ماستر", "master", "credit card", "card"]):
138
+ return "card"
139
+
140
+ if contains_any(t, ["تقسيط", "value", "فاليو"]):
141
+ return "installments"
142
+
143
+ return None
144
+
145
+
146
+ def is_complaint(text: str) -> bool:
147
+ t = normalize_text(text)
148
+ return contains_any(t, [
149
+ "شكوى", "اشتكي", "اشتك", "مشكلة كبيرة", "complaint"
150
+ ])
151
+
152
+
153
+ def wants_direct_support(text: str) -> bool:
154
+ t = normalize_text(text)
155
+ return contains_any(t, [
156
+ "تواصل", "اكلم", "عايز حد يكلمني", "عايزة حد يكلمني",
157
+ "عايز اكلم خدمة العملاء", "عايزة اكلم خدمة العملاء"
158
+ ])
159
+
160
+
161
+ def wants_start(text: str) -> bool:
162
+ t = normalize_text(text)
163
+ return contains_any(t, [
164
+ "ابدأ", "ابدا", "مساعدة", "مساعده", "start", "menu", "القائمة"
165
+ ])
166
+
167
+
168
+ def route_message(state: str, text: str, session: dict):
169
+ t = normalize_text(text)
170
+ flow_data = session.get("flow_data", {}) or {}
171
+
172
+ # =========================
173
+ # START
174
+ # =========================
175
+ if state == START:
176
+ return greeting_main_menu_reply(flow_data)
177
+
178
+ # =========================
179
+ # WAITING_MAIN_MENU
180
+ # =========================
181
+ if state == WAITING_MAIN_MENU:
182
+ if is_complaint(t):
183
+ return create_complaint_flow_result(flow_data)
184
+
185
+ if wants_direct_support(t):
186
+ return create_support_handoff_result(
187
+ flow_data=flow_data,
188
+ summary="طلب تواصل مباشر مع خدمة العملاء"
189
+ )
190
+
191
+ if wants_start(t):
192
+ return ask_new_or_current_reply(flow_data)
193
+
194
+ # لو العميل دخل مباشرة بنية واضحة
195
+ if is_new_student(t):
196
+ flow_data["customer_type"] = "new"
197
+ return ask_audience_reply(flow_data)
198
+
199
+ if is_current_student(t):
200
+ flow_data["customer_type"] = "current"
201
+ return current_student_menu_reply(flow_data)
202
+
203
+ return ask_new_or_current_reply(flow_data)
204
+
205
+ # =========================
206
+ # WAITING_USER_TYPE
207
+ # =========================
208
+ if state == WAITING_USER_TYPE:
209
+ if is_new_student(t):
210
+ flow_data["customer_type"] = "new"
211
+ return ask_audience_reply(flow_data)
212
+
213
+ if is_current_student(t):
214
+ flow_data["customer_type"] = "current"
215
+ return current_student_menu_reply(flow_data)
216
+
217
+ if is_complaint(t):
218
+ return create_complaint_flow_result(flow_data)
219
+
220
+ return {
221
+ "next_state": WAITING_USER_TYPE,
222
+ "flow_data": flow_data,
223
+ "reply": "تمام، محتاج أعرف الأول: إنت طالب جديد ولا طالب حالي؟"
224
+ }
225
+
226
+ # =========================
227
+ # WAITING_AUDIENCE
228
+ # =========================
229
+ if state == WAITING_AUDIENCE:
230
+ if is_adults(t):
231
+ flow_data["audience"] = "adults"
232
+ return ask_prior_study_reply(flow_data)
233
+
234
+ if is_children(t):
235
+ flow_data["audience"] = "children"
236
+ return ask_prior_study_reply(flow_data)
237
+
238
+ return {
239
+ "next_state": WAITING_AUDIENCE,
240
+ "flow_data": flow_data,
241
+ "reply": "تمام، الكورس للكبار ولا للأطفال؟"
242
+ }
243
+
244
+ # =========================
245
+ # WAITING_PRIOR_STUDY
246
+ # =========================
247
+ if state == WAITING_PRIOR_STUDY:
248
+ if is_no(t):
249
+ flow_data["prior_study"] = False
250
+ return beginner_schedule_reply(flow_data)
251
+
252
+ if is_yes(t):
253
+ flow_data["prior_study"] = True
254
+ return placement_test_reply(flow_data)
255
+
256
+ return {
257
+ "next_state": WAITING_PRIOR_STUDY,
258
+ "flow_data": flow_data,
259
+ "reply": "هل درست اللغة الألمانية قبل كده؟ رد بنعم أو لا."
260
+ }
261
+
262
+ # =========================
263
+ # WAITING_BEGINNER_SCHEDULE_CHOICE
264
+ # =========================
265
+ if state == WAITING_BEGINNER_SCHEDULE_CHOICE:
266
+ if contains_any(t, ["تم", "اخترت", "اختارت", "جاهز", "جاهزة"]):
267
+ return payment_methods_reply(flow_data)
268
+
269
+ if contains_any(t, ["عايز احجز", "عايزة احجز", "احجز", "حجز", "اشترك", "اشتراك"]):
270
+ return payment_methods_reply(flow_data)
271
+
272
+ if contains_any(t, ["محتاج مساعدة", "محتاجه مساعدة", "استفسار", "سؤال"]):
273
+ return create_support_handoff_result(
274
+ flow_data=flow_data,
275
+ summary="استفسار عن مواعيد كورسات المبتدئين"
276
+ )
277
+
278
+ return {
279
+ "next_state": WAITING_BEGINNER_SCHEDULE_CHOICE,
280
+ "flow_data": flow_data,
281
+ "reply": "بعد ما تختار المعاد المناسب، ابعتلي تم أو قولّي تحب نكمّل الحجز."
282
+ }
283
+
284
+ # =========================
285
+ # WAITING_PLACEMENT_TEST_CONFIRMATION
286
+ # =========================
287
+ if state == WAITING_PLACEMENT_TEST_CONFIRMATION:
288
+ if contains_any(t, ["تم", "اخترت", "اختارت", "جاهز", "جاهزة"]):
289
+ return create_sales_handoff_result(
290
+ flow_data=flow_data,
291
+ summary="عميل اختار أو مهتم باختبار تحديد مستوى"
292
+ )
293
+
294
+ if contains_any(t, ["تفاصيل", "اعرف اكتر", "محتاج مساعدة", "مساعدة", "استفسار"]):
295
+ return create_support_handoff_result(
296
+ flow_data=flow_data,
297
+ summary="استفسار عن اختبار تحديد المستوى"
298
+ )
299
+
300
+ return {
301
+ "next_state": WAITING_PLACEMENT_TEST_CONFIRMATION,
302
+ "flow_data": flow_data,
303
+ "reply": "بعد ما تراجع مواعيد اختبار تحديد المستوى، ابعتلي تم أو قولّي لو محتاج مساعدة."
304
+ }
305
+
306
+ # =========================
307
+ # WAITING_CURRENT_STUDENT_ACTION
308
+ # =========================
309
+ if state == WAITING_CURRENT_STUDENT_ACTION:
310
+ if is_support_request(t):
311
+ return support_question_reply(flow_data)
312
+
313
+ if is_next_level_booking(t):
314
+ return ask_level_selection_reply(flow_data)
315
+
316
+ if is_complaint(t):
317
+ return create_complaint_flow_result(flow_data)
318
+
319
+ return {
320
+ "next_state": WAITING_CURRENT_STUDENT_ACTION,
321
+ "flow_data": flow_data,
322
+ "reply": "تحب استفسار بخصوص الكورس ولا حجز المستوى التالي؟"
323
+ }
324
+
325
+ # =========================
326
+ # WAITING_SUPPORT_QUESTION
327
+ # =========================
328
+ if state == WAITING_SUPPORT_QUESTION:
329
+ flow_data["support_question"] = text
330
+
331
+ return create_support_handoff_result(
332
+ flow_data=flow_data,
333
+ summary=f"استفسار طالب حالي: {text}"
334
+ )
335
+
336
+ # =========================
337
+ # WAITING_LEVEL_SELECTION
338
+ # =========================
339
+ if state == WAITING_LEVEL_SELECTION:
340
+ level = detect_level(t)
341
+
342
+ if level:
343
+ flow_data["selected_level"] = level
344
+ return payment_methods_reply(flow_data)
345
+
346
+ if contains_any(t, ["مش عارف", "مش متأكد", "مش متاكدة", "محتاج مساعدة", "استفسار"]):
347
+ return create_support_handoff_result(
348
+ flow_data=flow_data,
349
+ summary="طالب حالي محتاج مساعدة في اختيار المستوى التالي"
350
+ )
351
+
352
+ return {
353
+ "next_state": WAITING_LEVEL_SELECTION,
354
+ "flow_data": flow_data,
355
+ "reply": "قولّي المستوى اللي تحب تحجزه: 1A أو 2A أو 1B أو 1C2/B."
356
+ }
357
+
358
+ # =========================
359
+ # WAITING_PAYMENT_METHOD
360
+ # =========================
361
+ if state == WAITING_PAYMENT_METHOD:
362
+ payment_method = detect_payment_method(t)
363
+
364
+ if payment_method:
365
+ flow_data["payment_method"] = payment_method
366
+
367
+ payment_summaries = {
368
+ "branch_or_cash": "عميل جاهز للحجز واختار الدفع كاش أو في الفرع",
369
+ "bank_transfer": "عميل جاهز للحجز واختار تحويل بنكي",
370
+ "vodafone_cash": "عميل جاهز للحجز واختار Vodafone Cash",
371
+ "card": "عميل جاهز للحجز واختار الدفع بالبطاقة",
372
+ "installments": "عميل مهتم بالتقسيط",
373
+ }
374
+
375
+ return create_sales_handoff_result(
376
+ flow_data=flow_data,
377
+ summary=payment_summaries[payment_method]
378
+ )
379
+
380
+ if contains_any(t, ["استفسار", "سؤال", "محتاج مساعدة", "محتاجه مساعدة"]):
381
+ return create_support_handoff_result(
382
+ flow_data=flow_data,
383
+ summary="استفسار عن طرق الدفع أو إتمام الحجز"
384
+ )
385
+
386
+ return {
387
+ "next_state": WAITING_PAYMENT_METHOD,
388
+ "flow_data": flow_data,
389
+ "reply": "اختار طريقة الدفع المناسبة: الفرع / تحويل بنكي / Vodafone Cash / Visa / تقسيط Value."
390
+ }
391
+
392
+ # =========================
393
+ # WAITING_COMPLAINT_FORM
394
+ # =========================
395
+ if state == WAITING_COMPLAINT_FORM:
396
+ if contains_any(t, ["تم", "خلصت", "سجلت", "قدمت", "بعت"]):
397
+ return {
398
+ "next_state": WAITING_COMPLAINT_FORM,
399
+ "flow_data": flow_data,
400
+ "reply": "تمام، بعد مراجعة الشكوى هيتم التواصل معاك. ولو حابب تكتب أي تفاصيل إضافية ابعتها هنا."
401
+ }
402
+
403
+ return {
404
+ "next_state": WAITING_COMPLAINT_FORM,
405
+ "flow_data": flow_data,
406
+ "reply": "سجّل الشكوى من الفورم أولًا، ولو احتجت مساعدة ابعتلي."
407
+ }
408
+
409
+ # =========================
410
+ # HANDOFF_DONE
411
+ # =========================
412
+ if state == HANDOFF_DONE:
413
+ if is_complaint(t):
414
+ return create_complaint_flow_result(flow_data)
415
+
416
+ if is_support_request(t):
417
+ return create_support_handoff_result(
418
+ flow_data=flow_data,
419
+ summary=f"استفسار إضافي بعد handoff: {text}"
420
+ )
421
+
422
+ if contains_any(t, ["شكرا", "متشكر", "تسلم", "ميرسي"]):
423
+ return {
424
+ "next_state": HANDOFF_DONE,
425
+ "flow_data": flow_data,
426
+ "reply": "العفو، تحت أمرك في أي وقت."
427
+ }
428
+
429
+ return {
430
+ "next_state": HANDOFF_DONE,
431
+ "flow_data": flow_data,
432
+ "reply": "تم تسجيل طلبك بالفعل، وفريقنا هيتواصل معاك قريب. لو عندك استفسار إضافي ابعته هنا."
433
+ }
434
+
435
+ return unknown_reply(flow_data)
engine/states.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ START = "START"
2
+
3
+ WAITING_MAIN_MENU = "WAITING_MAIN_MENU"
4
+ WAITING_USER_TYPE = "WAITING_USER_TYPE"
5
+ WAITING_AUDIENCE = "WAITING_AUDIENCE"
6
+ WAITING_PRIOR_STUDY = "WAITING_PRIOR_STUDY"
7
+
8
+ WAITING_BEGINNER_SCHEDULE_CHOICE = "WAITING_BEGINNER_SCHEDULE_CHOICE"
9
+ WAITING_PLACEMENT_TEST_CONFIRMATION = "WAITING_PLACEMENT_TEST_CONFIRMATION"
10
+
11
+ WAITING_CURRENT_STUDENT_ACTION = "WAITING_CURRENT_STUDENT_ACTION"
12
+ WAITING_SUPPORT_QUESTION = "WAITING_SUPPORT_QUESTION"
13
+
14
+ WAITING_LEVEL_SELECTION = "WAITING_LEVEL_SELECTION"
15
+ WAITING_PAYMENT_METHOD = "WAITING_PAYMENT_METHOD"
16
+
17
+ WAITING_COMPLAINT_FORM = "WAITING_COMPLAINT_FORM"
18
+ HANDOFF_DONE = "HANDOFF_DONE"
handoff/complaints.py ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from urllib.parse import urlencode
2
+ from config import COMPLAINT_FORM_URL
3
+
4
+
5
+ def build_complaint_url(uid: str):
6
+ query = urlencode({"uid": uid})
7
+ sep = "&" if "?" in COMPLAINT_FORM_URL else "?"
8
+ return f"{COMPLAINT_FORM_URL}{sep}{query}"
9
+
10
+
11
+ def create_complaint_flow_result(flow_data: dict):
12
+ flow_data = flow_data or {}
13
+ uid = flow_data.get("uid")
14
+
15
+ complaint_url = build_complaint_url(uid)
16
+
17
+ return {
18
+ "next_state": "WAITING_COMPLAINT_FORM",
19
+ "flow_data": flow_data,
20
+ "reply": "يسعدنا تواصلك. سجل الشكوى من الرابط ده، وفريقنا هيتابعها.",
21
+ "action": {
22
+ "type": "url_button",
23
+ "header": "احنا هنا عشان نساعدك 🤝",
24
+ "body": "يسعدنا تواصلك. من فضلك سجل الشكوى من الرابط التالي، وفريقنا هيتابع معاك.",
25
+ "footer": "شكراً لاختيار ÄDK",
26
+ "url": complaint_url,
27
+ "button_text": "تسجيل الشكوى",
28
+ }
29
+ }
handoff/sales.py ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from db import supabase
2
+
3
+
4
+ def create_sales_handoff(customer_phone: str, bot_number: str, summary: str, metadata=None):
5
+ payload = {
6
+ "customer_phone": customer_phone,
7
+ "bot_number": bot_number,
8
+ "department": "sales",
9
+ "summary": summary,
10
+ "status": "pending",
11
+ "metadata": metadata or {},
12
+ }
13
+ res = supabase.table("handoff_requests").insert(payload).execute()
14
+ rows = res.data or []
15
+ return rows[0] if rows else None
16
+
17
+
18
+ def create_sales_handoff_result(flow_data: dict, summary: str):
19
+ flow_data = flow_data or {}
20
+ flow_data["handoff_department"] = "sales"
21
+ flow_data["handoff_summary"] = summary
22
+
23
+ return {
24
+ "next_state": "HANDOFF_DONE",
25
+ "flow_data": flow_data,
26
+ "reply": (
27
+ "تمام، تم رفع طلبك لفريق المبيعات.\n"
28
+ "هيتم التواصل معاك قريب لاستكمال التفاصيل."
29
+ ),
30
+ "action": {
31
+ "type": "handoff",
32
+ "department": "sales",
33
+ "summary": summary,
34
+ }
35
+ }
handoff/support.py ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from db import supabase
2
+
3
+
4
+ def create_sales_handoff(customer_phone: str, bot_number: str, summary: str, metadata=None):
5
+ payload = {
6
+ "customer_phone": customer_phone,
7
+ "bot_number": bot_number,
8
+ "department": "sales",
9
+ "summary": summary,
10
+ "status": "pending",
11
+ "metadata": metadata or {},
12
+ }
13
+ res = supabase.table("handoff_requests").insert(payload).execute()
14
+ rows = res.data or []
15
+ return rows[0] if rows else None
16
+
17
+
18
+ def create_sales_handoff_result(flow_data: dict, summary: str):
19
+ flow_data = flow_data or {}
20
+ flow_data["handoff_department"] = "sales"
21
+ flow_data["handoff_summary"] = summary
22
+
23
+ return {
24
+ "next_state": "HANDOFF_DONE",
25
+ "flow_data": flow_data,
26
+ "reply": (
27
+ "تمام، تم رفع طلبك لفريق المبيعات.\n"
28
+ "هيتم التواصل معاك قريب لاستكمال التفاصيل."
29
+ ),
30
+ "action": {
31
+ "type": "handoff",
32
+ "department": "sales",
33
+ "summary": summary,
34
+ }
35
+ }