buzzbandit commited on
Commit
bd7d50d
Β·
verified Β·
1 Parent(s): 824e679

try fixes again

Browse files
Files changed (1) hide show
  1. app.py +90 -47
app.py CHANGED
@@ -19,7 +19,6 @@ 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
 
@@ -67,7 +66,6 @@ def save_cached_embeddings(aliases, embeds):
67
  print(f"⚠️ Cache save failed: {e}")
68
 
69
  def auto_alias_categories(embedder, all_categories, all_item_names, top_k=6, threshold=0.38):
70
- """Builds semantic aliases for each category by finding similar item names."""
71
  print("πŸ€– Building category aliases dynamically...")
72
  cat_embs = {c: embedder.encode(c, convert_to_tensor=True) for c in all_categories}
73
  item_embs = {i: embedder.encode(i, convert_to_tensor=True) for i in all_item_names}
@@ -120,12 +118,13 @@ def normalize_country_query(q: str) -> str | None:
120
  return q.upper()
121
  return None
122
 
 
123
  def parse_freeform_query(text: str):
124
  """Supports 'plushies in uk' and 'uk plushies'."""
125
  if not text:
126
  return "", ""
127
  text = text.strip().lower()
128
- m = re.match(r"(.+?)\\s+in\\s+(.+)", text, flags=re.IGNORECASE)
129
  if m:
130
  return m.group(1).strip(), m.group(2).strip()
131
  parts = text.split()
@@ -137,23 +136,29 @@ def parse_freeform_query(text: str):
137
  return first, second
138
  return text, ""
139
 
 
140
  def semantic_match(query, top_k=15):
141
  if not query:
142
  return {"category": None, "items": []}
 
143
  query = query.strip().lower()
144
  q_emb = embedder.encode(query, convert_to_tensor=True)
145
  sims_items = {n: float(util.cos_sim(q_emb, emb)) for n, emb in ITEM_EMBEDS.items()}
146
  ranked_items = sorted(sims_items.items(), key=lambda x: x[1], reverse=True)
147
  item_hits = [n for n, score in ranked_items[:top_k] if score > 0.35]
 
148
  sims_cats = {c: float(util.cos_sim(q_emb, emb)) for c, emb in CATEGORY_EMBEDS.items()}
149
  ranked_cats = sorted(sims_cats.items(), key=lambda x: x[1], reverse=True)
150
  top_cat, cat_score = (ranked_cats[0] if ranked_cats else (None, 0.0))
 
151
  related_items = []
152
  if top_cat and cat_score > 0.35:
153
  related_items = [n for n, t in ITEM_TO_TYPE.items() if t == top_cat]
 
154
  combined = list(set(item_hits + related_items))
155
  return {"category": top_cat if related_items else None, "items": combined}
156
 
 
157
  def fetch_yata(force_refresh=False):
158
  if not force_refresh and _cache["data"] and (time.time() - _cache["timestamp"] < 300):
159
  return _cache["data"], _cache["last_update"]
@@ -172,19 +177,14 @@ def fetch_yata(force_refresh=False):
172
  print(f"❌ Fetch error: {e}")
173
  return {"stocks": {}}, "Fetch failed"
174
 
175
- # βœ… Persistent capacity memory (server side)
176
- _user_capacity = 10
177
-
178
  def query_inventory(query_text="", category="", country_name="", capacity=10, refresh=False):
179
- global _user_capacity
180
- if capacity != _user_capacity:
181
- print(f"πŸ”„ Updating stored capacity: {_user_capacity} β†’ {capacity}")
182
- _user_capacity = capacity
183
-
184
  data, last_update = fetch_yata(force_refresh=refresh)
185
  stocks = data.get("stocks", {})
186
  rows = []
187
 
 
188
  parsed_item, parsed_country = parse_freeform_query(query_text)
189
  if not country_name and parsed_country:
190
  country_name = parsed_country
@@ -192,38 +192,43 @@ def query_inventory(query_text="", category="", country_name="", capacity=10, re
192
  semantic_result = semantic_match(item_term) if item_term else {"category": None, "items": []}
193
  semantic_items = semantic_result["items"]
194
  semantic_category = semantic_result["category"]
195
- user_code = normalize_country_query(country_name)
196
 
197
- COUNTRY_CATALOG = {code.upper(): {i["name"] for i in cdata.get("stocks", [])} for code, cdata in stocks.items()}
 
198
 
199
  for code_raw, cdata in stocks.items():
200
  code = code_raw.upper()
201
  cname = COUNTRY_NAMES.get(code, code)
 
202
  if country_name:
203
  if user_code:
204
  if code != user_code:
205
  continue
206
  elif country_name.lower() not in cname.lower():
207
  continue
 
208
  update_ts = cdata.get("update")
209
  update_str = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(update_ts)) if update_ts else "Unknown"
210
- live_lookup = {i["name"]: i for i in cdata.get("stocks", [])}
211
- candidate_names = sorted(COUNTRY_CATALOG.get(code, set()))
212
 
213
- for iname in candidate_names:
 
 
214
  itype = ITEM_TO_TYPE.get(iname, "").lower()
215
- item_data = live_lookup.get(iname, {"quantity": 0, "cost": 0})
216
- qty = item_data.get("quantity", 0)
217
- cost = item_data.get("cost", 0)
218
- item_ok = (
219
- (item_term and (
 
220
  item_term.lower() in iname.lower()
221
  or iname in semantic_items
222
  or (semantic_category and itype == semantic_category.lower())
223
- ))
224
- or (category and category.lower() == itype)
225
- or (not item_term and not category)
226
- )
 
 
227
  if item_ok:
228
  rows.append({
229
  "Country": cname,
@@ -231,7 +236,8 @@ def query_inventory(query_text="", category="", country_name="", capacity=10, re
231
  "Category": itype.title() if itype else "",
232
  "Quantity": qty,
233
  "Cost": cost,
234
- "Max Capacity Cost": cost * _user_capacity,
 
235
  "Updated": update_str,
236
  })
237
 
@@ -242,20 +248,37 @@ def query_inventory(query_text="", category="", country_name="", capacity=10, re
242
  for col in ["Quantity", "Cost", "Max Capacity Cost"]:
243
  if col in df.columns:
244
  df[col] = df[col].apply(lambda x: f"{x:,.0f}" if isinstance(x, (int, float)) and x != "" else x)
245
- return df, f"Last update: {last_update} | Capacity: {_user_capacity}"
246
 
 
247
  def run_query(query_text, category, country, capacity, refresh):
248
  return query_inventory(query_text, category, country, capacity, refresh)
249
 
 
250
  with gr.Blocks(title="🧳 Torn Inventory Viewer") as iface:
251
  gr.Markdown("## 🧳 Torn Inventory Viewer")
252
- gr.Markdown("_Search Torn YATA travel stocks with semantic and persistent capacity._")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
253
 
254
- query_box = gr.Textbox(label="Search (e.g. 'flowers in England')")
255
- category_drop = gr.Dropdown(label="Category", choices=[""] + ALL_CATEGORIES)
256
- country_box = gr.Textbox(label="Country")
257
- capacity_slider = gr.Number(label="Travel Capacity", value=_user_capacity, minimum=5, maximum=88, precision=0)
258
- refresh_check = gr.Checkbox(label="Force refresh", value=False)
259
 
260
  run_btn = gr.Button("πŸ” Search / Refresh")
261
  result_df = gr.Dataframe(label="Results")
@@ -265,6 +288,7 @@ with gr.Blocks(title="🧳 Torn Inventory Viewer") as iface:
265
  inputs=[query_box, category_drop, country_box, capacity_slider, refresh_check],
266
  outputs=[result_df, meta_box])
267
 
 
268
  gr.HTML("""
269
  <script>
270
  (function () {
@@ -272,24 +296,42 @@ with gr.Blocks(title="🧳 Torn Inventory Viewer") as iface:
272
  const app = document.querySelector('gradio-app');
273
  return app && app.shadowRoot ? app.shadowRoot : document;
274
  }
275
- function restoreOnce() {
 
 
276
  const saved = localStorage.getItem('travel_capacity');
277
- const r = root();
278
- const field = r.querySelector('input[type=number]');
279
- if (saved && field) field.value = saved;
280
- }
281
- function attachSaver() {
282
- const r = root();
283
- const field = r.querySelector('input[type=number]');
284
- if (!field || field.dataset.tcListen === '1') return;
285
- field.dataset.tcListen = '1';
286
- field.addEventListener('input', () => {
287
- localStorage.setItem('travel_capacity', field.value);
288
- });
 
 
 
 
 
 
 
 
 
 
 
289
  }
 
290
  window.addEventListener('DOMContentLoaded', () => {
291
- setTimeout(() => { restoreOnce(); attachSaver(); }, 500);
292
  });
 
 
 
 
293
  })();
294
  </script>
295
  """)
@@ -299,4 +341,5 @@ try:
299
  iface.launch()
300
  except Exception as e:
301
  import traceback
 
302
  traceback.print_exc()
 
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_CATEGORIES = sorted(set(ITEM_TO_TYPE.values()))
23
  ITEM_FILE_MTIME = os.path.getmtime("items.json")
24
 
 
66
  print(f"⚠️ Cache save failed: {e}")
67
 
68
  def auto_alias_categories(embedder, all_categories, all_item_names, top_k=6, threshold=0.38):
 
69
  print("πŸ€– Building category aliases dynamically...")
70
  cat_embs = {c: embedder.encode(c, convert_to_tensor=True) for c in all_categories}
71
  item_embs = {i: embedder.encode(i, convert_to_tensor=True) for i in all_item_names}
 
118
  return q.upper()
119
  return None
120
 
121
+ # ---------------- Helpers ----------------
122
  def parse_freeform_query(text: str):
123
  """Supports 'plushies in uk' and 'uk plushies'."""
124
  if not text:
125
  return "", ""
126
  text = text.strip().lower()
127
+ m = re.match(r"(.+?)\s+in\s+(.+)", text, flags=re.IGNORECASE)
128
  if m:
129
  return m.group(1).strip(), m.group(2).strip()
130
  parts = text.split()
 
136
  return first, second
137
  return text, ""
138
 
139
+ # ---------------- Semantic Match ----------------
140
  def semantic_match(query, top_k=15):
141
  if not query:
142
  return {"category": None, "items": []}
143
+
144
  query = query.strip().lower()
145
  q_emb = embedder.encode(query, convert_to_tensor=True)
146
  sims_items = {n: float(util.cos_sim(q_emb, emb)) for n, emb in ITEM_EMBEDS.items()}
147
  ranked_items = sorted(sims_items.items(), key=lambda x: x[1], reverse=True)
148
  item_hits = [n for n, score in ranked_items[:top_k] if score > 0.35]
149
+
150
  sims_cats = {c: float(util.cos_sim(q_emb, emb)) for c, emb in CATEGORY_EMBEDS.items()}
151
  ranked_cats = sorted(sims_cats.items(), key=lambda x: x[1], reverse=True)
152
  top_cat, cat_score = (ranked_cats[0] if ranked_cats else (None, 0.0))
153
+
154
  related_items = []
155
  if top_cat and cat_score > 0.35:
156
  related_items = [n for n, t in ITEM_TO_TYPE.items() if t == top_cat]
157
+
158
  combined = list(set(item_hits + related_items))
159
  return {"category": top_cat if related_items else None, "items": combined}
160
 
161
+ # ---------------- Fetch YATA ----------------
162
  def fetch_yata(force_refresh=False):
163
  if not force_refresh and _cache["data"] and (time.time() - _cache["timestamp"] < 300):
164
  return _cache["data"], _cache["last_update"]
 
177
  print(f"❌ Fetch error: {e}")
178
  return {"stocks": {}}, "Fetch failed"
179
 
180
+ # ---------------- Core logic ----------------
 
 
181
  def query_inventory(query_text="", category="", country_name="", capacity=10, refresh=False):
182
+ print(f"πŸ”Ž Query received: '{query_text}', category='{category}', country='{country_name}', cap={capacity}")
 
 
 
 
183
  data, last_update = fetch_yata(force_refresh=refresh)
184
  stocks = data.get("stocks", {})
185
  rows = []
186
 
187
+ # Parse query
188
  parsed_item, parsed_country = parse_freeform_query(query_text)
189
  if not country_name and parsed_country:
190
  country_name = parsed_country
 
192
  semantic_result = semantic_match(item_term) if item_term else {"category": None, "items": []}
193
  semantic_items = semantic_result["items"]
194
  semantic_category = semantic_result["category"]
 
195
 
196
+ # Country gating (strict)
197
+ user_code = normalize_country_query(country_name)
198
 
199
  for code_raw, cdata in stocks.items():
200
  code = code_raw.upper()
201
  cname = COUNTRY_NAMES.get(code, code)
202
+
203
  if country_name:
204
  if user_code:
205
  if code != user_code:
206
  continue
207
  elif country_name.lower() not in cname.lower():
208
  continue
209
+
210
  update_ts = cdata.get("update")
211
  update_str = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(update_ts)) if update_ts else "Unknown"
 
 
212
 
213
+ # ONLY iterate this country's actual items (no global catalog)
214
+ for item in cdata.get("stocks", []):
215
+ iname = item.get("name", "")
216
  itype = ITEM_TO_TYPE.get(iname, "").lower()
217
+ qty = item.get("quantity", 0)
218
+ cost = item.get("cost", 0)
219
+
220
+ # Item filter
221
+ if item_term:
222
+ item_ok = (
223
  item_term.lower() in iname.lower()
224
  or iname in semantic_items
225
  or (semantic_category and itype == semantic_category.lower())
226
+ )
227
+ elif category:
228
+ item_ok = category.lower() == itype
229
+ else:
230
+ item_ok = True
231
+
232
  if item_ok:
233
  rows.append({
234
  "Country": cname,
 
236
  "Category": itype.title() if itype else "",
237
  "Quantity": qty,
238
  "Cost": cost,
239
+ # IMPORTANT: use the function arg 'capacity' directly
240
+ "Max Capacity Cost": cost * capacity,
241
  "Updated": update_str,
242
  })
243
 
 
248
  for col in ["Quantity", "Cost", "Max Capacity Cost"]:
249
  if col in df.columns:
250
  df[col] = df[col].apply(lambda x: f"{x:,.0f}" if isinstance(x, (int, float)) and x != "" else x)
251
+ return df, f"Last update: {last_update}"
252
 
253
+ # ---------------- Wrapper ----------------
254
  def run_query(query_text, category, country, capacity, refresh):
255
  return query_inventory(query_text, category, country, capacity, refresh)
256
 
257
+ # ---------------- Gradio UI ----------------
258
  with gr.Blocks(title="🧳 Torn Inventory Viewer") as iface:
259
  gr.Markdown("## 🧳 Torn Inventory Viewer")
260
+ gr.Markdown("_Search Torn YATA travel stocks with semantic matching._")
261
+
262
+ query_box = gr.Textbox(label="Search (semantic, e.g. 'flowers in England')", elem_id="qbox")
263
+ category_drop = gr.Dropdown(label="Category (optional exact match)", choices=[""] + ALL_CATEGORIES, elem_id="catdrop")
264
+ country_box = gr.Textbox(label="Country (optional, e.g. UK, Cayman, Japan)", elem_id="countrybox")
265
+ capacity_slider = gr.Number(label="Travel Capacity", value=10, minimum=5, maximum=88, precision=0, elem_id="cap_num")
266
+ refresh_check = gr.Checkbox(label="Force refresh (ignore cache)", value=False)
267
+
268
+ # Hidden bridge for capacity β†’ backend init
269
+ cap_bridge = gr.Textbox(visible=False, elem_id="cap_bridge")
270
+
271
+ def apply_bridge(cap_text):
272
+ try:
273
+ v = float(cap_text)
274
+ if 5 <= v <= 88:
275
+ return gr.Number.update(value=v)
276
+ except:
277
+ pass
278
+ return gr.Number.update() # no change
279
 
280
+ # When the bridge receives a value, update the slider (so backend sees it)
281
+ cap_bridge.change(apply_bridge, inputs=[cap_bridge], outputs=[capacity_slider])
 
 
 
282
 
283
  run_btn = gr.Button("πŸ” Search / Refresh")
284
  result_df = gr.Dataframe(label="Results")
 
288
  inputs=[query_box, category_drop, country_box, capacity_slider, refresh_check],
289
  outputs=[result_df, meta_box])
290
 
291
+ # --- Shadow-DOM aware JS: restore capacity & push into hidden bridge BEFORE first search ---
292
  gr.HTML("""
293
  <script>
294
  (function () {
 
296
  const app = document.querySelector('gradio-app');
297
  return app && app.shadowRoot ? app.shadowRoot : document;
298
  }
299
+ function el(sel) { return root().querySelector(sel); }
300
+
301
+ function restoreCapacity() {
302
  const saved = localStorage.getItem('travel_capacity');
303
+ const capField = el('#cap_num input[type=number]');
304
+ const bridge = el('#cap_bridge textarea');
305
+
306
+ if (saved && capField) {
307
+ // Update visible field
308
+ capField.value = saved;
309
+ // Update frontend state
310
+ capField.dispatchEvent(new Event('input', { bubbles: true }));
311
+ // Push into hidden bridge -> triggers backend .change -> updates slider value server-side
312
+ if (bridge) {
313
+ bridge.value = saved;
314
+ bridge.dispatchEvent(new Event('input', { bubbles: true }));
315
+ bridge.dispatchEvent(new Event('change', { bubbles: true }));
316
+ }
317
+ }
318
+
319
+ // Save on changes
320
+ if (capField && !capField.dataset.synced) {
321
+ capField.dataset.synced = '1';
322
+ capField.addEventListener('input', () => {
323
+ localStorage.setItem('travel_capacity', capField.value);
324
+ });
325
+ }
326
  }
327
+
328
  window.addEventListener('DOMContentLoaded', () => {
329
+ setTimeout(restoreCapacity, 500);
330
  });
331
+
332
+ // Keep trying on re-renders
333
+ const obs = new MutationObserver(() => setTimeout(restoreCapacity, 100));
334
+ obs.observe(document.documentElement, { childList: true, subtree: true });
335
  })();
336
  </script>
337
  """)
 
341
  iface.launch()
342
  except Exception as e:
343
  import traceback
344
+ print("❌ Application failed to start:")
345
  traceback.print_exc()