hodfa840 commited on
Commit
16b9e90
Β·
1 Parent(s): f69e608

feat: Add health/beauty products and refine retriever logic

Browse files
app.py CHANGED
@@ -70,9 +70,13 @@ IMAGE_MAP = {
70
  "Overcoat": "https://images.unsplash.com/photo-1544923246-77307dd270b5?w=400&h=300&fit=crop",
71
  "Wallet": "https://images.unsplash.com/photo-1627123424574-724758594e93?w=400&h=300&fit=crop",
72
  "Belt": "https://images.unsplash.com/photo-1553062407-98eeb64c6a62?w=400&h=300&fit=crop",
73
- "Candle": "https://images.unsplash.com/photo-1602607616777-b8fb tried?w=400&h=300&fit=crop",
74
  "Blanket": "https://images.unsplash.com/photo-1555041469-a586c61ea9bc?w=400&h=300&fit=crop",
75
  "Clock": "https://images.unsplash.com/photo-1563861826100-9cb868fdbe1c?w=400&h=300&fit=crop",
 
 
 
 
76
  "Towel": "https://images.unsplash.com/photo-1583845112203-29329902332e?w=400&h=300&fit=crop",
77
  "Hoodie": "https://images.unsplash.com/photo-1556821840-3a63f95609a7?w=400&h=300&fit=crop",
78
  "Chino": "https://images.unsplash.com/photo-1473966968600-fa801b869a1a?w=400&h=300&fit=crop",
@@ -361,7 +365,7 @@ body, .gradio-container {
361
  footer { display: none !important; }
362
  """
363
 
364
- with gr.Blocks(css=css, theme=gr.themes.Base(), title="RetailMind β€” Self-Healing AI") as app:
365
 
366
  # ── Header ────────────────────────────────────────────────────
367
  gr.HTML("""
@@ -381,10 +385,14 @@ with gr.Blocks(css=css, theme=gr.themes.Base(), title="RetailMind β€” Self-Heali
381
  # ── LEFT: Chat Panel ─────────────────────────────────────
382
  with gr.Column(scale=4, elem_classes=["glass-panel"]):
383
  gr.HTML("<div class='panel-header'>πŸ’¬ AI Shopping Assistant</div>")
 
 
 
 
 
384
  chatbot = gr.Chatbot(
385
  height=420,
386
  container=False,
387
- show_copy_button=True,
388
  placeholder="Ask me about products, deals, or seasonal picks…",
389
  )
390
  with gr.Row():
@@ -465,4 +473,4 @@ with gr.Blocks(css=css, theme=gr.themes.Base(), title="RetailMind β€” Self-Heali
465
 
466
 
467
  if __name__ == "__main__":
468
- app.launch(server_name="0.0.0.0", share=True)
 
70
  "Overcoat": "https://images.unsplash.com/photo-1544923246-77307dd270b5?w=400&h=300&fit=crop",
71
  "Wallet": "https://images.unsplash.com/photo-1627123424574-724758594e93?w=400&h=300&fit=crop",
72
  "Belt": "https://images.unsplash.com/photo-1553062407-98eeb64c6a62?w=400&h=300&fit=crop",
73
+ "Candle": "https://images.unsplash.com/photo-1602607616777-b8fbdc2cd8a9?w=400&h=300&fit=crop",
74
  "Blanket": "https://images.unsplash.com/photo-1555041469-a586c61ea9bc?w=400&h=300&fit=crop",
75
  "Clock": "https://images.unsplash.com/photo-1563861826100-9cb868fdbe1c?w=400&h=300&fit=crop",
76
+ "Sunscreen": "https://images.unsplash.com/photo-1556228578-83b6329731eb?w=400&h=300&fit=crop",
77
+ "Lipstick": "https://images.unsplash.com/photo-1586495777744-4413f21062fa?w=400&h=300&fit=crop",
78
+ "Serum": "https://images.unsplash.com/photo-1620916566398-39f1143ab7be?w=400&h=300&fit=crop",
79
+ "Lip Balm": "https://images.unsplash.com/photo-1629813359670-357ff8ca8e21?w=400&h=300&fit=crop",
80
  "Towel": "https://images.unsplash.com/photo-1583845112203-29329902332e?w=400&h=300&fit=crop",
81
  "Hoodie": "https://images.unsplash.com/photo-1556821840-3a63f95609a7?w=400&h=300&fit=crop",
82
  "Chino": "https://images.unsplash.com/photo-1473966968600-fa801b869a1a?w=400&h=300&fit=crop",
 
365
  footer { display: none !important; }
366
  """
367
 
368
+ with gr.Blocks(title="RetailMind β€” Self-Healing AI") as app:
369
 
370
  # ── Header ────────────────────────────────────────────────────
371
  gr.HTML("""
 
385
  # ── LEFT: Chat Panel ─────────────────────────────────────
386
  with gr.Column(scale=4, elem_classes=["glass-panel"]):
387
  gr.HTML("<div class='panel-header'>πŸ’¬ AI Shopping Assistant</div>")
388
+ gr.HTML("""
389
+ <div style="padding: 10px 14px; margin-bottom: 12px; background: rgba(59, 130, 246, 0.1); border: 1px solid rgba(59, 130, 246, 0.2); border-radius: 8px; font-size: 0.85em; color: #93c5fd; line-height: 1.4;">
390
+ <b>🏷️ In Stock:</b> Outerwear & Apparel <span>·</span> Footwear <span>·</span> Tech Accessories <span>·</span> Home & Lifestyle <span>·</span> Health & Beauty
391
+ </div>
392
+ """)
393
  chatbot = gr.Chatbot(
394
  height=420,
395
  container=False,
 
396
  placeholder="Ask me about products, deals, or seasonal picks…",
397
  )
398
  with gr.Row():
 
473
 
474
 
475
  if __name__ == "__main__":
476
+ app.launch(server_name="0.0.0.0", share=True, css=css, theme=gr.themes.Base())
modules/data_simulation.py CHANGED
@@ -236,6 +236,20 @@ _TEMPLATES: list[dict] = [
236
  {"title": "Retro Aviator Sunglasses", "category": "casual", "price": 29.99,
237
  "desc": "Classic aviator frames in brushed gold metal with gradient smoke lenses. UV400 protection, adjustable nose pads, and spring-loaded temples for a comfortable fit.",
238
  "tags": ["aviator", "UV400", "retro", "metal-frame"], "materials": "Brushed metal alloy, gradient polycarbonate lenses"},
 
 
 
 
 
 
 
 
 
 
 
 
 
 
239
  ]
240
 
241
 
@@ -282,7 +296,7 @@ def _expand_catalog(templates: list[dict], target_count: int = 200) -> list[Prod
282
 
283
  def generate_catalog() -> list[Product]:
284
  """Generate the full product catalog."""
285
- return _expand_catalog(_TEMPLATES, target_count=200)
286
 
287
 
288
  def get_scenarios() -> dict[str, list[str]]:
 
236
  {"title": "Retro Aviator Sunglasses", "category": "casual", "price": 29.99,
237
  "desc": "Classic aviator frames in brushed gold metal with gradient smoke lenses. UV400 protection, adjustable nose pads, and spring-loaded temples for a comfortable fit.",
238
  "tags": ["aviator", "UV400", "retro", "metal-frame"], "materials": "Brushed metal alloy, gradient polycarbonate lenses"},
239
+
240
+ # ── Health & Beauty ─────────────────────────────────────────────────────
241
+ {"title": "SPF 50 Mineral Sunscreen", "category": "health", "price": 24.99,
242
+ "desc": "Broad-spectrum mineral sunscreen that protects against UV rays without harmful chemicals. Reef-safe, non-greasy formula that absorbs quickly. Leaves no white cast.",
243
+ "tags": ["sunscreen", "skincare", "SPF", "reef-safe"], "materials": "Zinc oxide, aloe vera"},
244
+ {"title": "Hydrating Matte Lipstick", "category": "health", "price": 18.99,
245
+ "desc": "Long-lasting matte lipstick infused with hyaluronic acid and shea butter for all-day comfort. Highly pigmented shade that doesn't feather or dry out your lips.",
246
+ "tags": ["lipstick", "makeup", "beauty", "hydrating"], "materials": "Hyaluronic acid, shea butter, vegan pigments"},
247
+ {"title": "Vitamin C Glow Serum", "category": "health", "price": 34.99,
248
+ "desc": "Brightening facial serum with 15% Vitamin C and hyaluronic acid. Reduces dark spots, evens skin tone, and boosts collagen production. Safe for sensitive skin.",
249
+ "tags": ["serum", "skincare", "vitamin-c", "anti-aging"], "materials": "Ascorbic acid, hyaluronic acid, vitamin E"},
250
+ {"title": "Organic Lip Balm Trio", "category": "health", "price": 12.99,
251
+ "desc": "Sustainably sourced beeswax lip balm set. Includes peppermint, vanilla, and unscented. Deeply moisturizes chapped lips during harsh weather.",
252
+ "tags": ["lip-balm", "skincare", "organic", "moisturizing"], "materials": "Organic beeswax, coconut oil, essential oils"},
253
  ]
254
 
255
 
 
296
 
297
  def generate_catalog() -> list[Product]:
298
  """Generate the full product catalog."""
299
+ return _expand_catalog(_TEMPLATES, target_count=len(_TEMPLATES))
300
 
301
 
302
  def get_scenarios() -> dict[str, list[str]]:
modules/drift.py CHANGED
@@ -83,6 +83,16 @@ class DriftDetector:
83
 
84
  logger.info("DriftDetector initialized with %d concept anchors.", len(concept_phrases))
85
 
 
 
 
 
 
 
 
 
 
 
86
  # ── Public API ──────────────────────────────────────────────────────────
87
 
88
  def analyze_drift(self, query: str) -> tuple[str, dict[str, float]]:
 
83
 
84
  logger.info("DriftDetector initialized with %d concept anchors.", len(concept_phrases))
85
 
86
+ # Prepopulate history so chart renders correctly on first load
87
+ for _ in range(5):
88
+ self.history.append(DriftEvent(
89
+ timestamp=time.time(),
90
+ query="[system initialization]",
91
+ scores={c: 0.15 for c in self._concept_embs},
92
+ dominant="normal"
93
+ ))
94
+ for c in self._concept_embs:
95
+ self._ewma[c] = 0.15
96
  # ── Public API ──────────────────────────────────────────────────────────
97
 
98
  def analyze_drift(self, query: str) -> tuple[str, dict[str, float]]:
modules/retrieval.py CHANGED
@@ -79,6 +79,8 @@ class HybridRetriever:
79
 
80
  results = []
81
  for li in top_local:
 
 
82
  global_idx = candidate_indices[li]
83
  results.append({
84
  "product": self.catalog[global_idx],
@@ -128,11 +130,11 @@ class HybridRetriever:
128
  "electronics": ["tech", "electronic", "gadget", "headphone", "speaker", "charger", "smart"],
129
  "premium": ["luxury", "premium", "high-end", "designer", "artisan"],
130
  "home": ["home", "kitchen", "desk", "candle", "bath", "decor"],
131
- "casual": ["casual", "streetwear", "everyday", "hoodie", "sneaker", "jeans"],
132
  }
133
- q_lower = query.lower()
134
  for cat, keywords in category_keywords.items():
135
- if any(kw in q_lower for kw in keywords):
 
136
  return cat
137
  return None
138
 
 
79
 
80
  results = []
81
  for li in top_local:
82
+ if float(scores[li]) < 0.20:
83
+ continue
84
  global_idx = candidate_indices[li]
85
  results.append({
86
  "product": self.catalog[global_idx],
 
130
  "electronics": ["tech", "electronic", "gadget", "headphone", "speaker", "charger", "smart"],
131
  "premium": ["luxury", "premium", "high-end", "designer", "artisan"],
132
  "home": ["home", "kitchen", "desk", "candle", "bath", "decor"],
133
+ "health": ["health", "beauty", "sunscreen", "lipstick", "serum", "balm", "skincare", "makeup"],
134
  }
 
135
  for cat, keywords in category_keywords.items():
136
+ pattern = r'\b(?:' + '|'.join(keywords) + r')\b'
137
+ if re.search(pattern, query, re.IGNORECASE):
138
  return cat
139
  return None
140
 
tests/test_catalog.py CHANGED
@@ -13,7 +13,7 @@ class TestCatalog:
13
 
14
  def test_catalog_size(self):
15
  catalog = generate_catalog()
16
- assert len(catalog) == 200, f"Expected 200 products, got {len(catalog)}"
17
 
18
  def test_product_has_required_fields(self):
19
  catalog = generate_catalog()
@@ -33,7 +33,7 @@ class TestCatalog:
33
  assert 1.0 <= p["rating"] <= 5.0, f"Product {p['id']} has invalid rating: {p['rating']}"
34
 
35
  def test_categories_are_valid(self):
36
- valid = {"winter", "summer", "eco-friendly", "sports", "electronics", "premium", "home", "casual"}
37
  catalog = generate_catalog()
38
  for p in catalog:
39
  assert p["category"] in valid, f"Invalid category: {p['category']}"
 
13
 
14
  def test_catalog_size(self):
15
  catalog = generate_catalog()
16
+ assert len(catalog) >= 50, f"Expected at least 50 products, got {len(catalog)}"
17
 
18
  def test_product_has_required_fields(self):
19
  catalog = generate_catalog()
 
33
  assert 1.0 <= p["rating"] <= 5.0, f"Product {p['id']} has invalid rating: {p['rating']}"
34
 
35
  def test_categories_are_valid(self):
36
+ valid = {"winter", "summer", "eco-friendly", "sports", "electronics", "premium", "home", "casual", "health"}
37
  catalog = generate_catalog()
38
  for p in catalog:
39
  assert p["category"] in valid, f"Invalid category: {p['category']}"