Spaces:
Running
Running
cbfgh
Browse files
app.py
CHANGED
|
@@ -233,6 +233,9 @@ def finalize_booking(user: str) -> str:
|
|
| 233 |
return f"All set — your booking is confirmed with code {code}. We’ll call you shortly to confirm details."
|
| 234 |
|
| 235 |
# ---------- Core reply engine ----------
|
|
|
|
|
|
|
|
|
|
| 236 |
def secretary_reply(text: str, user: str = "unknown") -> str:
|
| 237 |
raw = text or ""
|
| 238 |
t = normalize(raw)
|
|
@@ -260,16 +263,7 @@ def secretary_reply(text: str, user: str = "unknown") -> str:
|
|
| 260 |
return friendly(LAMAKI_SERVICES)
|
| 261 |
return friendly(LD_EVENTS_SERVICES)
|
| 262 |
|
| 263 |
-
# ---
|
| 264 |
-
for event, packages in EVENT_TYPES.items():
|
| 265 |
-
if event in t:
|
| 266 |
-
suggestions = []
|
| 267 |
-
for p in packages:
|
| 268 |
-
p_norm = normalize(p)
|
| 269 |
-
suggestions.append(f"- {p}: {REPLY.get(p_norm, '')}")
|
| 270 |
-
return friendly(f"For a {event} we recommend:\n" + "\n".join(suggestions) + "\nWhich one would you like?")
|
| 271 |
-
|
| 272 |
-
# --- Tent guest detection (e.g., "tent for 250 people") ---
|
| 273 |
m = re.search(r"(tent|tents?).{0,20}?(\d{1,4})", raw, flags=re.I)
|
| 274 |
if m:
|
| 275 |
guests = int(m.group(2))
|
|
@@ -294,7 +288,6 @@ def secretary_reply(text: str, user: str = "unknown") -> str:
|
|
| 294 |
|
| 295 |
# --- Price / Cost enquiries ---
|
| 296 |
if any(w in t for w in ("price", "cost", "how much", "rates", "fee", "charge")):
|
| 297 |
-
# if user previously selected an item
|
| 298 |
last = LAST_ITEM.get(user)
|
| 299 |
if last and normalize(last) in REPLY:
|
| 300 |
return friendly(REPLY[normalize(last)])
|
|
@@ -305,31 +298,60 @@ def secretary_reply(text: str, user: str = "unknown") -> str:
|
|
| 305 |
return friendly(REPLY[key])
|
| 306 |
return friendly("Please tell me which item or package you'd like the price for (e.g., 'gold package' or 'double 18').")
|
| 307 |
|
| 308 |
-
# ---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 309 |
for alias, key in ITEM_ALIASES.items():
|
| 310 |
if alias in t:
|
| 311 |
LAST_ITEM[user] = key
|
| 312 |
-
|
|
|
|
|
|
|
|
|
|
| 313 |
|
| 314 |
-
# --- Exact REPLY keys match
|
| 315 |
for k in list(REPLY.keys()):
|
| 316 |
if k in t:
|
| 317 |
LAST_ITEM[user] = k
|
|
|
|
| 318 |
if "package" in k or "combo" in k:
|
| 319 |
-
|
| 320 |
-
|
| 321 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 322 |
|
| 323 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 324 |
if re.search(r"\b(book|reserve|schedule|site visit|site-visit|sitevisit)\b", t):
|
| 325 |
-
# detect site visit vs event booking
|
| 326 |
if "site" in t:
|
| 327 |
return friendly(start_booking(user, "site_visit"))
|
| 328 |
if "consult" in t:
|
| 329 |
return friendly(start_booking(user, "consultation"))
|
| 330 |
return friendly(start_booking(user, "event_booking"))
|
| 331 |
|
| 332 |
-
# --- Adding booking details: date / guests / location ---
|
| 333 |
dt = find_date_in_text(raw)
|
| 334 |
if dt:
|
| 335 |
add_booking_detail(user, "date", dt)
|
|
@@ -347,12 +369,10 @@ def secretary_reply(text: str, user: str = "unknown") -> str:
|
|
| 347 |
add_booking_detail(user, "location", loc)
|
| 348 |
return friendly(f"Noted venue: {loc}. Any special requests or additional gear?")
|
| 349 |
|
| 350 |
-
# --- finalize booking request ---
|
| 351 |
if any(pat in t for pat in ("confirm booking", "finalize booking", "complete booking", "confirm reservation", "confirm my booking")):
|
| 352 |
res = finalize_booking(user)
|
| 353 |
return friendly(res)
|
| 354 |
|
| 355 |
-
# --- Check booking by code (8 hex chars) ---
|
| 356 |
mcode = re.search(r"\b([A-F0-9]{8})\b", raw, flags=re.I)
|
| 357 |
if mcode:
|
| 358 |
code = mcode.group(1).upper()
|
|
@@ -361,17 +381,14 @@ def secretary_reply(text: str, user: str = "unknown") -> str:
|
|
| 361 |
return friendly(f"Booking {code} was created on {info.get('confirmed_at')}. Details: {info}")
|
| 362 |
return friendly("Sorry, I can't find that booking code. Please check and send it again.")
|
| 363 |
|
| 364 |
-
# --- Lamaki construction queries fallback ---
|
| 365 |
if any(w in t for w in LAMAKI_WORDS):
|
| 366 |
return friendly(LAMAKI_SERVICES)
|
| 367 |
|
| 368 |
-
# --- Short ambiguous inputs prompt ---
|
| 369 |
if len(t.split()) <= 2:
|
| 370 |
return friendly("Could you provide a few more details? For example: 'price for silver package' or 'book site visit 2025-12-05 200 guests at ABC Hall'.")
|
| 371 |
|
| 372 |
-
# --- Default fallback helpful prompt ---
|
| 373 |
return friendly("I’m here to help — tell me the item, package, or service you want and I’ll assist from there.")
|
| 374 |
-
|
| 375 |
"""
|
| 376 |
Flask endpoints, health check, and run instructions.
|
| 377 |
"""
|
|
|
|
| 233 |
return f"All set — your booking is confirmed with code {code}. We’ll call you shortly to confirm details."
|
| 234 |
|
| 235 |
# ---------- Core reply engine ----------
|
| 236 |
+
# ---------- Core reply engine (improved) ----------
|
| 237 |
+
from difflib import get_close_matches
|
| 238 |
+
|
| 239 |
def secretary_reply(text: str, user: str = "unknown") -> str:
|
| 240 |
raw = text or ""
|
| 241 |
t = normalize(raw)
|
|
|
|
| 263 |
return friendly(LAMAKI_SERVICES)
|
| 264 |
return friendly(LD_EVENTS_SERVICES)
|
| 265 |
|
| 266 |
+
# --- Tent guest detection ---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 267 |
m = re.search(r"(tent|tents?).{0,20}?(\d{1,4})", raw, flags=re.I)
|
| 268 |
if m:
|
| 269 |
guests = int(m.group(2))
|
|
|
|
| 288 |
|
| 289 |
# --- Price / Cost enquiries ---
|
| 290 |
if any(w in t for w in ("price", "cost", "how much", "rates", "fee", "charge")):
|
|
|
|
| 291 |
last = LAST_ITEM.get(user)
|
| 292 |
if last and normalize(last) in REPLY:
|
| 293 |
return friendly(REPLY[normalize(last)])
|
|
|
|
| 298 |
return friendly(REPLY[key])
|
| 299 |
return friendly("Please tell me which item or package you'd like the price for (e.g., 'gold package' or 'double 18').")
|
| 300 |
|
| 301 |
+
# --- Generic gear suggestions ---
|
| 302 |
+
if "mixer" in t:
|
| 303 |
+
options = ["mixer wing", "mixer skytone"]
|
| 304 |
+
suggestions = "\n".join(f"- {opt}: {REPLY[normalize(opt)]}" for opt in options)
|
| 305 |
+
return friendly(f"We have the following mixers:\n{suggestions}")
|
| 306 |
+
|
| 307 |
+
if "line array" in t or "line arrays" in t:
|
| 308 |
+
return friendly(REPLY.get("line array", "Line arrays are available — please specify how many or which combo you want."))
|
| 309 |
+
|
| 310 |
+
if "sub" in t or "18" in t:
|
| 311 |
+
subs = ["single 18", "double 18"]
|
| 312 |
+
suggestions = "\n".join(f"- {opt}: {REPLY[normalize(opt)]}" for opt in subs)
|
| 313 |
+
return friendly(f"We have the following subwoofers:\n{suggestions}")
|
| 314 |
+
|
| 315 |
+
# --- Alias matching (safe) ---
|
| 316 |
for alias, key in ITEM_ALIASES.items():
|
| 317 |
if alias in t:
|
| 318 |
LAST_ITEM[user] = key
|
| 319 |
+
reply_text = REPLY.get(normalize(key))
|
| 320 |
+
if not reply_text:
|
| 321 |
+
reply_text = f"I found {key}, but price/description not available."
|
| 322 |
+
return friendly(reply_text)
|
| 323 |
|
| 324 |
+
# --- Exact REPLY keys match ---
|
| 325 |
for k in list(REPLY.keys()):
|
| 326 |
if k in t:
|
| 327 |
LAST_ITEM[user] = k
|
| 328 |
+
base = REPLY[k]
|
| 329 |
if "package" in k or "combo" in k:
|
| 330 |
+
base += " If you'd like to book this, tell me the date, approximate guest count, and venue."
|
| 331 |
+
return friendly(base)
|
| 332 |
+
|
| 333 |
+
# --- Fuzzy matching ---
|
| 334 |
+
def fuzzy_lookup(text):
|
| 335 |
+
keys = list(REPLY.keys()) + list(ITEM_ALIASES.keys())
|
| 336 |
+
matches = get_close_matches(text, keys, n=1, cutoff=0.5)
|
| 337 |
+
if matches:
|
| 338 |
+
return ITEM_ALIASES.get(matches[0], matches[0])
|
| 339 |
+
return None
|
| 340 |
|
| 341 |
+
fuzzy_key = fuzzy_lookup(t)
|
| 342 |
+
if fuzzy_key:
|
| 343 |
+
LAST_ITEM[user] = fuzzy_key
|
| 344 |
+
reply_text = REPLY.get(normalize(fuzzy_key), f"I found {fuzzy_key}, but price/description not available.")
|
| 345 |
+
return friendly(reply_text)
|
| 346 |
+
|
| 347 |
+
# --- Booking flow ---
|
| 348 |
if re.search(r"\b(book|reserve|schedule|site visit|site-visit|sitevisit)\b", t):
|
|
|
|
| 349 |
if "site" in t:
|
| 350 |
return friendly(start_booking(user, "site_visit"))
|
| 351 |
if "consult" in t:
|
| 352 |
return friendly(start_booking(user, "consultation"))
|
| 353 |
return friendly(start_booking(user, "event_booking"))
|
| 354 |
|
|
|
|
| 355 |
dt = find_date_in_text(raw)
|
| 356 |
if dt:
|
| 357 |
add_booking_detail(user, "date", dt)
|
|
|
|
| 369 |
add_booking_detail(user, "location", loc)
|
| 370 |
return friendly(f"Noted venue: {loc}. Any special requests or additional gear?")
|
| 371 |
|
|
|
|
| 372 |
if any(pat in t for pat in ("confirm booking", "finalize booking", "complete booking", "confirm reservation", "confirm my booking")):
|
| 373 |
res = finalize_booking(user)
|
| 374 |
return friendly(res)
|
| 375 |
|
|
|
|
| 376 |
mcode = re.search(r"\b([A-F0-9]{8})\b", raw, flags=re.I)
|
| 377 |
if mcode:
|
| 378 |
code = mcode.group(1).upper()
|
|
|
|
| 381 |
return friendly(f"Booking {code} was created on {info.get('confirmed_at')}. Details: {info}")
|
| 382 |
return friendly("Sorry, I can't find that booking code. Please check and send it again.")
|
| 383 |
|
|
|
|
| 384 |
if any(w in t for w in LAMAKI_WORDS):
|
| 385 |
return friendly(LAMAKI_SERVICES)
|
| 386 |
|
|
|
|
| 387 |
if len(t.split()) <= 2:
|
| 388 |
return friendly("Could you provide a few more details? For example: 'price for silver package' or 'book site visit 2025-12-05 200 guests at ABC Hall'.")
|
| 389 |
|
|
|
|
| 390 |
return friendly("I’m here to help — tell me the item, package, or service you want and I’ll assist from there.")
|
| 391 |
+
|
| 392 |
"""
|
| 393 |
Flask endpoints, health check, and run instructions.
|
| 394 |
"""
|