buzzbandit commited on
Commit
415e948
Β·
verified Β·
1 Parent(s): 8fcbcc3

show search results even when inventory is 0

Browse files
Files changed (1) hide show
  1. app.py +30 -56
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
- # ---------------- Improved semantic search ----------------
140
- def semantic_match(query, top_k=15, debug_top_n=8):
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
- try:
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"🚫 No semantic fallback triggered for '{query}' β€” returning {len(item_hits)} item matches.")
206
- return {"category": None, "items": item_hits}
 
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
- for item in cdata.get("stocks", []):
263
- iname = item.get("name", "")
 
 
 
 
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 country_ok and item_ok:
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()