Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -539,8 +539,6 @@ def clean_item_query(text: str) -> str:
|
|
| 539 |
"ุนูุฏู", "ุนูุฏูุง",
|
| 540 |
"ููู", "ูููุง",
|
| 541 |
"ุจููุฏู
", "ุจุชูุฏู
"
|
| 542 |
-
"ููุณู", "ุนูุฏู",
|
| 543 |
-
|
| 544 |
]
|
| 545 |
|
| 546 |
for w in remove_words:
|
|
@@ -648,11 +646,11 @@ def name_search(query, top_k=10, category=None):
|
|
| 648 |
matched = df[df["name_score"] > 0].copy()
|
| 649 |
|
| 650 |
if category and not matched.empty:
|
| 651 |
-
|
| 652 |
-
cat_match = matched[matched["
|
| 653 |
-
|
| 654 |
-
|
| 655 |
-
|
| 656 |
|
| 657 |
return matched.sort_values("name_score", ascending=False).head(top_k)
|
| 658 |
|
|
@@ -1059,11 +1057,11 @@ def build_response(place, intent, category=None, total_results=1):
|
|
| 1059 |
|
| 1060 |
def handle_detail(text, place):
|
| 1061 |
if not place:
|
| 1062 |
-
return "ู
|
| 1063 |
t = norm(text)
|
| 1064 |
name = place.get("name", "ุงูู
ูุงู")
|
| 1065 |
|
| 1066 |
-
if any(w in t for w in ["ุจููุชุญ","ุจุชูุชุญ","ู
ูุงุนูุฏ","ุงู
ุชู","ุงู
ุชู","ุจูููู","ุจุชููู"]):
|
| 1067 |
h = place.get("opening_hours","")
|
| 1068 |
if not h or str(h).strip() in ("","nan","none"):
|
| 1069 |
return f"ู
ุนูุฏูุงุด ู
ุนููู
ุงุช ุนู ู
ูุงุนูุฏ *{name}*."
|
|
@@ -1071,6 +1069,15 @@ def handle_detail(text, place):
|
|
| 1071 |
status = "๐ข ู
ูุชูุญ ุฏูููุชู." if on == 1 else ("๐ด ู
ุบูู ุฏูููุชู." if on == 0 else "")
|
| 1072 |
return f"{fmt_hours(h)}{status}"
|
| 1073 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1074 |
if any(w in t for w in ["ุนููุงู","ููู","ูููู","ููููุง","ู
ูุงูู","ู
ูุงููุง","ุงูุนููุงู",
|
| 1075 |
"ููููุดู","ุงูู
ููุน","ุงุฒุงู ุงุฑูุญู","ุงุฒุงู ุงุฑูุญ","map","ุงูุฎุฑูุทุฉ"]):
|
| 1076 |
addr = place.get("address",""); loc = place.get("location","")
|
|
@@ -1084,10 +1091,6 @@ def handle_detail(text, place):
|
|
| 1084 |
parts.append(f"๐บ๏ธ https://maps.google.com/?q={lat},{lon}")
|
| 1085 |
return "\n".join(parts) if parts else f"ุนููุงู {name}: ุบูุฑ ู
ุชุงุญ."
|
| 1086 |
|
| 1087 |
-
if any(w in t for w in ["ุชููููู","ุฑูู
","phone","ุงุชุตู"]):
|
| 1088 |
-
ph = place.get("phone","")
|
| 1089 |
-
return f"๐ {name}: {ph}" if ph and ph not in ("","nan") else f"ู
ุด ุนูุฏูุง ุฑูู
*{name}*."
|
| 1090 |
-
|
| 1091 |
if any(w in t for w in ["ุชูููู
","ูุงู
ูุฌู
ุฉ","rating","ูุฌูู
"]):
|
| 1092 |
r = fmt_rating(place.get("rating_norm", place.get("rating", 0)))
|
| 1093 |
return r or "ู
ุด ุนูุฏูุง ุชูููู
ููุฐุง ุงูู
ูุงู."
|
|
@@ -1623,6 +1626,13 @@ def chat(text: str, session, user_lat=None, user_lon=None):
|
|
| 1623 |
session.add(text, reply, result["intent"], {}, session.last_place, [])
|
| 1624 |
return result
|
| 1625 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1626 |
# โโ next result โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 1627 |
if ref == "next" and session.last_results:
|
| 1628 |
ptr = session.result_pointer + 1
|
|
@@ -1659,6 +1669,21 @@ def chat(text: str, session, user_lat=None, user_lon=None):
|
|
| 1659 |
ir = predict_intent(text)
|
| 1660 |
intent = ir["intent"]
|
| 1661 |
conf = ir["confidence"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1662 |
result["intent"] = intent
|
| 1663 |
result["confidence"] = conf
|
| 1664 |
|
|
@@ -1740,17 +1765,24 @@ def chat(text: str, session, user_lat=None, user_lon=None):
|
|
| 1740 |
session.context_slots = {}
|
| 1741 |
log.info("Context cleared for new category")
|
| 1742 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1743 |
merged = session.merge(ents)
|
| 1744 |
|
| 1745 |
-
category
|
| 1746 |
-
|
| 1747 |
-
)
|
| 1748 |
-
sub_cat = merged.get("sub_category")
|
| 1749 |
if sub_cat and normalize_category(sub_cat):
|
| 1750 |
sub_cat = None
|
| 1751 |
-
|
| 1752 |
-
|
| 1753 |
-
|
|
|
|
| 1754 |
open_only = (intent == "open_now")
|
| 1755 |
|
| 1756 |
# โโ extract place name โ ู
ุน ู
ูุน proximity words โโโโโโโโโโโโ
|
|
@@ -1954,18 +1986,21 @@ _ITEM_SEARCH_PATTERNS = [
|
|
| 1954 |
"ู
ูุงู ุนูุฏู", "ู
ูุงู ุนูุฏูุง", "ุงู
ุงูู ุนูุฏูุง", "ุฃู
ุงูู ุนูุฏูุง",
|
| 1955 |
"ู
ุทุนู
ุนูุฏู", "ูุงููู ุนูุฏู", "ุนูุฏู", "ุนูุฏูุง",
|
| 1956 |
"ุจูุนู
ู", "ุจุชุนู
ู", "ุจููุฏู
", "ุจุชูุฏู
", "ููู", "ูููุง",
|
| 1957 |
-
"ุนุงูุฒ", "ุนุงูุฒุฉ", "ุนุงูุฒู", "ุนุงูุฒ", "ููุณู ูู", "ุจุฏูุฑ ุนูู", "ููู",
|
| 1958 |
"place has", "places have", "has", "serves", "serve", "where can i find"
|
| 1959 |
]
|
| 1960 |
|
| 1961 |
def _looks_like_item_search(text: str) -> bool:
|
| 1962 |
"""
|
| 1963 |
-
Detect item/menu-item search without
|
| 1964 |
-
|
| 1965 |
-
|
| 1966 |
-
-
|
| 1967 |
-
|
| 1968 |
-
-
|
|
|
|
|
|
|
|
|
|
| 1969 |
"""
|
| 1970 |
t = norm(text)
|
| 1971 |
|
|
@@ -1973,7 +2008,7 @@ def _looks_like_item_search(text: str) -> bool:
|
|
| 1973 |
item_context_words = [
|
| 1974 |
"ุนูุฏู", "ุนูุฏูุง", "ุจูุนู
ู", "ุจุชุนู
ู", "ุจููุฏู
", "ุจุชูุฏู
",
|
| 1975 |
"ููู", "ูููุง", "ู
ูุงู ุนูุฏู", "ู
ูุงู ุนูุฏูุง", "ู
ุทุนู
ุนูุฏู", "ูุงููู ุนูุฏู",
|
| 1976 |
-
"has", "serves", "serve"
|
| 1977 |
]
|
| 1978 |
if any(kw in t for kw in _MENU_TRIGGERS) and not any(norm(p) in t for p in item_context_words):
|
| 1979 |
return False
|
|
@@ -1982,44 +2017,48 @@ def _looks_like_item_search(text: str) -> bool:
|
|
| 1982 |
candidate = extract_item_query(text)
|
| 1983 |
cand_clean = clean_text(candidate or "")
|
| 1984 |
|
| 1985 |
-
# If the remaining candidate is only a category or a ranking adjective, do NOT treat it as food item.
|
| 1986 |
category_words = set()
|
| 1987 |
for words in CATEGORY_KEYWORDS.values():
|
| 1988 |
category_words.update(clean_text(w) for w in words)
|
| 1989 |
category_words.update(clean_text(k) for k in _CAT_MAP.keys())
|
|
|
|
| 1990 |
|
| 1991 |
ranking_words = {
|
| 1992 |
"ุงุญุณู", "ุงูุถู", "ุงูุงุญุณู", "ุงูุงูุถู", "ุฃุญุณู", "ุฃูุถู",
|
| 1993 |
"ูููุณ", "ูููุณุฉ", "ุญูู", "ุญููุฉ", "ุชุฑุดูุญ", "ุฑุดุญูู",
|
| 1994 |
-
"best", "good", "recommended", "recommend"
|
| 1995 |
}
|
| 1996 |
ranking_words = {clean_text(w) for w in ranking_words}
|
| 1997 |
|
|
|
|
| 1998 |
cand_tokens = [w for w in cand_clean.split() if w]
|
| 1999 |
-
|
|
|
|
|
|
|
| 2000 |
return False
|
| 2001 |
|
| 2002 |
-
# Pure
|
| 2003 |
if inferred_cat in ("supermarket", "pharmacy", "housing"):
|
| 2004 |
return False
|
| 2005 |
-
|
|
|
|
|
|
|
| 2006 |
return False
|
| 2007 |
|
| 2008 |
has_item_keyword = any(norm(kw) in t for kw in _ITEM_TRIGGERS)
|
| 2009 |
-
has_item_pattern = any(norm(p) in t for p in _ITEM_SEARCH_PATTERNS)
|
| 2010 |
has_strong_item_context = any(norm(p) in t for p in item_context_words)
|
| 2011 |
|
| 2012 |
-
# Known item names are enough: "ุนุงูุฒุฉ ุจุฑุฌุฑ".
|
| 2013 |
if has_item_keyword:
|
| 2014 |
return True
|
| 2015 |
|
| 2016 |
-
# Unknown item
|
| 2017 |
-
if has_strong_item_context
|
| 2018 |
return True
|
| 2019 |
|
| 2020 |
-
#
|
| 2021 |
-
|
| 2022 |
-
|
|
|
|
| 2023 |
|
| 2024 |
return False
|
| 2025 |
|
|
@@ -2426,7 +2465,7 @@ def _infer_item_kind(item_query: str) -> str:
|
|
| 2426 |
return "unknown"
|
| 2427 |
|
| 2428 |
|
| 2429 |
-
def scan_menus_for_item(item_query: str, max_places_to_scan: int =
|
| 2430 |
"""
|
| 2431 |
Last-resort but important fallback:
|
| 2432 |
Search inside actual menus when /search-by-item returns empty or too strict.
|
|
@@ -2793,6 +2832,11 @@ def clean_place(place: dict) -> dict:
|
|
| 2793 |
|
| 2794 |
image = _first_value(place, "image", "image_url", "main_image_url", "photo", "photo_url", default="")
|
| 2795 |
favorite_raw = _first_value(place, "is_favorited", "is_favorite", default=False)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2796 |
|
| 2797 |
return {
|
| 2798 |
"id": str(_first_value(place, "id", "place_id", default="")),
|
|
@@ -2808,7 +2852,7 @@ def clean_place(place: dict) -> dict:
|
|
| 2808 |
"opening_hours": _first_value(place, "opening_hours", "openning_hours", default=""),
|
| 2809 |
"distance_km": distance,
|
| 2810 |
"image": image or "",
|
| 2811 |
-
"phone":
|
| 2812 |
"lat": lat,
|
| 2813 |
"lon": lon,
|
| 2814 |
"is_favorited": bool(favorite_raw),
|
|
|
|
| 539 |
"ุนูุฏู", "ุนูุฏูุง",
|
| 540 |
"ููู", "ูููุง",
|
| 541 |
"ุจููุฏู
", "ุจุชูุฏู
"
|
|
|
|
|
|
|
| 542 |
]
|
| 543 |
|
| 544 |
for w in remove_words:
|
|
|
|
| 646 |
matched = df[df["name_score"] > 0].copy()
|
| 647 |
|
| 648 |
if category and not matched.empty:
|
| 649 |
+
target_cat = normalize_category(category)
|
| 650 |
+
cat_match = matched[matched["category"].astype(str).apply(lambda x: _category_match_value(x, target_cat))]
|
| 651 |
+
if cat_match.empty and "category_clean" in matched.columns:
|
| 652 |
+
cat_match = matched[matched["category_clean"].astype(str).apply(lambda x: _category_match_value(x, target_cat))]
|
| 653 |
+
matched = cat_match
|
| 654 |
|
| 655 |
return matched.sort_values("name_score", ascending=False).head(top_k)
|
| 656 |
|
|
|
|
| 1057 |
|
| 1058 |
def handle_detail(text, place):
|
| 1059 |
if not place:
|
| 1060 |
+
return "ุชูุตุฏ ุฃููู ู
ูุงูุ ุงูุชุจ ุงุณู
ุงูู
ูุงู ุฃู ุงุณุฃููู ุงูุฃูู ุนู ู
ุทุนู
/ุตูุฏููุฉ/ูุงููู. ๐"
|
| 1061 |
t = norm(text)
|
| 1062 |
name = place.get("name", "ุงูู
ูุงู")
|
| 1063 |
|
| 1064 |
+
if any(w in t for w in ["ุจููุชุญ","ุจุชูุชุญ","ู
ูุงุนูุฏ","ู
ูุงุนูุฏู","ู
ูุงุนูุฏูุง","ุงู
ุชู","ุงู
ุชู","ุจูููู","ุจุชููู"]):
|
| 1065 |
h = place.get("opening_hours","")
|
| 1066 |
if not h or str(h).strip() in ("","nan","none"):
|
| 1067 |
return f"ู
ุนูุฏูุงุด ู
ุนููู
ุงุช ุนู ู
ูุงุนูุฏ *{name}*."
|
|
|
|
| 1069 |
status = "๐ข ู
ูุชูุญ ุฏูููุชู." if on == 1 else ("๐ด ู
ุบูู ุฏูููุชู." if on == 0 else "")
|
| 1070 |
return f"{fmt_hours(h)}{status}"
|
| 1071 |
|
| 1072 |
+
# Phone must be checked before address because queries like "ููู ุฑูู
ุชููููููุ" contain "ููู".
|
| 1073 |
+
if any(w in t for w in ["ุชููููู","ุชูููู","ุฑูู
","ุฑูู
ู","ุฑูู
ูุง","phone","ุงุชุตู"]):
|
| 1074 |
+
ph = place.get("phone","")
|
| 1075 |
+
if isinstance(ph, (list, tuple)):
|
| 1076 |
+
ph = ", ".join(str(x) for x in ph if x)
|
| 1077 |
+
elif isinstance(ph, str) and ph.startswith("[") and ph.endswith("]"):
|
| 1078 |
+
ph = ph.strip("[]").replace("'", "").replace('"', "").strip()
|
| 1079 |
+
return f"๐ {name}: {ph}" if ph and ph not in ("","nan") else f"ู
ุด ุนูุฏูุง ุฑูู
*{name}*."
|
| 1080 |
+
|
| 1081 |
if any(w in t for w in ["ุนููุงู","ููู","ูููู","ููููุง","ู
ูุงูู","ู
ูุงููุง","ุงูุนููุงู",
|
| 1082 |
"ููููุดู","ุงูู
ููุน","ุงุฒุงู ุงุฑูุญู","ุงุฒุงู ุงุฑูุญ","map","ุงูุฎุฑูุทุฉ"]):
|
| 1083 |
addr = place.get("address",""); loc = place.get("location","")
|
|
|
|
| 1091 |
parts.append(f"๐บ๏ธ https://maps.google.com/?q={lat},{lon}")
|
| 1092 |
return "\n".join(parts) if parts else f"ุนููุงู {name}: ุบูุฑ ู
ุชุงุญ."
|
| 1093 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1094 |
if any(w in t for w in ["ุชูููู
","ูุงู
ูุฌู
ุฉ","rating","ูุฌูู
"]):
|
| 1095 |
r = fmt_rating(place.get("rating_norm", place.get("rating", 0)))
|
| 1096 |
return r or "ู
ุด ุนูุฏูุง ุชูููู
ููุฐุง ุงูู
ูุงู."
|
|
|
|
| 1626 |
session.add(text, reply, result["intent"], {}, session.last_place, [])
|
| 1627 |
return result
|
| 1628 |
|
| 1629 |
+
if ref == "detail" and not session.last_place:
|
| 1630 |
+
reply = "ุชูุตุฏ ุฃููู ู
ูุงูุ ุงูุชุจ ุงุณู
ุงูู
ูุงู ุฃู ุงุณุฃููู ุงูุฃูู ุนู ู
ุทุนู
/ุตูุฏููุฉ/ูุงููู. ๐"
|
| 1631 |
+
result.update(reply=reply, intent="missing_info", confidence=1.0,
|
| 1632 |
+
best_place=None, all_results=[])
|
| 1633 |
+
session.add(text, reply, "missing_info", {}, None, [])
|
| 1634 |
+
return result
|
| 1635 |
+
|
| 1636 |
# โโ next result โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 1637 |
if ref == "next" and session.last_results:
|
| 1638 |
ptr = session.result_pointer + 1
|
|
|
|
| 1669 |
ir = predict_intent(text)
|
| 1670 |
intent = ir["intent"]
|
| 1671 |
conf = ir["confidence"]
|
| 1672 |
+
# Deterministic category intent override: the model/context must not turn
|
| 1673 |
+
# "ุนุงูุฒุฉ ุตูุฏููุฉ" into restaurant or keep old category from previous turns.
|
| 1674 |
+
current_text_cat = infer_category(text)
|
| 1675 |
+
if current_text_cat:
|
| 1676 |
+
forced_intent_map = {
|
| 1677 |
+
"restaurant": "nearest_restaurant",
|
| 1678 |
+
"pharmacy": "nearest_pharmacy",
|
| 1679 |
+
"cafe": "nearest_cafe",
|
| 1680 |
+
"supermarket": "nearest_supermarket",
|
| 1681 |
+
"housing": "housing_search",
|
| 1682 |
+
}
|
| 1683 |
+
if intent in ("fallback", "recommend_place", "open_now", "place_details") or intent in SEARCH_INTENTS:
|
| 1684 |
+
intent = forced_intent_map.get(current_text_cat, intent)
|
| 1685 |
+
conf = max(float(conf or 0), 0.96)
|
| 1686 |
+
|
| 1687 |
result["intent"] = intent
|
| 1688 |
result["confidence"] = conf
|
| 1689 |
|
|
|
|
| 1765 |
session.context_slots = {}
|
| 1766 |
log.info("Context cleared for new category")
|
| 1767 |
|
| 1768 |
+
# Merge context carefully: explicit category in the current user message wins.
|
| 1769 |
+
# If the user changes from restaurant โ pharmacy/supermarket/etc, old context is cleared.
|
| 1770 |
+
current_text_cat = infer_category(text) or INTENT_TO_CATEGORY.get(intent)
|
| 1771 |
+
old_ctx_cat = normalize_category(session.context_slots.get("category")) if session.context_slots else None
|
| 1772 |
+
if current_text_cat and old_ctx_cat and normalize_category(current_text_cat) != old_ctx_cat:
|
| 1773 |
+
session.context_slots = {}
|
| 1774 |
+
log.info(f"Context cleared: category changed {old_ctx_cat} โ {current_text_cat}")
|
| 1775 |
+
|
| 1776 |
merged = session.merge(ents)
|
| 1777 |
|
| 1778 |
+
category = normalize_category(current_text_cat or merged.get("category"))
|
| 1779 |
+
sub_cat = ents.get("sub_category") or (merged.get("sub_category") if not current_text_cat else None)
|
|
|
|
|
|
|
| 1780 |
if sub_cat and normalize_category(sub_cat):
|
| 1781 |
sub_cat = None
|
| 1782 |
+
# inherit location only for true continuations, not for a new category question
|
| 1783 |
+
location = ents.get("location") or (merged.get("location") if not current_text_cat else None)
|
| 1784 |
+
price_range = ents.get("price") or merged.get("price")
|
| 1785 |
+
place_name = ents.get("place_name") or (merged.get("place_name") if not current_text_cat else None)
|
| 1786 |
open_only = (intent == "open_now")
|
| 1787 |
|
| 1788 |
# โโ extract place name โ ู
ุน ู
ูุน proximity words โโโโโโโโโโโโ
|
|
|
|
| 1986 |
"ู
ูุงู ุนูุฏู", "ู
ูุงู ุนูุฏูุง", "ุงู
ุงูู ุนูุฏูุง", "ุฃู
ุงูู ุนูุฏูุง",
|
| 1987 |
"ู
ุทุนู
ุนูุฏู", "ูุงููู ุนูุฏู", "ุนูุฏู", "ุนูุฏูุง",
|
| 1988 |
"ุจูุนู
ู", "ุจุชุนู
ู", "ุจููุฏู
", "ุจุชูุฏู
", "ููู", "ูููุง",
|
| 1989 |
+
"ุนุงูุฒ", "ุนุงูุฒุฉ", "ุนุงูุฒู", "ุนุงูุฒู", "ุนุงูุฒ", "ู
ุญุชุงุฌ", "ู
ุญุชุงุฌุฉ", "ููุณู ูู", "ุจุฏูุฑ ุนูู", "ููู",
|
| 1990 |
"place has", "places have", "has", "serves", "serve", "where can i find"
|
| 1991 |
]
|
| 1992 |
|
| 1993 |
def _looks_like_item_search(text: str) -> bool:
|
| 1994 |
"""
|
| 1995 |
+
Detect item/menu-item search without breaking normal category searches.
|
| 1996 |
+
|
| 1997 |
+
Rules:
|
| 1998 |
+
- Category-only requests stay category search:
|
| 1999 |
+
"ุนุงูุฒุฉ ุตูุฏููุฉ" โ nearest_pharmacy, "ุนุงูุฒุฉ ุณูุจุฑู
ุงุฑูุช" โ nearest_supermarket.
|
| 2000 |
+
- Ranking/category requests stay place search:
|
| 2001 |
+
"ุนุงูุฒุฉ ุฃุญุณู ู
ุทุนู
" โ recommend/restaurant, not item=ุฃุญุณู.
|
| 2002 |
+
- Known or unknown item requests become search_by_item:
|
| 2003 |
+
"ุนุงูุฒุฉ ุจุฑุฌุฑ", "ุนุงูุฒุฉ ูุชุฉ", "ู
ูุงู ุนูุฏู ุงุณู
ูุฒู ุฎูุฎ".
|
| 2004 |
"""
|
| 2005 |
t = norm(text)
|
| 2006 |
|
|
|
|
| 2008 |
item_context_words = [
|
| 2009 |
"ุนูุฏู", "ุนูุฏูุง", "ุจูุนู
ู", "ุจุชุนู
ู", "ุจููุฏู
", "ุจุชูุฏู
",
|
| 2010 |
"ููู", "ูููุง", "ู
ูุงู ุนูุฏู", "ู
ูุงู ุนูุฏูุง", "ู
ุทุนู
ุนูุฏู", "ูุงููู ุนูุฏู",
|
| 2011 |
+
"ู
ุญู ุนูุฏู", "ู
ุทุนู
ููู", "ูุงููู ููู", "has", "serves", "serve"
|
| 2012 |
]
|
| 2013 |
if any(kw in t for kw in _MENU_TRIGGERS) and not any(norm(p) in t for p in item_context_words):
|
| 2014 |
return False
|
|
|
|
| 2017 |
candidate = extract_item_query(text)
|
| 2018 |
cand_clean = clean_text(candidate or "")
|
| 2019 |
|
|
|
|
| 2020 |
category_words = set()
|
| 2021 |
for words in CATEGORY_KEYWORDS.values():
|
| 2022 |
category_words.update(clean_text(w) for w in words)
|
| 2023 |
category_words.update(clean_text(k) for k in _CAT_MAP.keys())
|
| 2024 |
+
category_words.update({"restaurant", "restaurants", "cafe", "coffee", "pharmacy", "supermarket", "market", "housing", "hotel"})
|
| 2025 |
|
| 2026 |
ranking_words = {
|
| 2027 |
"ุงุญุณู", "ุงูุถู", "ุงูุงุญุณู", "ุงูุงูุถู", "ุฃุญุณู", "ุฃูุถู",
|
| 2028 |
"ูููุณ", "ูููุณุฉ", "ุญูู", "ุญููุฉ", "ุชุฑุดูุญ", "ุฑุดุญูู",
|
| 2029 |
+
"best", "good", "recommended", "recommend", "ุงูุฑุจ", "ูุฑูุจ", "nearest"
|
| 2030 |
}
|
| 2031 |
ranking_words = {clean_text(w) for w in ranking_words}
|
| 2032 |
|
| 2033 |
+
generic_words = {"ู
ูุงู", "ุงู
ุงูู", "ุญุงุฌู", "ุญุงุฌุฉ", "ุดู", "ุงู", "ุงู", "ููู", "ุนูุฏู", "ุนูุฏู"}
|
| 2034 |
cand_tokens = [w for w in cand_clean.split() if w]
|
| 2035 |
+
|
| 2036 |
+
# Candidate is only category/ranking/generic โ not item.
|
| 2037 |
+
if not cand_tokens or all((w in category_words or w in ranking_words or w in generic_words) for w in cand_tokens):
|
| 2038 |
return False
|
| 2039 |
|
| 2040 |
+
# Pure pharmacy/supermarket/housing requests are never item searches.
|
| 2041 |
if inferred_cat in ("supermarket", "pharmacy", "housing"):
|
| 2042 |
return False
|
| 2043 |
+
|
| 2044 |
+
# Restaurant/cafe requests with only category/ranking words are place search.
|
| 2045 |
+
if inferred_cat in ("restaurant", "cafe") and all(w in category_words or w in ranking_words or w in generic_words for w in cand_tokens):
|
| 2046 |
return False
|
| 2047 |
|
| 2048 |
has_item_keyword = any(norm(kw) in t for kw in _ITEM_TRIGGERS)
|
|
|
|
| 2049 |
has_strong_item_context = any(norm(p) in t for p in item_context_words)
|
| 2050 |
|
|
|
|
| 2051 |
if has_item_keyword:
|
| 2052 |
return True
|
| 2053 |
|
| 2054 |
+
# Unknown item with strong context: "ู
ูุงู ุนูุฏู ูุชุฉ".
|
| 2055 |
+
if has_strong_item_context:
|
| 2056 |
return True
|
| 2057 |
|
| 2058 |
+
# Unknown item without category: "ุนุงูุฒุฉ ูุชุฉ" / "ุนุงูุฒ ู
ูููุชู".
|
| 2059 |
+
demand_words = ["ุนุงูุฒ", "ุนุงูุฒุฉ", "ุนุงูุฒู", "ุนุงูุฒ", "ู
ุญุชุงุฌ", "ู
ุญุชุงุฌุฉ", "ููุณู", "ุจุฏูุฑ ุนูู", "ููู"]
|
| 2060 |
+
if inferred_cat is None and any(norm(w) in t for w in demand_words) and len(cand_clean) >= 3:
|
| 2061 |
+
return True
|
| 2062 |
|
| 2063 |
return False
|
| 2064 |
|
|
|
|
| 2465 |
return "unknown"
|
| 2466 |
|
| 2467 |
|
| 2468 |
+
def scan_menus_for_item(item_query: str, max_places_to_scan: int = 300) -> list[dict]:
|
| 2469 |
"""
|
| 2470 |
Last-resort but important fallback:
|
| 2471 |
Search inside actual menus when /search-by-item returns empty or too strict.
|
|
|
|
| 2832 |
|
| 2833 |
image = _first_value(place, "image", "image_url", "main_image_url", "photo", "photo_url", default="")
|
| 2834 |
favorite_raw = _first_value(place, "is_favorited", "is_favorite", default=False)
|
| 2835 |
+
phone_val = _first_value(place, "phone", "mobile", "telephone", default="")
|
| 2836 |
+
if isinstance(phone_val, (list, tuple)):
|
| 2837 |
+
phone_val = ", ".join(str(x) for x in phone_val if x)
|
| 2838 |
+
elif isinstance(phone_val, str) and phone_val.startswith("[") and phone_val.endswith("]"):
|
| 2839 |
+
phone_val = phone_val.strip("[]").replace("'", "").replace('"', "").strip()
|
| 2840 |
|
| 2841 |
return {
|
| 2842 |
"id": str(_first_value(place, "id", "place_id", default="")),
|
|
|
|
| 2852 |
"opening_hours": _first_value(place, "opening_hours", "openning_hours", default=""),
|
| 2853 |
"distance_km": distance,
|
| 2854 |
"image": image or "",
|
| 2855 |
+
"phone": phone_val,
|
| 2856 |
"lat": lat,
|
| 2857 |
"lon": lon,
|
| 2858 |
"is_favorited": bool(favorite_raw),
|