buzzbandit commited on
Commit
4703376
Β·
verified Β·
1 Parent(s): 2d2298c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +160 -59
app.py CHANGED
@@ -20,6 +20,7 @@ with open("items.json", "r", encoding="utf-8") as f:
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
 
@@ -115,7 +116,7 @@ def parse_freeform_query(text: str):
115
  if not text:
116
  return "", ""
117
  text = text.strip().lower()
118
- m = re.match(r"(.+?)\\s+in\\s+(.+)", text, flags=re.IGNORECASE)
119
  if m:
120
  return m.group(1).strip(), m.group(2).strip()
121
  parts = text.split()
@@ -155,7 +156,7 @@ def fetch_yata(force_refresh=False):
155
  _cache.update({
156
  "data": data,
157
  "timestamp": time.time(),
158
- "last_update": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
159
  })
160
  return data, _cache["last_update"]
161
  except Exception as e:
@@ -172,38 +173,62 @@ def get_live_categories(data):
172
  live_cats.add(cat.lower())
173
  return sorted(live_cats)
174
 
175
- # ---------------- Core logic ----------------
176
  def query_inventory(query_text="", category="", country_name="", capacity=10, refresh=False):
177
  data, last_update = fetch_yata(force_refresh=refresh)
178
  rows = []
 
 
179
  parsed_item, parsed_country = parse_freeform_query(query_text)
180
  if not country_name and parsed_country:
181
  country_name = parsed_country
182
  item_term = parsed_item
183
- semantic_result = semantic_match(item_term) if item_term else {"category": None, "items": []}
184
- semantic_items = semantic_result["items"]
185
- semantic_category = semantic_result["category"]
 
 
 
 
 
 
186
  user_code = normalize_country_query(country_name)
 
187
  for code_raw, cdata in data.get("stocks", {}).items():
188
  code = code_raw.upper()
189
  cname = COUNTRY_NAMES.get(code, code)
 
190
  if country_name:
191
- if user_code and code != user_code:
192
- continue
193
- elif not user_code and country_name.lower() not in cname.lower():
 
194
  continue
 
195
  update_ts = cdata.get("update")
196
  update_str = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime(update_ts)) if update_ts else "Unknown"
 
197
  for item in cdata.get("stocks", []):
198
  iname = item.get("name", "")
199
  itype = ITEM_TO_TYPE.get(iname, "").lower()
200
  qty = item.get("quantity", 0)
201
  cost = item.get("cost", 0)
202
- item_ok = (
203
- (item_term and (item_term.lower() in iname.lower() or iname in semantic_items or (semantic_category and itype == semantic_category.lower())))
204
- or (category and category.lower() == itype)
205
- or (not item_term and not category)
206
- )
 
 
 
 
 
 
 
 
 
 
 
207
  if item_ok:
208
  rows.append({
209
  "Country": cname,
@@ -214,13 +239,97 @@ def query_inventory(query_text="", category="", country_name="", capacity=10, re
214
  "Max Capacity Cost": cost * capacity,
215
  "Updated": update_str
216
  })
 
217
  if not rows:
218
  return pd.DataFrame([{"Result": "No inventory found for that query."}]), f"Last update: {last_update}"
 
219
  df = pd.DataFrame(rows).sort_values(by=["Country", "Item"])
220
  for col in ["Quantity", "Cost", "Max Capacity Cost"]:
221
  df[col] = df[col].apply(lambda x: f"{x:,.0f}" if isinstance(x, (int, float)) else x)
222
  return df, f"Last update: {last_update}"
223
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
224
  # ---------------- Wrappers ----------------
225
  def run_query(query_text, category, country, capacity, refresh):
226
  data, _ = fetch_yata(force_refresh=refresh)
@@ -228,67 +337,59 @@ def run_query(query_text, category, country, capacity, refresh):
228
  live_categories = get_live_categories(data)
229
  return df, ts, gr.update(choices=[""] + live_categories)
230
 
231
- def run_multi(searches, capacity):
232
- """Executes multiple searches (like convenience buttons) correctly filtering by country."""
233
- dfs = []
234
- print(f"🧩 Running multi-search: {len(searches)} queries")
235
- for q in searches:
236
- item_term, country_term = parse_freeform_query(q)
237
- print(f" πŸ” Sub-query β†’ item='{item_term}', country='{country_term}'")
238
- df, _ = query_inventory(item_term, "", country_term, capacity, False)
239
- dfs.append(df)
240
- merged = pd.concat(dfs, ignore_index=True)
241
- total = pd.DataFrame([{
242
- "Country": "β€”", "Item": "TOTAL", "Category": "",
243
- "Quantity": merged["Quantity"].replace(",", "", regex=True).astype(float).sum(),
244
- "Cost": "", "Max Capacity Cost": "", "Updated": ""
245
- }])
246
- merged = pd.concat([merged, total])
247
- return merged, f"Last update: {time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime())}"
248
-
249
  # ---------------- Gradio UI ----------------
250
- with gr.Blocks(title="🧳 Torn Foreign Stocks") as iface:
251
- gr.Markdown("## 🧳 Torn Foreign Stocks")
252
- gr.Markdown("_Search YATA's foreign stocks data_")
253
-
254
- query_box = gr.Textbox(label="Search (semantic, e.g. 'flowers in England')")
255
- category_drop = gr.Dropdown(label="Category (optional exact match)", choices=[""] + ALL_CATEGORIES)
256
- country_box = gr.Textbox(label="Country (optional, e.g. UK, Cayman, Japan)")
257
- capacity_slider = gr.Number(label="Travel Capacity", value=10, minimum=5, maximum=88, precision=0)
258
- refresh_check = gr.Checkbox(label="Force refresh (ignore cache)", value=False)
259
 
 
260
  with gr.Row():
261
  btn_short = gr.Button("🌸 Flushies (short haul)")
262
  btn_medium = gr.Button("🧸 Flushies (medium haul)")
263
  btn_long = gr.Button("🎁 Flushies (long haul)")
264
- btn_xanax = gr.Button("πŸ’Š Xanax (SA)")
265
  btn_temps = gr.Button("🧨 Temps")
266
 
 
 
 
 
 
 
267
  result_df = gr.Dataframe(label="Results")
268
  meta_box = gr.Textbox(label="Metadata / Last Update")
269
  run_btn = gr.Button("πŸ” Search / Refresh")
270
 
271
- run_btn.click(run_query, inputs=[query_box, category_drop, country_box, capacity_slider, refresh_check],
 
272
  outputs=[result_df, meta_box, category_drop])
273
 
274
- btn_short.click(lambda c: run_multi(["flowers in mexico", "flowers in cayman islands", "flowers in canada",
275
- "plushies in mexico", "plushies in cayman islands", "plushies in canada"], c),
276
- inputs=[capacity_slider], outputs=[result_df, meta_box])
277
- btn_medium.click(lambda c: run_multi(["flowers in hawaii", "flowers in united kingdom", "flowers in argentina",
278
- "flowers in switzerland", "flowers in japan",
279
- "plushies in hawaii", "plushies in united kingdom", "plushies in argentina",
280
- "plushies in switzerland", "plushies in japan"], c),
281
- inputs=[capacity_slider], outputs=[result_df, meta_box])
282
- btn_long.click(lambda c: run_multi(["flowers in uae", "flowers in china", "flowers in south africa",
283
- "plushies in uae", "plushies in china", "plushies in south africa"], c),
284
- inputs=[capacity_slider], outputs=[result_df, meta_box])
 
 
 
 
 
 
 
285
  btn_xanax.click(lambda c: run_multi(["xanax in south africa"], c),
286
- inputs=[capacity_slider], outputs=[result_df, meta_box])
287
- btn_temps.click(lambda c: run_multi(["tear gas in argentina", "smoke grenade in south africa",
288
- "flash grenade in switzerland"], c),
289
- inputs=[capacity_slider], outputs=[result_df, meta_box])
 
290
 
291
- # --- JS for local time conversion ---
292
  gr.HTML("""
293
  <script>
294
  (function(){
 
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_ITEMS_LOWER = {name.lower(): name for name in ALL_ITEMS}
24
  ALL_CATEGORIES = sorted(set(ITEM_TO_TYPE.values()))
25
  ITEM_FILE_MTIME = os.path.getmtime("items.json")
26
 
 
116
  if not text:
117
  return "", ""
118
  text = text.strip().lower()
119
+ m = re.match(r"(.+?)\s+in\s+(.+)", text, flags=re.IGNORECASE)
120
  if m:
121
  return m.group(1).strip(), m.group(2).strip()
122
  parts = text.split()
 
156
  _cache.update({
157
  "data": data,
158
  "timestamp": time.time(),
159
+ "last_update": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()) # UTC ISO
160
  })
161
  return data, _cache["last_update"]
162
  except Exception as e:
 
173
  live_cats.add(cat.lower())
174
  return sorted(live_cats)
175
 
176
+ # ---------------- Core logic: single query ----------------
177
  def query_inventory(query_text="", category="", country_name="", capacity=10, refresh=False):
178
  data, last_update = fetch_yata(force_refresh=refresh)
179
  rows = []
180
+
181
+ # Parse freeform if present
182
  parsed_item, parsed_country = parse_freeform_query(query_text)
183
  if not country_name and parsed_country:
184
  country_name = parsed_country
185
  item_term = parsed_item
186
+
187
+ # Detect if user meant an exact item (e.g., "xanax")
188
+ item_lower = (item_term or "").lower()
189
+ exact_item_name = ALL_ITEMS_LOWER.get(item_lower)
190
+ sem = semantic_match(item_term) if item_term and not exact_item_name else {"category": None, "items": []}
191
+ semantic_items = sem["items"]
192
+ semantic_category = sem["category"]
193
+
194
+ # Country gating (strict)
195
  user_code = normalize_country_query(country_name)
196
+
197
  for code_raw, cdata in data.get("stocks", {}).items():
198
  code = code_raw.upper()
199
  cname = COUNTRY_NAMES.get(code, code)
200
+
201
  if country_name:
202
+ if user_code:
203
+ if code != user_code:
204
+ continue
205
+ elif country_name.lower() not in cname.lower():
206
  continue
207
+
208
  update_ts = cdata.get("update")
209
  update_str = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime(update_ts)) if update_ts else "Unknown"
210
+
211
  for item in cdata.get("stocks", []):
212
  iname = item.get("name", "")
213
  itype = ITEM_TO_TYPE.get(iname, "").lower()
214
  qty = item.get("quantity", 0)
215
  cost = item.get("cost", 0)
216
+
217
+ # Strict item filtering
218
+ if item_term:
219
+ if exact_item_name:
220
+ item_ok = (iname.lower() == item_lower) # exact item only
221
+ else:
222
+ item_ok = (
223
+ (item_lower and item_lower in iname.lower()) or
224
+ (semantic_category and itype == semantic_category.lower()) or
225
+ (iname in semantic_items)
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,
 
239
  "Max Capacity Cost": cost * capacity,
240
  "Updated": update_str
241
  })
242
+
243
  if not rows:
244
  return pd.DataFrame([{"Result": "No inventory found for that query."}]), f"Last update: {last_update}"
245
+
246
  df = pd.DataFrame(rows).sort_values(by=["Country", "Item"])
247
  for col in ["Quantity", "Cost", "Max Capacity Cost"]:
248
  df[col] = df[col].apply(lambda x: f"{x:,.0f}" if isinstance(x, (int, float)) else x)
249
  return df, f"Last update: {last_update}"
250
 
251
+ # ---------------- Multi-query (convenience buttons) ----------------
252
+ def run_multi(phrases, capacity):
253
+ """
254
+ Execute multiple 'item in country' phrases with strict per-country filtering and no duplicates.
255
+ """
256
+ data, last_update = fetch_yata(False)
257
+
258
+ # Group requested item_terms by normalized country code
259
+ tasks_by_code = {} # code -> [item_term, ...]
260
+ for phrase in phrases:
261
+ item_term, country_term = parse_freeform_query(phrase)
262
+ code = normalize_country_query(country_term) or ""
263
+ if not code:
264
+ # If country not recognized (shouldn't happen with our lists), fall back to pass-through single query
265
+ code = "__ALL__"
266
+ tasks_by_code.setdefault(code, []).append(item_term)
267
+
268
+ rows = []
269
+ for code_raw, cdata in data.get("stocks", {}).items():
270
+ code = code_raw.upper()
271
+ if code not in tasks_by_code:
272
+ continue
273
+
274
+ cname = COUNTRY_NAMES.get(code, code)
275
+ update_ts = cdata.get("update")
276
+ update_str = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime(update_ts)) if update_ts else "Unknown"
277
+
278
+ # Precompute semantic intents for each term in this country
279
+ compiled_terms = []
280
+ for term in tasks_by_code[code]:
281
+ t = (term or "").strip().lower()
282
+ exact_item_name = ALL_ITEMS_LOWER.get(t)
283
+ if exact_item_name:
284
+ compiled_terms.append({"mode": "exact", "value": t})
285
+ else:
286
+ sem = semantic_match(t) if t else {"category": None, "items": []}
287
+ compiled_terms.append({
288
+ "mode": "fuzzy",
289
+ "value": t,
290
+ "category": (sem["category"] or "").lower() if sem["category"] else "",
291
+ "items": set(sem["items"])
292
+ })
293
+
294
+ # Scan this country's items once
295
+ for item in cdata.get("stocks", []):
296
+ iname = item.get("name", "")
297
+ itype = ITEM_TO_TYPE.get(iname, "").lower()
298
+ qty = item.get("quantity", 0)
299
+ cost = item.get("cost", 0)
300
+
301
+ matched = False
302
+ for ct in compiled_terms:
303
+ if ct["mode"] == "exact":
304
+ if iname.lower() == ct["value"]:
305
+ matched = True
306
+ break
307
+ else:
308
+ q = ct["value"]
309
+ if (q and q in iname.lower()) or (ct["category"] and itype == ct["category"]) or (iname in ct["items"]):
310
+ matched = True
311
+ break
312
+
313
+ if matched:
314
+ rows.append({
315
+ "Country": cname,
316
+ "Item": iname,
317
+ "Category": itype.title(),
318
+ "Quantity": qty,
319
+ "Cost": cost,
320
+ "Max Capacity Cost": cost * capacity,
321
+ "Updated": update_str
322
+ })
323
+
324
+ if not rows:
325
+ return pd.DataFrame([{"Result": "No results for that set."}]), f"Last update: {last_update}"
326
+
327
+ # Deduplicate rows by (Country, Item, Updated)
328
+ df = pd.DataFrame(rows).drop_duplicates(subset=["Country", "Item", "Updated"]).sort_values(by=["Country", "Item"])
329
+ for col in ["Quantity", "Cost", "Max Capacity Cost"]:
330
+ df[col] = df[col].apply(lambda x: f"{x:,.0f}" if isinstance(x, (int, float)) else x)
331
+ return df, f"Last update: {last_update}"
332
+
333
  # ---------------- Wrappers ----------------
334
  def run_query(query_text, category, country, capacity, refresh):
335
  data, _ = fetch_yata(force_refresh=refresh)
 
337
  live_categories = get_live_categories(data)
338
  return df, ts, gr.update(choices=[""] + live_categories)
339
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
340
  # ---------------- Gradio UI ----------------
341
+ with gr.Blocks(title="🧳 Torn Inventory Viewer") as iface:
342
+ gr.Markdown("## 🧳 Torn Inventory Viewer")
343
+ gr.Markdown("_Search Torn YATA travel stocks β€” timestamps shown in your local timezone._")
 
 
 
 
 
 
344
 
345
+ # Convenience buttons
346
  with gr.Row():
347
  btn_short = gr.Button("🌸 Flushies (short haul)")
348
  btn_medium = gr.Button("🧸 Flushies (medium haul)")
349
  btn_long = gr.Button("🎁 Flushies (long haul)")
350
+ btn_xanax = gr.Button("πŸ’Š Xanax")
351
  btn_temps = gr.Button("🧨 Temps")
352
 
353
+ query_box = gr.Textbox(label="Search (semantic, e.g. 'flowers in England')")
354
+ category_drop = gr.Dropdown(label="Category (optional exact match)", choices=[""] + ALL_CATEGORIES)
355
+ country_box = gr.Textbox(label="Country (optional, e.g. UK, Cayman, Japan)")
356
+ capacity_slider = gr.Number(label="Travel Capacity", value=10, minimum=5, maximum=88, precision=0)
357
+ refresh_check = gr.Checkbox(label="Force refresh (ignore cache)", value=False)
358
+
359
  result_df = gr.Dataframe(label="Results")
360
  meta_box = gr.Textbox(label="Metadata / Last Update")
361
  run_btn = gr.Button("πŸ” Search / Refresh")
362
 
363
+ run_btn.click(run_query,
364
+ inputs=[query_box, category_drop, country_box, capacity_slider, refresh_check],
365
  outputs=[result_df, meta_box, category_drop])
366
 
367
+ # Convenience button bindings (use run_multi with per-country grouping)
368
+ btn_short.click(lambda c: run_multi(
369
+ ["flowers in mexico", "flowers in cayman islands", "flowers in canada",
370
+ "plushies in mexico", "plushies in cayman islands", "plushies in canada"], c),
371
+ inputs=[capacity_slider], outputs=[result_df, meta_box])
372
+
373
+ btn_medium.click(lambda c: run_multi(
374
+ ["flowers in hawaii", "flowers in united kingdom", "flowers in argentina",
375
+ "flowers in switzerland", "flowers in japan",
376
+ "plushies in hawaii", "plushies in united kingdom", "plushies in argentina",
377
+ "plushies in switzerland", "plushies in japan"], c),
378
+ inputs=[capacity_slider], outputs=[result_df, meta_box])
379
+
380
+ btn_long.click(lambda c: run_multi(
381
+ ["flowers in uae", "flowers in china", "flowers in south africa",
382
+ "plushies in uae", "plushies in china", "plushies in south africa"], c),
383
+ inputs=[capacity_slider], outputs=[result_df, meta_box])
384
+
385
  btn_xanax.click(lambda c: run_multi(["xanax in south africa"], c),
386
+ inputs=[capacity_slider], outputs=[result_df, meta_box])
387
+
388
+ btn_temps.click(lambda c: run_multi(
389
+ ["tear gas in argentina", "smoke grenade in south africa", "flash grenade in switzerland"], c),
390
+ inputs=[capacity_slider], outputs=[result_df, meta_box])
391
 
392
+ # --- JS: convert all UTC ISO timestamps to browser's local time ---
393
  gr.HTML("""
394
  <script>
395
  (function(){