codeBOKER commited on
Commit
c925062
ยท
1 Parent(s): 0445c27

Allow drivers to publish trips in their own style; add reply_instruction for no-match results; improve privacy prompts

Browse files

- Update driver_message handling: use when driver provides a custom ad (not just >2 emojis)
- Add reply_instruction to about_falzh, search_trips, create_driver_account, add_trip_by_driver tools for better AI response control
- Add _no_trip_reply_instruction helper to prevent narrow retries or false promises
- Update Arabic prompts: driver style flexibility, phone number privacy, FAQ improvements
- Update system prompts to direct unsure feature queries to about_falzh
- Add/update tests for new behaviors

app/ai/tool_schemas.py CHANGED
@@ -7,7 +7,8 @@ _ABOUT_FALZH = {
7
  "function": {
8
  "name": "about_falzh",
9
  "description": (
10
- "Retrieve official FALZH company, FAQ, policy, or pricing information."
 
11
  ),
12
  "parameters": {
13
  "type": "object",
@@ -246,9 +247,10 @@ _ADD_TRIP_BY_DRIVER = {
246
  "driver_message": {
247
  "type": "string",
248
  "description": (
249
- "Optional cleaned version of the driver's original message. "
250
- "Remove phone numbers, personal info, and driver name. "
251
- "Keep emojis and decorations. Only set when >2 emojis."
 
252
  ),
253
  },
254
  },
 
7
  "function": {
8
  "name": "about_falzh",
9
  "description": (
10
+ "Retrieve official FALZH company, FAQ, policy, pricing, or feature information. "
11
+ "Use before answering any FALZH capability/request you are unsure about."
12
  ),
13
  "parameters": {
14
  "type": "object",
 
247
  "driver_message": {
248
  "type": "string",
249
  "description": (
250
+ "Optional cleaned version of the driver's original trip ad when the "
251
+ "driver wrote it in a style likely meant for passengers. Remove phone "
252
+ "numbers, personal info, and driver name. Keep route info, dates, "
253
+ "times, emojis, and decorations."
254
  ),
255
  },
256
  },
app/services/conversation_service.py CHANGED
@@ -205,6 +205,10 @@ class ConversationService:
205
  "in the groups and we are impressed. Explain that we have registered them in "
206
  "FALZH so they can now send trips directly here instead of posting in groups. "
207
  "Show them how: just send the trip details (route, date, time) in chat. "
 
 
 
 
208
  "Explain the benefits: passengers find their trips via AI search, they get "
209
  "notified immediately when a passenger selects their trip, and registered "
210
  "drivers get priority visibility in search results. Tell them we will no "
 
205
  "in the groups and we are impressed. Explain that we have registered them in "
206
  "FALZH so they can now send trips directly here instead of posting in groups. "
207
  "Show them how: just send the trip details (route, date, time) in chat. "
208
+ "Tell them they may write each trip ad in their own preferred style/format, "
209
+ "and FALZH will extract the trip details automatically. Tell them personal "
210
+ "data such as phone numbers will be removed from public trip ads/cards and "
211
+ "shared only after a passenger selects the trip and wants to contact them. "
212
  "Explain the benefits: passengers find their trips via AI search, they get "
213
  "notified immediately when a passenger selects their trip, and registered "
214
  "drivers get priority visibility in search results. Tell them we will no "
app/tools/handlers.py CHANGED
@@ -76,6 +76,9 @@ class FalzhToolHandlers:
76
  data={
77
  "answer": "No matching FALZH policy or FAQ content was found.",
78
  "sources": [],
 
 
 
79
  },
80
  )
81
 
@@ -91,6 +94,10 @@ class FalzhToolHandlers:
91
  }
92
  for match in matches
93
  ],
 
 
 
 
94
  },
95
  )
96
 
@@ -228,6 +235,7 @@ class FalzhToolHandlers:
228
  "alternate_alert": alternate_alert,
229
  "sent_as_messages": False,
230
  "note": "No active matching trips were found.",
 
231
  },
232
  )
233
 
@@ -338,6 +346,13 @@ class FalzhToolHandlers:
338
  "name": self.customer.get("name"),
339
  "remoteJid": self.customer.get("remoteJid"),
340
  "message": "Driver account created successfully.",
 
 
 
 
 
 
 
341
  },
342
  )
343
 
@@ -592,8 +607,8 @@ class FalzhToolHandlers:
592
  use_driver_message = False
593
  if self.current_message:
594
  raw_msg = self.current_message.get("message") or ""
595
- if count_emojis(raw_msg) > 2:
596
- llm_message = _optional_string(arguments.get("driver_message"))
597
  driver_message = llm_message or raw_msg
598
  use_driver_message = True
599
 
@@ -1063,6 +1078,24 @@ def _alternate_time_alert(trips: list[dict[str, Any]]) -> str | None:
1063
  )
1064
 
1065
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1066
  def _trip_search_note(alternate_alert: str | None) -> str:
1067
  if alternate_alert:
1068
  return alternate_alert
 
76
  data={
77
  "answer": "No matching FALZH policy or FAQ content was found.",
78
  "sources": [],
79
+ "reply_instruction": (
80
+ "Apologize briefly. Do not guess or invent an answer."
81
+ ),
82
  },
83
  )
84
 
 
94
  }
95
  for match in matches
96
  ],
97
+ "reply_instruction": (
98
+ "Answer only from this context. If it does not clearly answer the user's "
99
+ "question, apologize briefly and say there is no confirmed information."
100
+ ),
101
  },
102
  )
103
 
 
235
  "alternate_alert": alternate_alert,
236
  "sent_as_messages": False,
237
  "note": "No active matching trips were found.",
238
+ "reply_instruction": _no_trip_reply_instruction(departure_request),
239
  },
240
  )
241
 
 
346
  "name": self.customer.get("name"),
347
  "remoteJid": self.customer.get("remoteJid"),
348
  "message": "Driver account created successfully.",
349
+ "reply_instruction": (
350
+ "Tell the driver in Arabic that they can publish each trip in their own "
351
+ "format/style and FALZH will extract the trip details automatically. Also "
352
+ "tell them personal data such as phone numbers will be removed from trip "
353
+ "ads/cards and shared with passengers only after a passenger selects the "
354
+ "trip and wants to contact them."
355
+ ),
356
  },
357
  )
358
 
 
607
  use_driver_message = False
608
  if self.current_message:
609
  raw_msg = self.current_message.get("message") or ""
610
+ llm_message = _optional_string(arguments.get("driver_message"))
611
+ if llm_message or count_emojis(raw_msg) > 2:
612
  driver_message = llm_message or raw_msg
613
  use_driver_message = True
614
 
 
1078
  )
1079
 
1080
 
1081
+ def _no_trip_reply_instruction(departure_request: Any) -> str:
1082
+ instruction = (
1083
+ "Tell the passenger briefly that no active matching trip is currently available. "
1084
+ "Do not promise to notify them when a future matching trip is added."
1085
+ )
1086
+ if departure_request.departure_date or departure_request.departure_time:
1087
+ instruction += (
1088
+ " Do not call search_trips again with a narrower/specific time for the same "
1089
+ "route unless the passenger provides new route/date information."
1090
+ )
1091
+ else:
1092
+ instruction += (
1093
+ " This was already a general search for the provided trip details; do not call "
1094
+ "search_trips again only to add a specific time for the same trip."
1095
+ )
1096
+ return instruction
1097
+
1098
+
1099
  def _trip_search_note(alternate_alert: str | None) -> str:
1100
  if alternate_alert:
1101
  return alternate_alert
prompts/falzh_info.md CHANGED
@@ -24,13 +24,13 @@
24
  ุงู„ุณุนุฑ ุชุญุฏุฏู‡ ุดุฑูƒุฉ ุงู„ุณุงุฆู‚ ุฃูˆ ุงู„ุณุงุฆู‚ ู†ูุณู‡. ูู„ุฒุฉ ูู‚ุท ุชุนุฑุถ ุงู„ุณุนุฑ ุงู„ู„ูŠ ุฃุฏุฎู„ู‡ ุงู„ุณุงุฆู‚ ุนู†ุฏ ู†ุดุฑ ุงู„ุฑุญู„ุฉ. ูู„ุฒุฉ ู„ุง ุชุญุฏุฏ ุงู„ุฃุณุนุงุฑ ูˆู„ุง ุชุชุนุงู…ู„ ููŠ ุงู„ู…ุฏููˆุนุงุช. ุฅุฐุง ูƒุงู† ุงู„ุณุนุฑ ู…ูˆ ู…ุญุฏุฏ ููŠ ุงู„ุฑุญู„ุฉุŒ ุงุณุฃู„ ุงู„ุณุงุฆู‚ ู…ุจุงุดุฑุฉ.
25
 
26
  ## ุฎุฏู…ุงุช ุงู„ุณุงุฆู‚ูŠู†
27
- ุชุณุฌูŠู„ ุญุณุงุจ ุณุงุฆู‚: ูŠุญุชุงุฌ ุงู„ุงุณู… ุงู„ูƒุงู…ู„. ุชุฑุจุท ูู„ุฒุฉ ุญุณุงุจูƒ ุจุฑู‚ู… ูˆุงุชุณุงุจูƒ. ุฅุถุงูุฉ ู…ุฑูƒุจุฉ: ุงุณู… ุงู„ู…ุฑูƒุจุฉ ู…ุทู„ูˆุจุŒ ุฑู‚ู… ุงู„ู„ูˆุญุฉ ูˆุนุฏุฏ ุงู„ู…ู‚ุงุนุฏ ุงุฎุชูŠุงุฑูŠ. ู†ุดุฑ ุฑุญู„ุฉ: ูŠู„ุฒู… ุงู„ู…ุฏูŠู†ุฉ (ู…ู† ูˆุฅู„ู‰)ุŒ ุงู„ุชุงุฑูŠุฎุŒ ุงู„ูุชุฑุฉ (ุตุจุงุญ/ุธู‡ุฑ/ู„ูŠู„). ุนุฏุฏ ุงู„ู…ู‚ุงุนุฏ ูˆุงู„ุณุนุฑ ุชูู…ู„ุฃ ุชู„ู‚ุงุฆูŠุงู‹ ู…ู† ุขุฎุฑ ุฑุญู„ุฉ ุฃูˆ ู…ุฑูƒุจุชูƒ ุงู„ู…ุณุฌู„ุฉ. ุชุนุฏูŠู„ ุฑุญู„ุฉ: ุชู‚ุฏุฑ ุชุบูŠุฑ ุงู„ูˆุฌู‡ุฉุŒ ุงู„ูˆู‚ุชุŒ ุงู„ุณูŠุงุฑุฉุŒ ุงู„ู…ู‚ุงุนุฏุŒ ุฃูˆ ุงู„ุณุนุฑ. ุชุงุฑูŠุฎ ุงู„ุฑุญู„ุฉ ู„ุง ูŠู…ูƒู† ุชุบูŠูŠุฑู‡ โ€” ู„ุงุฒู… ุชู†ุดุฆ ุฑุญู„ุฉ ุฌุฏูŠุฏุฉ. ุญุฐู ุฑุญู„ุฉ: ุชู„ุบูŠ ุงู„ุฑุญู„ุฉ ูˆุชุฑูุนู‡ุง ู…ู† ุงู„ุจุญุซ. ู…ู…ู†ูˆุน ูŠูƒูˆู† ุนู†ุฏูƒ ุฑุญู„ุชูŠู† ููŠ ู†ูุณ ุงู„ุชุงุฑูŠุฎ ูˆุงู„ูุชุฑุฉ.
28
 
29
  ## ุงู„ุณูŠุงุณุงุช
30
  ูู„ุฒุฉ ู„ุง ุชุญุฌุฒ ุฑุญู„ุงุช ูˆู„ุง ุชุคูƒุฏ ุญุฌูˆุฒุงุช. ุงุฎุชูŠุงุฑ ุงู„ุฑุญู„ุฉ ูŠุนู†ูŠ ุชุณุฌูŠู„ ุงู‡ุชู…ุงู… ูู‚ุท. ูู„ุฒุฉ ู„ุง ุชุชุนุงู…ู„ ููŠ ุงู„ู…ุฏููˆุนุงุช ู†ู‡ุงุฆูŠุงู‹. ุงู„ู…ุณุงูุฑ ูŠุชุญู…ู„ ู…ุณุคูˆู„ูŠุฉ ุชู‚ุฏูŠู… ู…ุนู„ูˆู…ุงุช ุตุญูŠุญุฉ ุนู† ูˆุฌู‡ุชู‡ ูˆูˆู‚ุชู‡ ูˆุนุฏุฏ ุงู„ู…ู‚ุงุนุฏ. ูู„ุฒุฉ ูู‚ุท ุชู‚ุฏู… ู…ุนู„ูˆู…ุงุช ุนู† ุฎุฏู…ุงุชู‡ุง โ€” ุชุฑูุถ ุงู„ุฅุฌุงุจุฉ ุนู† ุฃูŠ ู…ูˆุถูˆุน ุฎุงุฑุฌ ุงู„ุณูุฑ.
31
 
32
  ## ุงู„ุฎุตูˆุตูŠุฉ
33
- ุฑู‚ู… ูˆุงุชุณุงุจูƒ ูŠูุณุชุฎุฏู… ูู‚ุท ู„ุฑุจุท ุญุณุงุจูƒ ูˆุฅุฑุณุงู„ ุงู„ุฅุดุนุงุฑุงุช. ุฑู‚ู… ู‡ุงุชููƒ ูŠูุดุงุฑูƒ ู…ุน ุงู„ุณุงุฆู‚ ูู‚ุท ุนู†ุฏู…ุง ุชุฎุชุงุฑ ุฑุญู„ุฉุŒ ุนุดุงู† ูŠุชูˆุงุตู„ ู…ุนูƒ. ูู„ุฒุฉ ู„ุง ุชุดุงุฑูƒ ู…ุนู„ูˆู…ุงุชูƒ ู…ุน ุฃูŠ ุทุฑู ุขุฎุฑ.
34
 
35
  ## ุงู„ุฃุณุฆู„ุฉ ุงู„ุดุงุฆุนุฉ
36
  ุณ: ูˆุด ู‡ูŠ ูู„ุฒุฉุŸ ุฌ: ุฎุฏู…ุฉ ุณูุฑ ุนู„ู‰ ูˆุงุชุณุงุจ. ุชู‚ุฏุฑ ุชุจุญุซ ุนู† ุฑุญู„ุงุช ุณูŠุงุฑุงุช ูˆุจุงุตุงุช ูˆุชุฎุชุงุฑ ุฑุญู„ุฉ ูˆุชุชูˆุงุตู„ ู…ุน ุงู„ุณุงุฆู‚ โ€” ูƒู„ ุดูŠุก ุฏุงุฎู„ ูˆุงุชุณุงุจ.
@@ -42,10 +42,11 @@
42
  ุณ: ู‡ู„ ุฃู‚ุฏุฑ ุฃู„ุบูŠ ุงุฎุชูŠุงุฑูŠุŸ ุฌ: ุงุฎุชูŠุงุฑ ุงู„ุฑุญู„ุฉ ู‡ูˆ ุฅุฑุณุงู„ ุงู‡ุชู…ุงู… ูู‚ุท ูˆู„ุง ูŠูˆุฌุฏ ุฅู„ุบุงุก ู…ู† ุงู„ู†ุธุงู…. ุฅุฐุง ุชุบูŠุฑ ุฑุฃูŠูƒุŒ ุชูˆุงุตู„ ู…ุน ุงู„ุณุงุฆู‚ ู…ุจุงุดุฑุฉ.
43
  ุณ: ูƒูŠู ุฃุณุฌู„ ูƒุณุงุฆู‚ุŸ ุฌ: ู‚ูˆู„ "ุฃุจูŠ ุฃุณุฌู„ ูƒุณุงุฆู‚" ุฃูˆ ุงุฎุชุฑ ูˆุถุน ุงู„ุณุงุฆู‚. ูŠุญุชุงุฌ ุงู„ุงุณู… ุงู„ูƒุงู…ู„.
44
  ุณ: ูƒูŠู ุฃุจุญุซ ุนู† ุฑุญู„ุฉ ู„ุณุงุฆู‚ ู…ุนูŠู†ุŸ ุฌ: ู‚ูˆู„ "ุฃุจูŠ ุฑุญู„ุงุช ุงู„ุณุงุฆู‚ ุฃุญู…ุฏ" ุฃูˆ "ุฏูˆุฑ ุฑุญู„ุฉ ู…ุน ุงู„ุณุงุฆู‚". ูู„ุฒุฉ ุชุจุญุซ ููŠ ุงู„ุฑุญู„ุงุช ุงู„ู…ุชุงุญุฉ ู„ู‡ุงู„ุณุงุฆู‚ ูˆุชุฑุณู„ ู„ูƒ ุงู„ู†ุชุงุฆุฌ.
45
- ุณ: ูƒูŠู ุฃุถูŠู ุฑุญู„ุฉ ูƒุณุงุฆู‚ุŸ ุฌ: ู‚ูˆู„ "ุฃุจูŠ ุฃุถูŠู ุฑุญู„ุฉ ู…ู† ุนุฏู† ุฅู„ู‰ ุงู„ุญุฏูŠุฏุฉ ุงู„ุฌู…ุนุฉ ุงู„ุตุจุงุญ". ูู„ุฒุฉ ุชุทู„ุจ ุงู„ุชูุงุตูŠู„ ุงู„ู†ุงู‚ุตุฉ.
 
46
  ุณ: ูƒูŠู ุฃุนุฏู„ ุฃูˆ ุฃู„ุบูŠ ุฑุญู„ุฉุŸ ุฌ: ู‚ูˆู„ "ุฃุจูŠ ุฃุนุฏู„ ุฑุญู„ุฉ" ุฃูˆ "ุฃุจูŠ ุฃู„ุบูŠ ุฑุญู„ุฉ". ูู„ุฒุฉ ุชุฑุณู„ ู„ูƒ ู‚ุงุฆู…ุฉ ุฑุญู„ุงุชูƒ โ€” ุชุฑุฏ ุนู„ู‰ ุงู„ู„ูŠ ุชุจูŠู‡.
47
  ุณ: ู‡ู„ ููŠู‡ ุฑุญู„ุงุช ุจูŠู† ูƒู„ ุงู„ู…ุฏู†ุŸ ุฌ: ูู„ุฒุฉ ุชุนุฑุถ ุงู„ุฑุญู„ุงุช ุงู„ู„ูŠ ูŠู†ุดุฑู‡ุง ุงู„ุณุงุฆู‚ูŠู†. ุฅุฐุง ู…ุง ู„ู‚ูŠุช ุฑุญู„ุชูƒุŒ ุงุณุฃู„ ู…ุฑุฉ ุซุงู†ูŠุฉ ุฃูˆ ุชูˆุงุตู„ ู…ุน ุงู„ุฏุนู….
48
  ุณ: ู‡ู„ ุงู„ุฎุฏู…ุฉ ู…ุฌุงู†ูŠุฉุŸ ุฌ: ุงุณุชุฎุฏุงู… ูู„ุฒุฉ ู…ุฌุงู†ูŠ. ุงู„ุณุนุฑ ู„ู„ุณูุฑ ุชุชูู‚ ููŠู‡ ู…ุน ุงู„ุณุงุฆู‚.
49
 
50
  ## ุงู„ุฏุนู… ุงู„ูู†ูŠ
51
- ุฅุฐุง ู…ุง ู„ู‚ู‰ ุงู„ู…ุณุงุนุฏ ุฅุฌุงุจุฉ ุฃูˆ ุฑุญู„ุฉ ู…ู†ุงุณุจุฉุŒ ูŠุฌู…ุน ู…ุนู„ูˆู…ุงุช ุทู„ุจูƒ ูˆูŠุดุฑุญ ุฃู† ูุฑูŠู‚ ุงู„ุฏุนู… ุณูŠุชุงุจุน ู…ุนูƒ. ู„ู„ุชูˆุงุตู„ ู…ุน ุงู„ุฏุนู… ุงู„ูู†ูŠ: ุฃุฑุณู„ ุงุณุชูุณุงุฑูƒ ูˆุณู†ุฑุฏ ุนู„ูŠูƒ ููŠ ุฃู‚ุฑุจ ูˆู‚ุช.
 
24
  ุงู„ุณุนุฑ ุชุญุฏุฏู‡ ุดุฑูƒุฉ ุงู„ุณุงุฆู‚ ุฃูˆ ุงู„ุณุงุฆู‚ ู†ูุณู‡. ูู„ุฒุฉ ูู‚ุท ุชุนุฑุถ ุงู„ุณุนุฑ ุงู„ู„ูŠ ุฃุฏุฎู„ู‡ ุงู„ุณุงุฆู‚ ุนู†ุฏ ู†ุดุฑ ุงู„ุฑุญู„ุฉ. ูู„ุฒุฉ ู„ุง ุชุญุฏุฏ ุงู„ุฃุณุนุงุฑ ูˆู„ุง ุชุชุนุงู…ู„ ููŠ ุงู„ู…ุฏููˆุนุงุช. ุฅุฐุง ูƒุงู† ุงู„ุณุนุฑ ู…ูˆ ู…ุญุฏุฏ ููŠ ุงู„ุฑุญู„ุฉุŒ ุงุณุฃู„ ุงู„ุณุงุฆู‚ ู…ุจุงุดุฑุฉ.
25
 
26
  ## ุฎุฏู…ุงุช ุงู„ุณุงุฆู‚ูŠู†
27
+ ุชุณุฌูŠู„ ุญุณุงุจ ุณุงุฆู‚: ูŠุญุชุงุฌ ุงู„ุงุณู… ุงู„ูƒุงู…ู„. ุชุฑุจุท ูู„ุฒุฉ ุญุณุงุจูƒ ุจุฑู‚ู… ูˆุงุชุณุงุจูƒ. ุฅุถุงูุฉ ู…ุฑูƒุจุฉ: ุงุณู… ุงู„ู…ุฑูƒุจุฉ ู…ุทู„ูˆุจุŒ ุฑู‚ู… ุงู„ู„ูˆุญุฉ ูˆุนุฏุฏ ุงู„ู…ู‚ุงุนุฏ ุงุฎุชูŠุงุฑูŠ. ู†ุดุฑ ุฑุญู„ุฉ: ูŠู„ุฒู… ุงู„ู…ุฏูŠู†ุฉ (ู…ู† ูˆุฅู„ู‰)ุŒ ุงู„ุชุงุฑูŠุฎุŒ ุงู„ูุชุฑุฉ (ุตุจุงุญ/ุธู‡ุฑ/ู„ูŠู„). ูŠู‚ุฏุฑ ุงู„ุณุงุฆู‚ ูŠูƒุชุจ ุฅุนู„ุงู† ุงู„ุฑุญู„ุฉ ุจุตูŠุบุชู‡ ุงู„ุฎุงุตุฉ ูˆุจุฃุณู„ูˆุจู‡ ุงู„ู…ุนุชุงุฏุŒ ูˆูู„ุฒุฉ ุชุณุชุฎุฑุฌ ุชูุงุตูŠู„ ุงู„ุฑุญู„ุฉ ุชู„ู‚ุงุฆูŠุงู‹. ุนุฏุฏ ุงู„ู…ู‚ุงุนุฏ ูˆุงู„ุณุนุฑ ุชูู…ู„ุฃ ุชู„ู‚ุงุฆูŠุงู‹ ู…ู† ุขุฎุฑ ุฑุญู„ุฉ ุฃูˆ ู…ุฑูƒุจุชูƒ ุงู„ู…ุณุฌู„ุฉ. ุชุนุฏูŠู„ ุฑุญู„ุฉ: ุชู‚ุฏุฑ ุชุบูŠุฑ ุงู„ูˆุฌู‡ุฉุŒ ุงู„ูˆู‚ุชุŒ ุงู„ุณูŠุงุฑุฉุŒ ุงู„ู…ู‚ุงุนุฏุŒ ุฃูˆ ุงู„ุณุนุฑ. ุชุงุฑูŠุฎ ุงู„ุฑุญู„ุฉ ู„ุง ูŠู…ูƒู† ุชุบูŠูŠุฑู‡ โ€” ู„ุงุฒู… ุชู†ุดุฆ ุฑุญู„ุฉ ุฌุฏูŠุฏุฉ. ุญุฐู ุฑุญู„ุฉ: ุชู„ุบูŠ ุงู„ุฑุญู„ุฉ ูˆุชุฑูุนู‡ุง ู…ู† ุงู„ุจุญุซ. ู…ู…ู†ูˆุน ูŠูƒูˆู† ุนู†ุฏูƒ ุฑุญู„ุชูŠู† ููŠ ู†ูุณ ุงู„ุชุงุฑูŠุฎ ูˆุงู„ูุชุฑุฉ.
28
 
29
  ## ุงู„ุณูŠุงุณุงุช
30
  ูู„ุฒุฉ ู„ุง ุชุญุฌุฒ ุฑุญู„ุงุช ูˆู„ุง ุชุคูƒุฏ ุญุฌูˆุฒุงุช. ุงุฎุชูŠุงุฑ ุงู„ุฑุญู„ุฉ ูŠุนู†ูŠ ุชุณุฌูŠู„ ุงู‡ุชู…ุงู… ูู‚ุท. ูู„ุฒุฉ ู„ุง ุชุชุนุงู…ู„ ููŠ ุงู„ู…ุฏููˆุนุงุช ู†ู‡ุงุฆูŠุงู‹. ุงู„ู…ุณุงูุฑ ูŠุชุญู…ู„ ู…ุณุคูˆู„ูŠุฉ ุชู‚ุฏูŠู… ู…ุนู„ูˆู…ุงุช ุตุญูŠุญุฉ ุนู† ูˆุฌู‡ุชู‡ ูˆูˆู‚ุชู‡ ูˆุนุฏุฏ ุงู„ู…ู‚ุงุนุฏ. ูู„ุฒุฉ ูู‚ุท ุชู‚ุฏู… ู…ุนู„ูˆู…ุงุช ุนู† ุฎุฏู…ุงุชู‡ุง โ€” ุชุฑูุถ ุงู„ุฅุฌุงุจุฉ ุนู† ุฃูŠ ู…ูˆุถูˆุน ุฎุงุฑุฌ ุงู„ุณูุฑ.
31
 
32
  ## ุงู„ุฎุตูˆุตูŠุฉ
33
+ ุฑู‚ู… ูˆุงุชุณุงุจูƒ ูŠูุณุชุฎุฏู… ูู‚ุท ู„ุฑุจุท ุญุณุงุจูƒ ูˆุฅุฑุณุงู„ ุงู„ุฅุดุนุงุฑุงุช. ุฑู‚ู… ู‡ุงุชู ุงู„ุฑุงูƒุจ ูŠูุดุงุฑูƒ ู…ุน ุงู„ุณุงุฆู‚ ูู‚ุท ุนู†ุฏู…ุง ูŠุฎุชุงุฑ ุฑุญู„ุฉุŒ ุนุดุงู† ูŠุชูˆุงุตู„ ู…ุนู‡. ุจูŠุงู†ุงุช ุงู„ุณุงุฆู‚ ุงู„ุดุฎุตูŠุฉ ู…ุซู„ ุฑู‚ู… ุงู„ู‡ุงุชู ุชูุฒุงู„ ู…ู† ุฅุนู„ุงู† ุงู„ุฑุญู„ุฉ ูˆุจุทุงู‚ุงุช ุงู„ุนุฑุถุŒ ูˆู„ุง ุชุธู‡ุฑ ู„ู„ุฑุงูƒุจ ุฅู„ุง ุจุนุฏ ุฃู† ูŠุฎุชุงุฑ ุงู„ุฑุญู„ุฉ ูˆูŠุฑูŠุฏ ุงู„ุชูˆุงุตู„ ู…ุน ุงู„ุณุงุฆู‚. ูู„ุฒุฉ ู„ุง ุชุดุงุฑูƒ ู…ุนู„ูˆู…ุงุชูƒ ู…ุน ุฃูŠ ุทุฑู ุขุฎุฑ.
34
 
35
  ## ุงู„ุฃุณุฆู„ุฉ ุงู„ุดุงุฆุนุฉ
36
  ุณ: ูˆุด ู‡ูŠ ูู„ุฒุฉุŸ ุฌ: ุฎุฏู…ุฉ ุณูุฑ ุนู„ู‰ ูˆุงุชุณุงุจ. ุชู‚ุฏุฑ ุชุจุญุซ ุนู† ุฑุญู„ุงุช ุณูŠุงุฑุงุช ูˆุจุงุตุงุช ูˆุชุฎุชุงุฑ ุฑุญู„ุฉ ูˆุชุชูˆุงุตู„ ู…ุน ุงู„ุณุงุฆู‚ โ€” ูƒู„ ุดูŠุก ุฏุงุฎู„ ูˆุงุชุณุงุจ.
 
42
  ุณ: ู‡ู„ ุฃู‚ุฏุฑ ุฃู„ุบูŠ ุงุฎุชูŠุงุฑูŠุŸ ุฌ: ุงุฎุชูŠุงุฑ ุงู„ุฑุญู„ุฉ ู‡ูˆ ุฅุฑุณุงู„ ุงู‡ุชู…ุงู… ูู‚ุท ูˆู„ุง ูŠูˆุฌุฏ ุฅู„ุบุงุก ู…ู† ุงู„ู†ุธุงู…. ุฅุฐุง ุชุบูŠุฑ ุฑุฃูŠูƒุŒ ุชูˆุงุตู„ ู…ุน ุงู„ุณุงุฆู‚ ู…ุจุงุดุฑุฉ.
43
  ุณ: ูƒูŠู ุฃุณุฌู„ ูƒุณุงุฆู‚ุŸ ุฌ: ู‚ูˆู„ "ุฃุจูŠ ุฃุณุฌู„ ูƒุณุงุฆู‚" ุฃูˆ ุงุฎุชุฑ ูˆุถุน ุงู„ุณุงุฆู‚. ูŠุญุชุงุฌ ุงู„ุงุณู… ุงู„ูƒุงู…ู„.
44
  ุณ: ูƒูŠู ุฃุจุญุซ ุนู† ุฑุญู„ุฉ ู„ุณุงุฆู‚ ู…ุนูŠู†ุŸ ุฌ: ู‚ูˆู„ "ุฃุจูŠ ุฑุญู„ุงุช ุงู„ุณุงุฆู‚ ุฃุญู…ุฏ" ุฃูˆ "ุฏูˆุฑ ุฑุญู„ุฉ ู…ุน ุงู„ุณุงุฆู‚". ูู„ุฒุฉ ุชุจุญุซ ููŠ ุงู„ุฑุญู„ุงุช ุงู„ู…ุชุงุญุฉ ู„ู‡ุงู„ุณุงุฆู‚ ูˆุชุฑุณู„ ู„ูƒ ุงู„ู†ุชุงุฆุฌ.
45
+ ุณ: ู‡ู„ ุชู†ุจู‡ูˆู†ูŠ ุฅุฐุง ุชูˆูุฑุช ุฑุญู„ุฉ ุจุญุซุช ุนู†ู‡ุงุŸ ุฌ: ู„ุง ุชูˆุฌุฏ ุชู†ุจูŠู‡ุงุช ุชู„ู‚ุงุฆูŠุฉ ุนู†ุฏ ุฅุถุงูุฉ ุฑุญู„ุฉ ุฌุฏูŠุฏุฉ ุญุงู„ูŠุงู‹. ุชู‚ุฏุฑ ุชุจุญุซ ู…ุฑุฉ ุซุงู†ูŠุฉ ู„ุงุญู‚ุงู‹.
46
+ ุณ: ูƒูŠู ุฃุถูŠู ุฑุญู„ุฉ ูƒุณุงุฆู‚ุŸ ุฌ: ู‚ูˆู„ "ุฃุจูŠ ุฃุถูŠู ุฑุญู„ุฉ ู…ู† ุนุฏู† ุฅู„ู‰ ุงู„ุญุฏูŠุฏุฉ ุงู„ุฌู…ุนุฉ ุงู„ุตุจุงุญ" ุฃูˆ ุงูƒุชุจ ุฅุนู„ุงู†ูƒ ุจุชู†ุณูŠู‚ูƒ ุงู„ุฎุงุต. ูู„๏ฟฝ๏ฟฝุฉ ุชุณุชุฎุฑุฌ ุงู„ุชูุงุตูŠู„ ูˆุชุฒูŠู„ ุงู„ุฑู‚ู… ูˆุงู„ุจูŠุงู†ุงุช ุงู„ุดุฎุตูŠุฉ ู…ู† ุจุทุงู‚ุฉ ุงู„ุนุฑุถ.
47
  ุณ: ูƒูŠู ุฃุนุฏู„ ุฃูˆ ุฃู„ุบูŠ ุฑุญู„ุฉุŸ ุฌ: ู‚ูˆู„ "ุฃุจูŠ ุฃุนุฏู„ ุฑุญู„ุฉ" ุฃูˆ "ุฃุจูŠ ุฃู„ุบูŠ ุฑุญู„ุฉ". ูู„ุฒุฉ ุชุฑุณู„ ู„ูƒ ู‚ุงุฆู…ุฉ ุฑุญู„ุงุชูƒ โ€” ุชุฑุฏ ุนู„ู‰ ุงู„ู„ูŠ ุชุจูŠู‡.
48
  ุณ: ู‡ู„ ููŠู‡ ุฑุญู„ุงุช ุจูŠู† ูƒู„ ุงู„ู…ุฏู†ุŸ ุฌ: ูู„ุฒุฉ ุชุนุฑุถ ุงู„ุฑุญู„ุงุช ุงู„ู„ูŠ ูŠู†ุดุฑู‡ุง ุงู„ุณุงุฆู‚ูŠู†. ุฅุฐุง ู…ุง ู„ู‚ูŠุช ุฑุญู„ุชูƒุŒ ุงุณุฃู„ ู…ุฑุฉ ุซุงู†ูŠุฉ ุฃูˆ ุชูˆุงุตู„ ู…ุน ุงู„ุฏุนู….
49
  ุณ: ู‡ู„ ุงู„ุฎุฏู…ุฉ ู…ุฌุงู†ูŠุฉุŸ ุฌ: ุงุณุชุฎุฏุงู… ูู„ุฒุฉ ู…ุฌุงู†ูŠ. ุงู„ุณุนุฑ ู„ู„ุณูุฑ ุชุชูู‚ ููŠู‡ ู…ุน ุงู„ุณุงุฆู‚.
50
 
51
  ## ุงู„ุฏุนู… ุงู„ูู†ูŠ
52
+ ุฅุฐุง ู…ุง ู„ู‚ู‰ ุงู„ู…ุณุงุนุฏ ุฑุญู„ุฉ ู…ู†ุงุณุจุฉุŒ ูŠูˆุถุญ ุฃู†ู‡ ู„ุง ุชูˆุฌุฏ ุฑุญู„ุฉ ู…ุชุงุญุฉ ุญุงู„ูŠุงู‹ ูˆู„ุง ูŠุนุฏ ุจุชู†ุจูŠู‡ ุชู„ู‚ุงุฆูŠ ุนู†ุฏ ุฅุถุงูุฉ ุฑุญู„ุฉ ู„ุงุญู‚ุงู‹. ุฅุฐุง ุณูุฆู„ ุนู† ู…ูŠุฒุฉ ุฃูˆ ุทู„ุจ ูˆู„ู… ูŠุฌุฏ ู„ู‡ ุฅุฌุงุจุฉ ู…ูˆุซู‚ุฉ ููŠ ู…ุนู„ูˆู…ุงุช ูู„ุฒุฉุŒ ูŠุนุชุฐุฑ ูˆู„ุง ูŠุฎู…ู†. ู„ู„ุชูˆุงุตู„ ู…ุน ุงู„ุฏุนู… ุงู„ูู†ูŠ: ุฃุฑุณู„ ุงุณุชูุณุงุฑูƒ ูˆุณู†ุฑุฏ ุนู„ูŠูƒ ููŠ ุฃู‚ุฑุจ ูˆู‚ุช.
prompts/system.md CHANGED
@@ -4,7 +4,10 @@ Rules:
4
  - Reply in sender's language (Arabic/English). Be concise, warm, practical.
5
  - When writing in Arabic, the brand name must always be spelled exactly "ูู„ุฒุฉ" (ู-ู„-ุฒ-ุง). Never use "ูู„ุณุง", "ูู„ุธ", "ูู„ุธุฉ", "ูู„ุฒ", or any other variant.
6
  - For new users: be extra warm and welcoming. Greet them, introduce FALZH briefly, and guide them to their first action.
7
- - Use `about_falzh` for company/FAQ/policy/pricing/support.
 
 
 
8
  - Tool args: Arabic for departure/destination/vehicle_type/time labels. English for digits/times.
9
  - Never invent trips, prices, drivers, or bookings.
10
  - Never pass phone_number; WhatsApp supplies it.
 
4
  - Reply in sender's language (Arabic/English). Be concise, warm, practical.
5
  - When writing in Arabic, the brand name must always be spelled exactly "ูู„ุฒุฉ" (ู-ู„-ุฒ-ุง). Never use "ูู„ุณุง", "ูู„ุธ", "ูู„ุธุฉ", "ูู„ุฒ", or any other variant.
6
  - For new users: be extra warm and welcoming. Greet them, introduce FALZH briefly, and guide them to their first action.
7
+ - Use `about_falzh` for company/FAQ/policy/pricing/support, and for any feature,
8
+ capability, or FALZH request you are unsure about. Answer only from the returned
9
+ context. If it does not clearly answer the question, apologize briefly and say
10
+ you do not have confirmed information.
11
  - Tool args: Arabic for departure/destination/vehicle_type/time labels. English for digits/times.
12
  - Never invent trips, prices, drivers, or bookings.
13
  - Never pass phone_number; WhatsApp supplies it.
prompts/system_driver.md CHANGED
@@ -2,6 +2,10 @@
2
  - check_driver_trips for upcoming trips.
3
  - add_driver_car to register vehicle (name required).
4
  - add_trip_by_driver(route, departure_date, departure_time). If it returns an error, tell the driver the exact error in Arabic and do NOT call any other tools.
 
 
 
 
5
  - Accept natural date expressions from the driver (ุงู„ูŠูˆู…/ุจูƒุฑุฉ/ุบุฏุง/ูŠูˆู… ุงู„ุฎู…ูŠุณ/ุจุนุฏ ูŠูˆู…ูŠู† etc.)
6
  and convert them to YYYY-MM-DD using the current date in context. Never ask the driver
7
  to rewrite the date in a specific format.
@@ -10,7 +14,9 @@
10
  - To search/book as traveler: switch_to_passenger (name optional).
11
 
12
  Driver message cleanup:
13
- - When the driver's message has more than 2 emojis, set `driver_message` in add_trip_by_driver.
 
 
14
  - Remove ALL phone numbers AND the surrounding text (e.g. "ู„ู„ุชูˆุงุตู„ ุงู„ุงุชุตุงู„ ุนู„ู‰ ุงู„ุฑู‚ู… ุงู„ุขุชูŠ๐Ÿ“ž:-770026665" โ†’ remove entirely).
15
  - Remove the driver's name wherever it appears in the message.
16
  - Keep emojis, decorative elements, route info, dates, times, and seat info.
 
2
  - check_driver_trips for upcoming trips.
3
  - add_driver_car to register vehicle (name required).
4
  - add_trip_by_driver(route, departure_date, departure_time). If it returns an error, tell the driver the exact error in Arabic and do NOT call any other tools.
5
+ - If the driver asks how to publish trips, tell them they can write the trip ad in
6
+ their own style/format. You extract the route, date, time, seats, price, and
7
+ vehicle automatically, and the cleaned ad can be shown to passengers in the
8
+ driver's preferred style.
9
  - Accept natural date expressions from the driver (ุงู„ูŠูˆู…/ุจูƒุฑุฉ/ุบุฏุง/ูŠูˆู… ุงู„ุฎู…ูŠุณ/ุจุนุฏ ูŠูˆู…ูŠู† etc.)
10
  and convert them to YYYY-MM-DD using the current date in context. Never ask the driver
11
  to rewrite the date in a specific format.
 
14
  - To search/book as traveler: switch_to_passenger (name optional).
15
 
16
  Driver message cleanup:
17
+ - When the driver's message is a custom trip ad they likely want passengers to see,
18
+ set `driver_message` in add_trip_by_driver after cleaning it. For plain commands
19
+ like "add a trip from X to Y", omit `driver_message`.
20
  - Remove ALL phone numbers AND the surrounding text (e.g. "ู„ู„ุชูˆุงุตู„ ุงู„ุงุชุตุงู„ ุนู„ู‰ ุงู„ุฑู‚ู… ุงู„ุขุชูŠ๐Ÿ“ž:-770026665" โ†’ remove entirely).
21
  - Remove the driver's name wherever it appears in the message.
22
  - Keep emojis, decorative elements, route info, dates, times, and seat info.
prompts/system_new_user.md CHANGED
@@ -4,7 +4,7 @@ It is the sender's first message. Welcome them warmly and introduce FALZH with a
4
  2. Explain what FALZH is in 1 sentence: a WhatsApp travel service powered by AI โ€” no app to install.
5
  3. Give a quick how-to guide showing both paths clearly:
6
  - ุงู„ู…ุณุงูุฑ: ุงูƒุชุจ ูˆุฌู‡ุชูƒ (ู…ุซู„ุงู‹ "ู…ู† ุตู†ุนุงุก ุฅู„ู‰ ุนุฏู† ุจูƒุฑุฉ ุงู„ุตุจุงุญ") โ† ูู„ุฒุฉ ุชุจุญุซ โ† ุฑุฏ ุนู„ู‰ ุงู„ุจุทุงู‚ุฉ โ† ุชุชูˆุงุตู„ ู…ุน ุงู„ุณุงุฆู‚. Do NOT ask for name.
7
- - ุงู„ุณุงุฆู‚: ุณุฌู„ ูƒุณุงุฆู‚ โ† ุฃุถู ู…ุฑูƒุจุชูƒ โ† ุงู†ุดุฑ ุฑุญู„ุงุชูƒ. Collect their full name, then create_driver_account(name) -> switch_to_driver.
8
  4. Use emojis (๐Ÿš—, ๐Ÿšš, etc.) and friendly tone. Be warm, not robotic.
9
  5. Keep it short โ€” 4-5 lines max. Do not over-explain.
10
  6. Never ask for name of a passenger. Only ask for name if they want to become a driver.
 
4
  2. Explain what FALZH is in 1 sentence: a WhatsApp travel service powered by AI โ€” no app to install.
5
  3. Give a quick how-to guide showing both paths clearly:
6
  - ุงู„ู…ุณุงูุฑ: ุงูƒุชุจ ูˆุฌู‡ุชูƒ (ู…ุซู„ุงู‹ "ู…ู† ุตู†ุนุงุก ุฅู„ู‰ ุนุฏู† ุจูƒุฑุฉ ุงู„ุตุจุงุญ") โ† ูู„ุฒุฉ ุชุจุญุซ โ† ุฑุฏ ุนู„ู‰ ุงู„ุจุทุงู‚ุฉ โ† ุชุชูˆุงุตู„ ู…ุน ุงู„ุณุงุฆู‚. Do NOT ask for name.
7
+ - ุงู„ุณุงุฆู‚: ุณุฌู„ ูƒุณุงุฆู‚ โ† ุฃุฑุณู„ ุฅุนู„ุงู† ุฑุญู„ุชูƒ ุจุชู†ุณูŠู‚ูƒ ุงู„ุฎุงุต โ† ูู„ุฒุฉ ุชุณุชุฎุฑุฌ ุงู„ุชูุงุตูŠู„ ูˆุชุนุฑุถ ุงู„ุฅุนู„ุงู† ุจุนุฏ ุฅุฒุงู„ุฉ ุงู„ุฑู‚ู… ูˆุงู„ุจูŠุงู†ุงุช ุงู„ุดุฎุตูŠุฉ. Collect their full name, then create_driver_account(name) -> switch_to_driver.
8
  4. Use emojis (๐Ÿš—, ๐Ÿšš, etc.) and friendly tone. Be warm, not robotic.
9
  5. Keep it short โ€” 4-5 lines max. Do not over-explain.
10
  6. Never ask for name of a passenger. Only ask for name if they want to become a driver.
prompts/system_passenger.md CHANGED
@@ -1,5 +1,6 @@
1
  - search_trips as soon as the user mentions a departure, destination, or driver name. You do not need all details โ€” the tool accepts partial info. Only ask a follow-up if neither departure, destination, nor driver name was mentioned.
2
  - When search_trips finds matches, trip cards are sent to the user automatically along with a prompt to choose one. Do NOT add any text after calling search_trips โ€” the tool will handle the response.
 
3
  - When the user replies to a trip card, immediately call select_trip โ€” trip_id is auto-detected and seats default to 1. Do not ask for seat count or other details.
4
  - Selections are not reservations โ€” seats are not held.
5
  - After successful select_trip, tell the passenger the driver's phone number from driver_phone so they can contact the driver directly to arrange the booking.
 
1
  - search_trips as soon as the user mentions a departure, destination, or driver name. You do not need all details โ€” the tool accepts partial info. Only ask a follow-up if neither departure, destination, nor driver name was mentioned.
2
  - When search_trips finds matches, trip cards are sent to the user automatically along with a prompt to choose one. Do NOT add any text after calling search_trips โ€” the tool will handle the response.
3
+ - When search_trips returns no matches, follow the tool's `reply_instruction`. Do not promise alerts or notifications when a future matching trip is added.
4
  - When the user replies to a trip card, immediately call select_trip โ€” trip_id is auto-detected and seats default to 1. Do not ask for seat count or other details.
5
  - Selections are not reservations โ€” seats are not held.
6
  - After successful select_trip, tell the passenger the driver's phone number from driver_phone so they can contact the driver directly to arrange the booking.
tests/test_conversation_service.py CHANGED
@@ -180,6 +180,8 @@ async def test_returning_driver_gets_welcome_and_upgraded_to_driver(settings):
180
  system_contents = [m["content"] for m in ai_messages if m["role"] == "system"]
181
  assert any("ูู‡ุฏ" in msg for msg in system_contents)
182
  assert any("group" in msg.lower() or "ู…ุฌู…ูˆุนุงุช" in msg for msg in system_contents)
 
 
183
 
184
  # user_mode should be upgraded to 'driver'
185
  assert customer["user_mode"] == "driver"
@@ -225,4 +227,3 @@ async def test_conversation_uses_driver_tools_when_user_mode_is_driver(settings)
225
  }
226
 
227
 
228
-
 
180
  system_contents = [m["content"] for m in ai_messages if m["role"] == "system"]
181
  assert any("ูู‡ุฏ" in msg for msg in system_contents)
182
  assert any("group" in msg.lower() or "ู…ุฌู…ูˆุนุงุช" in msg for msg in system_contents)
183
+ assert any("own preferred style/format" in msg for msg in system_contents)
184
+ assert any("phone numbers will be removed" in msg for msg in system_contents)
185
 
186
  # user_mode should be upgraded to 'driver'
187
  assert customer["user_mode"] == "driver"
 
227
  }
228
 
229
 
 
tests/test_tools.py CHANGED
@@ -79,6 +79,17 @@ async def test_about_falzh_returns_retrieved_context():
79
  assert embeddings.query_texts == ["What is FALZH?"]
80
 
81
 
 
 
 
 
 
 
 
 
 
 
 
82
  @pytest.mark.asyncio
83
  async def test_search_trips_uses_supabase_vector_matches_and_validates_business_rules():
84
  repository = FakeRepository()
@@ -136,6 +147,26 @@ async def test_search_trips_reports_no_matches():
136
  assert result.ok is True
137
  assert result.data["matches"] == []
138
  assert "No active" in result.data["note"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
139
 
140
 
141
  @pytest.mark.asyncio
@@ -311,6 +342,8 @@ async def test_create_driver_account_uses_sender_phone():
311
  assert result.ok is True
312
  assert repository.created_drivers[0]["customers"]["remoteJid"] == "967700000010"
313
  assert result.data["driver_id"] == "driver-1"
 
 
314
 
315
 
316
  @pytest.mark.asyncio
@@ -662,6 +695,42 @@ async def test_add_trip_by_driver_uses_llm_cleaned_message():
662
  assert created_trip["driver_message"] == cleaned_msg
663
 
664
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
665
  @pytest.mark.asyncio
666
  async def test_add_trip_by_driver_no_driver_message_with_few_emojis():
667
  repository = FakeRepository()
 
79
  assert embeddings.query_texts == ["What is FALZH?"]
80
 
81
 
82
+ @pytest.mark.asyncio
83
+ async def test_about_falzh_no_match_instructs_apology():
84
+ handlers = make_handlers()
85
+
86
+ result = await handlers.about_falzh({"query": "Do you support rewards?", "language": "en"})
87
+
88
+ assert result.ok is True
89
+ assert result.data["sources"] == []
90
+ assert "Do not guess" in result.data["reply_instruction"]
91
+
92
+
93
  @pytest.mark.asyncio
94
  async def test_search_trips_uses_supabase_vector_matches_and_validates_business_rules():
95
  repository = FakeRepository()
 
147
  assert result.ok is True
148
  assert result.data["matches"] == []
149
  assert "No active" in result.data["note"]
150
+ assert "Do not promise to notify" in result.data["reply_instruction"]
151
+ assert "already a general search" in result.data["reply_instruction"]
152
+
153
+
154
+ @pytest.mark.asyncio
155
+ async def test_search_trips_no_timed_match_instructs_no_narrow_retry():
156
+ handlers = make_handlers()
157
+
158
+ result = await handlers.search_trips(
159
+ {
160
+ "departure": "Aden",
161
+ "destination": "Sana'a",
162
+ "travel_date": "2026-12-01",
163
+ "travel_time": "morning",
164
+ }
165
+ )
166
+
167
+ assert result.ok is True
168
+ assert result.data["matches"] == []
169
+ assert "narrower/specific time" in result.data["reply_instruction"]
170
 
171
 
172
  @pytest.mark.asyncio
 
342
  assert result.ok is True
343
  assert repository.created_drivers[0]["customers"]["remoteJid"] == "967700000010"
344
  assert result.data["driver_id"] == "driver-1"
345
+ assert "own format/style" in result.data["reply_instruction"]
346
+ assert "phone numbers" in result.data["reply_instruction"]
347
 
348
 
349
  @pytest.mark.asyncio
 
695
  assert created_trip["driver_message"] == cleaned_msg
696
 
697
 
698
+ @pytest.mark.asyncio
699
+ async def test_add_trip_by_driver_uses_cleaned_message_without_many_emojis():
700
+ repository = FakeRepository()
701
+ repository.drivers_by_remote_jid["967700000010"] = {
702
+ "id": "driver-1",
703
+ "name": "Ali",
704
+ "remoteJid": "967700000010",
705
+ }
706
+ repository.driver_cars_by_driver["driver-1"] = [
707
+ {"id": "car-1", "car_type": "SUV", "seat_count": 4},
708
+ ]
709
+ raw_msg = "ุฑุญู„ุฉ ูŠูˆู…ูŠุฉ ู…ู† ุนุฏู† ุฅู„ู‰ ุงู„ู…ูƒู„ุง ู„ู„ุชูˆุงุตู„ 770026665"
710
+ cleaned_msg = "ุฑุญู„ุฉ ูŠูˆู…ูŠุฉ ู…ู† ุนุฏู† ุฅู„ู‰ ุงู„ู…ูƒู„ุง"
711
+ handlers = make_handlers(repository=repository, sender_phone="967700000010")
712
+ handlers.current_message = {"message": raw_msg}
713
+
714
+ result = await handlers.add_trip_by_driver(
715
+ {
716
+ "departure": "ุนุฏู†",
717
+ "destination": "ุงู„ู…ูƒู„ุง",
718
+ "departure_date": "2026-06-10",
719
+ "departure_time": "morning",
720
+ "vehicle_type": "SUV",
721
+ "available_seats": 2,
722
+ "total_seats": 4,
723
+ "price": 50,
724
+ "driver_message": cleaned_msg,
725
+ }
726
+ )
727
+
728
+ assert result.ok is True
729
+ created_trip = repository.created_trips[-1]
730
+ assert created_trip["use_driver_message"] is True
731
+ assert created_trip["driver_message"] == cleaned_msg
732
+
733
+
734
  @pytest.mark.asyncio
735
  async def test_add_trip_by_driver_no_driver_message_with_few_emojis():
736
  repository = FakeRepository()