Lavender825 commited on
Commit
71dd534
·
1 Parent(s): cddf904

Polish merchant spotlight and category inputs

Browse files
Files changed (1) hide show
  1. app.py +60 -10
app.py CHANGED
@@ -322,6 +322,16 @@ def _tags_for_category(category: str) -> List[str]:
322
  return sorted({tag for p in products for tag in p.get("tags", [])})
323
 
324
 
 
 
 
 
 
 
 
 
 
 
325
  def update_product_choices(category: str):
326
  names = _product_names_for_category(category)
327
  return gr.update(choices=names, value=names[0] if names else None)
@@ -397,7 +407,7 @@ def _predict_product(product_name: str) -> Dict[str, Any]:
397
 
398
 
399
  def _predict_custom(review: str, features: str, categories: str, price: Any, rating: Any, count: Any) -> Dict[str, Any]:
400
- meta = {"features_text": features or "", "categories_text": categories or "", "price": _safe_float(price), "average_rating": _safe_float(rating), "rating_number": _safe_float(count)}
401
  return _predictor().predict(review or "No review text provided.", meta)
402
 
403
 
@@ -749,6 +759,38 @@ def export_merchant_scores(rows):
749
 
750
 
751
  def negative_product_spotlight(limit: int = 6) -> str:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
752
  items = []
753
  for product in PRODUCTS:
754
  try:
@@ -865,7 +907,7 @@ def _read_uploaded_records(file_obj) -> List[Dict[str, Any]]:
865
  def download_new_product_template():
866
  rows = [[
867
  "Cotton Polyester Blend, Slim Fit, Graphic Print, Machine Wash",
868
- "Clothing > Women > Tops > T-Shirts",
869
  29.99,
870
  4.1,
871
  35,
@@ -873,7 +915,7 @@ def download_new_product_template():
873
  ]]
874
  return _write_csv_download(
875
  "new_product_metadata_template.csv",
876
- ["features", "categories", "price", "average_rating", "rating_number", "focus_aspect"],
877
  rows,
878
  )
879
 
@@ -885,7 +927,7 @@ def batch_screen_new_products(file_obj) -> Tuple[str, List[List[Any]]]:
885
  rows = []
886
  for i, data in enumerate(records, 1):
887
  features = data.get("features") or data.get("features_text") or ""
888
- categories = data.get("categories") or data.get("categories_text") or ""
889
  price = _safe_float(data.get("price"), 0.0)
890
  rating = _safe_float(data.get("average_rating") or data.get("rating"), 4.0)
891
  count = _safe_float(data.get("rating_number") or data.get("rating_count"), 0.0)
@@ -945,7 +987,7 @@ def download_external_review_template():
945
  rows = [[
946
  "The fabric is soft and the color looks good, but it runs small.",
947
  "Cotton Blend, Slim Fit, Zipper Closure",
948
- "Clothing > Women > Jackets",
949
  39.99,
950
  4.2,
951
  312,
@@ -953,7 +995,7 @@ def download_external_review_template():
953
  ]]
954
  return _write_csv_download(
955
  "external_review_template.csv",
956
- ["review", "features", "categories", "price", "average_rating", "rating_number", "highlight_aspect"],
957
  rows,
958
  )
959
 
@@ -966,7 +1008,7 @@ def batch_external_review_predict(file_obj) -> Tuple[str, str, List[List[Any]]]:
966
  for i, data in enumerate(records, 1):
967
  review = data.get("review") or data.get("review_text") or ""
968
  features = data.get("features") or data.get("features_text") or ""
969
- categories = data.get("categories") or data.get("categories_text") or ""
970
  price = _safe_float(data.get("price"), 0.0)
971
  rating = _safe_float(data.get("average_rating") or data.get("rating"), 4.0)
972
  count = _safe_float(data.get("rating_number") or data.get("rating_count"), 0.0)
@@ -1060,7 +1102,14 @@ mark { background:#fde68a; color:#111827; border-radius:4px; padding:1px 3px; }
1060
  .negative-grid { display:grid; grid-template-columns:repeat(6, minmax(0, 1fr)); gap:10px; margin:8px 0 14px; }
1061
  .negative-card { display:grid; grid-template-columns:64px 1fr; gap:10px; border:1px solid #fecaca; border-left:4px solid #ef4444; border-radius:8px; padding:10px; background:#fffafa; }
1062
  .negative-card p { margin:5px 0 0; color:#334155; font-size:13px; }
 
 
 
 
 
 
1063
  .mini-product-img { width:64px; height:76px; object-fit:cover; border-radius:6px; border:1px solid var(--line); background:#f8fafc; }
 
1064
  @media (max-width:1100px) { .negative-grid { grid-template-columns:repeat(3, minmax(0, 1fr)); } }
1065
  @media (max-width:860px) { .aspect-grid, .metric-grid, .product-layout, .decision-layout, .negative-grid { grid-template-columns:1fr; } .product-img { width:100%; height:180px; } }
1066
  """
@@ -1068,6 +1117,8 @@ mark { background:#fde68a; color:#111827; border-radius:4px; padding:1px 3px; }
1068
 
1069
  def build_app() -> gr.Blocks:
1070
  categories = ["All"] + sorted({p["category"] for p in PRODUCTS})
 
 
1071
  tags = sorted({tag for p in PRODUCTS for tag in p.get("tags", [])})
1072
  with gr.Blocks(css=CSS, title="Clothing Sentiment Analysis") as demo:
1073
  gr.HTML('<div id="hero"><div class="eyebrow">BERT + Metadata Cross-Attention</div><h1>Clothing Review Sentiment Intelligence App</h1><p>Explore overall sentiment, six aspect-level opinions, metadata-driven risks, and updated 10W experiment reports in a compact customer decision-support prototype.</p><div class="hero-pills"><span>Consumer decision support</span><span>Merchant diagnostics</span><span>Research dashboard</span></div></div>')
@@ -1107,7 +1158,6 @@ def build_app() -> gr.Blocks:
1107
  gr.HTML('<div class="module-head"><h3>Product Score Monitor</h3><span class="info-tip">i<span class="tip-content">' + model_info_html() + '</span></span></div>')
1108
  gr.HTML('<div class="small-label">Most negative products across the six aspects</div>')
1109
  negative_spotlight = gr.HTML('<div class="note-card">Loading most negative products...</div>')
1110
- aspect_overview = gr.Dataframe(headers=["Aspect", "Negative Count", "Most Negative Product", "Confidence", "Reason"], value=merchant_aspect_overview(), datatype=["str", "number", "str", "number", "str"], interactive=False, label="Six-aspect negative overview")
1111
  with gr.Accordion("Score filters and export", open=True):
1112
  with gr.Row():
1113
  merchant_metric = gr.Dropdown(["Overall"] + ASPECTS, value="Overall", label="Aspect")
@@ -1124,7 +1174,7 @@ def build_app() -> gr.Blocks:
1124
  with gr.Column(scale=1):
1125
  with gr.Group(visible=True) as risk_single_group:
1126
  new_features = gr.Textbox("Cotton Polyester Blend, Slim Fit, Graphic Print, Machine Wash", label="New product features", lines=4)
1127
- new_categories = gr.Textbox("Clothing > Women > Tops > T-Shirts", label="New product categories")
1128
  with gr.Row():
1129
  new_price = gr.Number(29.99, label="Price")
1130
  new_rating = gr.Number(4.1, label="Expected or early average rating")
@@ -1151,7 +1201,7 @@ def build_app() -> gr.Blocks:
1151
  external_review = gr.Textbox("The fabric is soft and the color looks good, but it runs small and the zipper feels weak.", label="External customer review", lines=5)
1152
  with gr.Row():
1153
  ext_features = gr.Textbox("Cotton Blend, Slim Fit, Zipper Closure", label="Product features", lines=3)
1154
- ext_categories = gr.Textbox("Clothing > Women > Jackets", label="Product categories", lines=3)
1155
  with gr.Row():
1156
  ext_price = gr.Number(39.99, label="Price")
1157
  ext_rating = gr.Number(4.2, label="Average rating")
 
322
  return sorted({tag for p in products for tag in p.get("tags", [])})
323
 
324
 
325
+ def _category_to_metadata_text(category: str) -> str:
326
+ category = str(category or "").strip()
327
+ if not category:
328
+ return "Clothing"
329
+ for product in PRODUCTS:
330
+ if product.get("category") == category and product.get("categories"):
331
+ return product["categories"]
332
+ return f"Clothing > {category}"
333
+
334
+
335
  def update_product_choices(category: str):
336
  names = _product_names_for_category(category)
337
  return gr.update(choices=names, value=names[0] if names else None)
 
407
 
408
 
409
  def _predict_custom(review: str, features: str, categories: str, price: Any, rating: Any, count: Any) -> Dict[str, Any]:
410
+ meta = {"features_text": features or "", "categories_text": _category_to_metadata_text(categories), "price": _safe_float(price), "average_rating": _safe_float(rating), "rating_number": _safe_float(count)}
411
  return _predictor().predict(review or "No review text provided.", meta)
412
 
413
 
 
759
 
760
 
761
  def negative_product_spotlight(limit: int = 6) -> str:
762
+ cards = []
763
+ for aspect in ASPECTS:
764
+ best = None
765
+ for product in PRODUCTS:
766
+ try:
767
+ result = _predict_product(product["name"])
768
+ except Exception:
769
+ continue
770
+ d = result.get("aspect_details", {}).get(aspect, {})
771
+ if d.get("label") != "Negative":
772
+ continue
773
+ conf = _safe_float(d.get("confidence"))
774
+ hits = _keyword_hits(product.get("review", ""), aspect)
775
+ reason = ", ".join(hits[:3]) if hits else _short_text(product.get("review") or product.get("features"), 48)
776
+ if best is None or conf > best[0]:
777
+ best = (conf, product, reason)
778
+ if best is None:
779
+ cards.append(
780
+ f'<div class="negative-card compact-negative"><h4>{aspect}</h4>'
781
+ '<div class="empty-negative">No strong negative sample</div></div>'
782
+ )
783
+ continue
784
+ conf, product, reason = best
785
+ img = f'<img class="mini-product-img" src="{_esc(_product_image_url(product))}" alt="{_esc(product["name"])}">'
786
+ cards.append(
787
+ f'<div class="negative-card compact-negative"><h4>{aspect}</h4>'
788
+ f'<div class="negative-body">{img}<div><b>{_esc(_short_text(product["name"], 42))}</b>'
789
+ f'<div class="small-label">{_esc(product["category"])} | conf {conf:.2f}</div>'
790
+ f'<p>{_esc(_short_text(reason, 56))}</p></div></div></div>'
791
+ )
792
+ return '<div class="negative-grid">' + "".join(cards[:limit]) + '</div>'
793
+
794
  items = []
795
  for product in PRODUCTS:
796
  try:
 
907
  def download_new_product_template():
908
  rows = [[
909
  "Cotton Polyester Blend, Slim Fit, Graphic Print, Machine Wash",
910
+ "T-Shirts",
911
  29.99,
912
  4.1,
913
  35,
 
915
  ]]
916
  return _write_csv_download(
917
  "new_product_metadata_template.csv",
918
+ ["features", "category", "price", "average_rating", "rating_number", "focus_aspect"],
919
  rows,
920
  )
921
 
 
927
  rows = []
928
  for i, data in enumerate(records, 1):
929
  features = data.get("features") or data.get("features_text") or ""
930
+ categories = data.get("category") or data.get("categories") or data.get("categories_text") or ""
931
  price = _safe_float(data.get("price"), 0.0)
932
  rating = _safe_float(data.get("average_rating") or data.get("rating"), 4.0)
933
  count = _safe_float(data.get("rating_number") or data.get("rating_count"), 0.0)
 
987
  rows = [[
988
  "The fabric is soft and the color looks good, but it runs small.",
989
  "Cotton Blend, Slim Fit, Zipper Closure",
990
+ "Jackets",
991
  39.99,
992
  4.2,
993
  312,
 
995
  ]]
996
  return _write_csv_download(
997
  "external_review_template.csv",
998
+ ["review", "features", "category", "price", "average_rating", "rating_number", "highlight_aspect"],
999
  rows,
1000
  )
1001
 
 
1008
  for i, data in enumerate(records, 1):
1009
  review = data.get("review") or data.get("review_text") or ""
1010
  features = data.get("features") or data.get("features_text") or ""
1011
+ categories = data.get("category") or data.get("categories") or data.get("categories_text") or ""
1012
  price = _safe_float(data.get("price"), 0.0)
1013
  rating = _safe_float(data.get("average_rating") or data.get("rating"), 4.0)
1014
  count = _safe_float(data.get("rating_number") or data.get("rating_count"), 0.0)
 
1102
  .negative-grid { display:grid; grid-template-columns:repeat(6, minmax(0, 1fr)); gap:10px; margin:8px 0 14px; }
1103
  .negative-card { display:grid; grid-template-columns:64px 1fr; gap:10px; border:1px solid #fecaca; border-left:4px solid #ef4444; border-radius:8px; padding:10px; background:#fffafa; }
1104
  .negative-card p { margin:5px 0 0; color:#334155; font-size:13px; }
1105
+ .compact-negative { display:block; min-height:178px; }
1106
+ .compact-negative h4 { margin:0 0 8px; color:#b91c1c; font-size:14px; letter-spacing:.02em; }
1107
+ .negative-body { display:grid; grid-template-columns:54px 1fr; gap:8px; align-items:start; }
1108
+ .negative-body b { display:block; font-size:13px; line-height:1.25; }
1109
+ .negative-body p { font-size:12px; line-height:1.35; }
1110
+ .empty-negative { color:#64748b; font-size:12px; border:1px dashed #fecaca; border-radius:8px; padding:14px 8px; background:white; }
1111
  .mini-product-img { width:64px; height:76px; object-fit:cover; border-radius:6px; border:1px solid var(--line); background:#f8fafc; }
1112
+ .negative-body .mini-product-img { width:54px; height:64px; }
1113
  @media (max-width:1100px) { .negative-grid { grid-template-columns:repeat(3, minmax(0, 1fr)); } }
1114
  @media (max-width:860px) { .aspect-grid, .metric-grid, .product-layout, .decision-layout, .negative-grid { grid-template-columns:1fr; } .product-img { width:100%; height:180px; } }
1115
  """
 
1117
 
1118
  def build_app() -> gr.Blocks:
1119
  categories = ["All"] + sorted({p["category"] for p in PRODUCTS})
1120
+ merchant_categories = categories[1:] or ["Clothing"]
1121
+ default_merchant_category = merchant_categories[0]
1122
  tags = sorted({tag for p in PRODUCTS for tag in p.get("tags", [])})
1123
  with gr.Blocks(css=CSS, title="Clothing Sentiment Analysis") as demo:
1124
  gr.HTML('<div id="hero"><div class="eyebrow">BERT + Metadata Cross-Attention</div><h1>Clothing Review Sentiment Intelligence App</h1><p>Explore overall sentiment, six aspect-level opinions, metadata-driven risks, and updated 10W experiment reports in a compact customer decision-support prototype.</p><div class="hero-pills"><span>Consumer decision support</span><span>Merchant diagnostics</span><span>Research dashboard</span></div></div>')
 
1158
  gr.HTML('<div class="module-head"><h3>Product Score Monitor</h3><span class="info-tip">i<span class="tip-content">' + model_info_html() + '</span></span></div>')
1159
  gr.HTML('<div class="small-label">Most negative products across the six aspects</div>')
1160
  negative_spotlight = gr.HTML('<div class="note-card">Loading most negative products...</div>')
 
1161
  with gr.Accordion("Score filters and export", open=True):
1162
  with gr.Row():
1163
  merchant_metric = gr.Dropdown(["Overall"] + ASPECTS, value="Overall", label="Aspect")
 
1174
  with gr.Column(scale=1):
1175
  with gr.Group(visible=True) as risk_single_group:
1176
  new_features = gr.Textbox("Cotton Polyester Blend, Slim Fit, Graphic Print, Machine Wash", label="New product features", lines=4)
1177
+ new_categories = gr.Dropdown(merchant_categories, value=default_merchant_category, label="New product category")
1178
  with gr.Row():
1179
  new_price = gr.Number(29.99, label="Price")
1180
  new_rating = gr.Number(4.1, label="Expected or early average rating")
 
1201
  external_review = gr.Textbox("The fabric is soft and the color looks good, but it runs small and the zipper feels weak.", label="External customer review", lines=5)
1202
  with gr.Row():
1203
  ext_features = gr.Textbox("Cotton Blend, Slim Fit, Zipper Closure", label="Product features", lines=3)
1204
+ ext_categories = gr.Dropdown(merchant_categories, value=default_merchant_category, label="Product category")
1205
  with gr.Row():
1206
  ext_price = gr.Number(39.99, label="Price")
1207
  ext_rating = gr.Number(4.2, label="Average rating")