Spaces:
Running
Running
show search results even when inventory is 0
Browse files
app.py
CHANGED
|
@@ -17,7 +17,9 @@ _cache = {"data": None, "timestamp": 0, "last_update": "Unknown"}
|
|
| 17 |
# ---------------- Load category map ----------------
|
| 18 |
with open("items.json", "r", encoding="utf-8") as f:
|
| 19 |
items_data = json.load(f)["items"]
|
|
|
|
| 20 |
ITEM_TO_TYPE = {v["name"]: v["type"].lower() for v in items_data.values() if "name" in v and "type" in v}
|
|
|
|
| 21 |
ALL_CATEGORIES = sorted(set(ITEM_TO_TYPE.values()))
|
| 22 |
ITEM_FILE_MTIME = os.path.getmtime("items.json")
|
| 23 |
|
|
@@ -136,8 +138,8 @@ def parse_freeform_query(text: str):
|
|
| 136 |
return first, second
|
| 137 |
return text, ""
|
| 138 |
|
| 139 |
-
# ----------------
|
| 140 |
-
def semantic_match(query, top_k=15, debug_top_n=
|
| 141 |
"""Full diagnostic semantic search β logs item and category similarity scores, fallback logic."""
|
| 142 |
if not query:
|
| 143 |
print("β οΈ semantic_match called with empty query")
|
|
@@ -146,67 +148,32 @@ def semantic_match(query, top_k=15, debug_top_n=8):
|
|
| 146 |
query = query.strip().lower()
|
| 147 |
print(f"\nπ§ [semantic_match] Input query: '{query}'")
|
| 148 |
|
| 149 |
-
|
| 150 |
-
q_emb = embedder.encode(query, convert_to_tensor=True)
|
| 151 |
-
except Exception as e:
|
| 152 |
-
print(f"β οΈ Semantic encode error: {e}")
|
| 153 |
-
return {"category": None, "items": []}
|
| 154 |
-
|
| 155 |
-
# --- Item similarities ---
|
| 156 |
sims_items = {n: float(util.cos_sim(q_emb, emb)) for n, emb in ITEM_EMBEDS.items()}
|
| 157 |
ranked_items = sorted(sims_items.items(), key=lambda x: x[1], reverse=True)
|
| 158 |
-
top_items_preview = [f"{n} ({s:.2f})" for n, s in ranked_items[:debug_top_n]]
|
| 159 |
-
print(f" πΈ Top item similarities: {', '.join(top_items_preview)}")
|
| 160 |
-
|
| 161 |
item_hits = [n for n, score in ranked_items[:top_k] if score > 0.35]
|
| 162 |
top_item_score = float(ranked_items[0][1]) if ranked_items else 0.0
|
| 163 |
-
print(f" β
Found {len(item_hits)} item hits (top score={top_item_score:.2f})")
|
| 164 |
|
| 165 |
-
# --- Category similarities ---
|
| 166 |
sims_cats = {c: float(util.cos_sim(q_emb, emb)) for c, emb in CATEGORY_EMBEDS.items()}
|
| 167 |
ranked_cats = sorted(sims_cats.items(), key=lambda x: x[1], reverse=True)
|
| 168 |
-
top_cats_preview = [f"{c} ({s:.2f})" for c, s in ranked_cats[:debug_top_n]]
|
| 169 |
-
print(f" πΉ Top category similarities: {', '.join(top_cats_preview)}")
|
| 170 |
-
|
| 171 |
top_cat, cat_score = (ranked_cats[0] if ranked_cats else (None, 0.0))
|
| 172 |
-
strong_category = cat_score > 0.35
|
| 173 |
-
weak_items = len(item_hits) == 0 or (top_item_score < 0.4)
|
| 174 |
-
clearly_better = cat_score - top_item_score > 0.1
|
| 175 |
-
|
| 176 |
-
print(f" π‘ top_cat={top_cat}, cat_score={cat_score:.2f}, strong_category={strong_category}, "
|
| 177 |
-
f"weak_items={weak_items}, clearly_better={clearly_better}")
|
| 178 |
-
|
| 179 |
-
# --- Heuristic substring fallback ---
|
| 180 |
-
if not top_cat:
|
| 181 |
-
for c in CATEGORY_EMBEDS.keys():
|
| 182 |
-
if c in query or query in c:
|
| 183 |
-
print(f" π§© Heuristic substring fallback β '{c}'")
|
| 184 |
-
top_cat = c
|
| 185 |
-
strong_category = True
|
| 186 |
-
cat_score = 0.5
|
| 187 |
-
break
|
| 188 |
-
|
| 189 |
-
# --- Plural heuristic ---
|
| 190 |
-
if not top_cat and query.endswith("s"):
|
| 191 |
-
singular = query[:-1]
|
| 192 |
-
if singular in CATEGORY_EMBEDS:
|
| 193 |
-
print(f" π§© Plural fallback β '{singular}'")
|
| 194 |
-
top_cat = singular
|
| 195 |
-
strong_category = True
|
| 196 |
-
cat_score = 0.5
|
| 197 |
-
|
| 198 |
-
# --- Decision ---
|
| 199 |
-
if top_cat and (strong_category and (weak_items or clearly_better)):
|
| 200 |
-
related_items = [n for n, t in ITEM_TO_TYPE.items() if t and t == top_cat]
|
| 201 |
-
print(f"β
[FALLBACK] '{query}' β category '{top_cat}' "
|
| 202 |
-
f"({len(related_items)} items, cat_score={cat_score:.2f}, item_score={top_item_score:.2f})")
|
| 203 |
-
return {"category": top_cat, "items": related_items}
|
| 204 |
|
| 205 |
-
print(f"
|
| 206 |
-
|
|
|
|
| 207 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 208 |
|
|
|
|
|
|
|
|
|
|
| 209 |
|
|
|
|
|
|
|
| 210 |
|
| 211 |
# ---------------- Fetch YATA ----------------
|
| 212 |
def fetch_yata(force_refresh=False):
|
|
@@ -259,9 +226,16 @@ def query_inventory(query_text="", category="", country_name="", capacity=10, re
|
|
| 259 |
else:
|
| 260 |
country_ok = True
|
| 261 |
|
| 262 |
-
|
| 263 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 264 |
itype = ITEM_TO_TYPE.get(iname, "").lower()
|
|
|
|
|
|
|
|
|
|
| 265 |
|
| 266 |
if item_term:
|
| 267 |
item_ok = (
|
|
@@ -274,9 +248,7 @@ def query_inventory(query_text="", category="", country_name="", capacity=10, re
|
|
| 274 |
else:
|
| 275 |
item_ok = True
|
| 276 |
|
| 277 |
-
if
|
| 278 |
-
cost = item.get("cost", 0)
|
| 279 |
-
qty = item.get("quantity", 0)
|
| 280 |
rows.append({
|
| 281 |
"Country": cname,
|
| 282 |
"Item": iname,
|
|
@@ -288,6 +260,7 @@ def query_inventory(query_text="", category="", country_name="", capacity=10, re
|
|
| 288 |
})
|
| 289 |
|
| 290 |
if not rows:
|
|
|
|
| 291 |
return pd.DataFrame([{"Result": "No inventory found for that query."}]), f"Last update: {last_update}"
|
| 292 |
|
| 293 |
df = pd.DataFrame(rows)
|
|
@@ -321,6 +294,7 @@ with gr.Blocks(title="π§³ Torn Inventory Viewer") as iface:
|
|
| 321 |
gr.Markdown("## π§³ Torn Inventory Viewer")
|
| 322 |
gr.Markdown("_Search Torn YATA travel stocks with smart semantic matching_ \n"
|
| 323 |
"Try phrases like **'flowers in England'**, **'plushies in UK'**, or **'xanax'**. \n"
|
|
|
|
| 324 |
"Your travel capacity is saved automatically for next time.")
|
| 325 |
with gr.Row():
|
| 326 |
query_box.render()
|
|
|
|
| 17 |
# ---------------- Load category map ----------------
|
| 18 |
with open("items.json", "r", encoding="utf-8") as f:
|
| 19 |
items_data = json.load(f)["items"]
|
| 20 |
+
|
| 21 |
ITEM_TO_TYPE = {v["name"]: v["type"].lower() for v in items_data.values() if "name" in v and "type" in v}
|
| 22 |
+
ALL_ITEMS = list(ITEM_TO_TYPE.keys())
|
| 23 |
ALL_CATEGORIES = sorted(set(ITEM_TO_TYPE.values()))
|
| 24 |
ITEM_FILE_MTIME = os.path.getmtime("items.json")
|
| 25 |
|
|
|
|
| 138 |
return first, second
|
| 139 |
return text, ""
|
| 140 |
|
| 141 |
+
# ---------------- Semantic Match ----------------
|
| 142 |
+
def semantic_match(query, top_k=15, debug_top_n=5):
|
| 143 |
"""Full diagnostic semantic search β logs item and category similarity scores, fallback logic."""
|
| 144 |
if not query:
|
| 145 |
print("β οΈ semantic_match called with empty query")
|
|
|
|
| 148 |
query = query.strip().lower()
|
| 149 |
print(f"\nπ§ [semantic_match] Input query: '{query}'")
|
| 150 |
|
| 151 |
+
q_emb = embedder.encode(query, convert_to_tensor=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 152 |
sims_items = {n: float(util.cos_sim(q_emb, emb)) for n, emb in ITEM_EMBEDS.items()}
|
| 153 |
ranked_items = sorted(sims_items.items(), key=lambda x: x[1], reverse=True)
|
|
|
|
|
|
|
|
|
|
| 154 |
item_hits = [n for n, score in ranked_items[:top_k] if score > 0.35]
|
| 155 |
top_item_score = float(ranked_items[0][1]) if ranked_items else 0.0
|
|
|
|
| 156 |
|
|
|
|
| 157 |
sims_cats = {c: float(util.cos_sim(q_emb, emb)) for c, emb in CATEGORY_EMBEDS.items()}
|
| 158 |
ranked_cats = sorted(sims_cats.items(), key=lambda x: x[1], reverse=True)
|
|
|
|
|
|
|
|
|
|
| 159 |
top_cat, cat_score = (ranked_cats[0] if ranked_cats else (None, 0.0))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 160 |
|
| 161 |
+
print(f" πΈ Top item similarities: {[f'{n} ({s:.2f})' for n, s in ranked_items[:debug_top_n]]}")
|
| 162 |
+
print(f" πΉ Top category similarities: {[f'{c} ({s:.2f})' for c, s in ranked_cats[:debug_top_n]]}")
|
| 163 |
+
print(f" π‘ top_cat={top_cat}, cat_score={cat_score:.2f}, top_item_score={top_item_score:.2f}")
|
| 164 |
|
| 165 |
+
# Always include category items if strong enough
|
| 166 |
+
related_items = []
|
| 167 |
+
if top_cat and cat_score > 0.35:
|
| 168 |
+
related_items = [n for n, t in ITEM_TO_TYPE.items() if t and t == top_cat]
|
| 169 |
+
print(f"β
[CATEGORY DETECTED] '{query}' β '{top_cat}' ({len(related_items)} items, score={cat_score:.2f})")
|
| 170 |
|
| 171 |
+
combined = list(set(item_hits + related_items))
|
| 172 |
+
if combined:
|
| 173 |
+
return {"category": top_cat if related_items else None, "items": combined}
|
| 174 |
|
| 175 |
+
print(f"π« No semantic matches returned for '{query}'")
|
| 176 |
+
return {"category": None, "items": []}
|
| 177 |
|
| 178 |
# ---------------- Fetch YATA ----------------
|
| 179 |
def fetch_yata(force_refresh=False):
|
|
|
|
| 226 |
else:
|
| 227 |
country_ok = True
|
| 228 |
|
| 229 |
+
if not country_ok:
|
| 230 |
+
continue
|
| 231 |
+
|
| 232 |
+
# --- Merge with all known items (show zeroes too) ---
|
| 233 |
+
live_lookup = {i["name"]: i for i in cdata.get("stocks", [])}
|
| 234 |
+
for iname in ALL_ITEMS:
|
| 235 |
itype = ITEM_TO_TYPE.get(iname, "").lower()
|
| 236 |
+
item_data = live_lookup.get(iname, {"quantity": 0, "cost": 0})
|
| 237 |
+
qty = item_data.get("quantity", 0)
|
| 238 |
+
cost = item_data.get("cost", 0)
|
| 239 |
|
| 240 |
if item_term:
|
| 241 |
item_ok = (
|
|
|
|
| 248 |
else:
|
| 249 |
item_ok = True
|
| 250 |
|
| 251 |
+
if item_ok:
|
|
|
|
|
|
|
| 252 |
rows.append({
|
| 253 |
"Country": cname,
|
| 254 |
"Item": iname,
|
|
|
|
| 260 |
})
|
| 261 |
|
| 262 |
if not rows:
|
| 263 |
+
print(f"β οΈ No '{item_term}' items found in {country_name.title()} β likely out of stock.")
|
| 264 |
return pd.DataFrame([{"Result": "No inventory found for that query."}]), f"Last update: {last_update}"
|
| 265 |
|
| 266 |
df = pd.DataFrame(rows)
|
|
|
|
| 294 |
gr.Markdown("## π§³ Torn Inventory Viewer")
|
| 295 |
gr.Markdown("_Search Torn YATA travel stocks with smart semantic matching_ \n"
|
| 296 |
"Try phrases like **'flowers in England'**, **'plushies in UK'**, or **'xanax'**. \n"
|
| 297 |
+
"Shows items even if out of stock. \n"
|
| 298 |
"Your travel capacity is saved automatically for next time.")
|
| 299 |
with gr.Row():
|
| 300 |
query_box.render()
|