""" RetailMind — Self-Healing LLM for Store Intelligence Gradio application showcasing real-time semantic drift detection, autonomous prompt adaptation, and hybrid RAG retrieval. """ import logging import sys import gradio as gr import plotly.graph_objects as go from modules.data_simulation import generate_catalog, get_scenarios from modules.shared import get_embedding_model from modules.retrieval import HybridRetriever from modules.drift import DriftDetector from modules.adaptation import Adapter from modules.llm import generate_response # ── Logging ──────────────────────────────────────────────────────────────── logging.basicConfig( level=logging.INFO, format="%(asctime)s │ %(name)-24s │ %(levelname)-5s │ %(message)s", datefmt="%H:%M:%S", ) logger = logging.getLogger("retailmind") # ── Initialize components ───────────────────────────────────────────────── logger.info("Bootstrapping RetailMind…") catalog = generate_catalog() retriever = HybridRetriever(catalog) detector = DriftDetector() adapter = Adapter() scenarios = get_scenarios() logger.info("Ready — %d products indexed.", len(catalog)) # ── Helper: Image mapping ───────────────────────────────────────────────── IMAGE_MAP = { "Parka": "https://images.unsplash.com/photo-1544923246-77307dd270b5?w=400&h=300&fit=crop", "Sweater": "https://images.unsplash.com/photo-1610652492500-dea0624af6ee?w=400&h=300&fit=crop", "Gloves": "https://images.unsplash.com/photo-1551538827-9c037cb4f32a?w=400&h=300&fit=crop", "Boots": "https://images.unsplash.com/photo-1608256246200-53e635b5b65f?w=400&h=300&fit=crop", "Beanie": "https://images.unsplash.com/photo-1576871337622-98d48d1cf531?w=400&h=300&fit=crop", "Fleece": "https://images.unsplash.com/photo-1591047139829-d91aecb6caea?w=400&h=300&fit=crop", "Base Layer": "https://images.unsplash.com/photo-1489987707025-afc232f7ea0f?w=400&h=300&fit=crop", "Vest": "https://images.unsplash.com/photo-1591047139829-d91aecb6caea?w=400&h=300&fit=crop", "Sneakers": "https://images.unsplash.com/photo-1542291026-7eec264c27ff?w=400&h=300&fit=crop", "Shorts": "https://images.unsplash.com/photo-1591195853828-11db59a44f6b?w=400&h=300&fit=crop", "Sunglasses": "https://images.unsplash.com/photo-1511499767150-a48a237f0083?w=400&h=300&fit=crop", "Linen": "https://images.unsplash.com/photo-1596755094514-f87e34085b2c?w=400&h=300&fit=crop", "Sandals": "https://images.unsplash.com/photo-1603487742131-4160ec999306?w=400&h=300&fit=crop", "Tank": "https://images.unsplash.com/photo-1521572163474-6864f9cf17ab?w=400&h=300&fit=crop", "Hat": "https://images.unsplash.com/photo-1521369909029-2afed882baee?w=400&h=300&fit=crop", "Water Shoes": "https://images.unsplash.com/photo-1542291026-7eec264c27ff?w=400&h=300&fit=crop", "Backpack": "https://images.unsplash.com/photo-1553062407-98eeb64c6a62?w=400&h=300&fit=crop", "Bottle": "https://images.unsplash.com/photo-1602143407151-7111542de6e8?w=400&h=300&fit=crop", "Tee": "https://images.unsplash.com/photo-1521572163474-6864f9cf17ab?w=400&h=300&fit=crop", "Tote": "https://images.unsplash.com/photo-1622560480605-d83c853bc5c3?w=400&h=300&fit=crop", "Shoes": "https://images.unsplash.com/photo-1542291026-7eec264c27ff?w=400&h=300&fit=crop", "Jacket": "https://images.unsplash.com/photo-1551028719-00167b16eac5?w=400&h=300&fit=crop", "Watch": "https://images.unsplash.com/photo-1523275335684-37898b6baf30?w=400&h=300&fit=crop", "Mat": "https://images.unsplash.com/photo-1553062407-98eeb64c6a62?w=400&h=300&fit=crop", # reusing backpack as mat placeholder "Tights": "https://images.unsplash.com/photo-1556821840-3a63f95609a7?w=400&h=300&fit=crop", # reusing hoodie as apparel placeholder "Pack": "https://images.unsplash.com/photo-1553062407-98eeb64c6a62?w=400&h=300&fit=crop", "Headphones": "https://images.unsplash.com/photo-1505740420928-5e560c06d30e?w=400&h=300&fit=crop", "Tracker": "https://images.unsplash.com/photo-1557438159-51eec7a6c9e8?w=400&h=300&fit=crop", "Earbuds": "https://images.unsplash.com/photo-1590658268037-6bf12f032f55?w=400&h=300&fit=crop", "Charger": "https://images.unsplash.com/photo-1609091839311-d5365f9ff1c5?w=400&h=300&fit=crop", "Speaker": "https://images.unsplash.com/photo-1608043152269-423dbba4e7e1?w=400&h=300&fit=crop", "Lamp": "https://images.unsplash.com/photo-1507473885765-e6ed057ab6fe?w=400&h=300&fit=crop", "Power Bank": "https://images.unsplash.com/photo-1609091839311-d5365f9ff1c5?w=400&h=300&fit=crop", "Mug": "https://images.unsplash.com/photo-1514228742587-6b1558fcca3d?w=400&h=300&fit=crop", "Weekender": "https://images.unsplash.com/photo-1590874103328-eac38a683ce7?w=400&h=300&fit=crop", "Overcoat": "https://images.unsplash.com/photo-1544923246-77307dd270b5?w=400&h=300&fit=crop", "Wallet": "https://images.unsplash.com/photo-1627123424574-724758594e93?w=400&h=300&fit=crop", "Belt": "https://images.unsplash.com/photo-1553062407-98eeb64c6a62?w=400&h=300&fit=crop", "Candle": "https://images.unsplash.com/photo-1602607616777-b8fbdc2cd8a9?w=400&h=300&fit=crop", "Blanket": "https://images.unsplash.com/photo-1555041469-a586c61ea9bc?w=400&h=300&fit=crop", "Clock": "https://images.unsplash.com/photo-1563861826100-9cb868fdbe1c?w=400&h=300&fit=crop", "Sunscreen": "https://images.unsplash.com/photo-1556228578-83b6329731eb?w=400&h=300&fit=crop", "Lipstick": "https://images.unsplash.com/photo-1586495777744-4413f21062fa?w=400&h=300&fit=crop", "Serum": "https://images.unsplash.com/photo-1620916566398-39f1143ab7be?w=400&h=300&fit=crop", "Lip Balm": "https://images.unsplash.com/photo-1629813359670-357ff8ca8e21?w=400&h=300&fit=crop", "Towel": "https://images.unsplash.com/photo-1583845112203-29329902332e?w=400&h=300&fit=crop", "Hoodie": "https://images.unsplash.com/photo-1556821840-3a63f95609a7?w=400&h=300&fit=crop", "Chino": "https://images.unsplash.com/photo-1473966968600-fa801b869a1a?w=400&h=300&fit=crop", "Crossbody": "https://images.unsplash.com/photo-1590874103328-eac38a683ce7?w=400&h=300&fit=crop", "Socks": "https://images.unsplash.com/photo-1586350977771-b3b0abd50c82?w=400&h=300&fit=crop", "Basketball": "https://images.unsplash.com/photo-1546519638-68e109498ffc?w=400&h=300&fit=crop", "Jersey": "https://images.unsplash.com/photo-1565299624946-b28f40a0ae38?w=400&h=300&fit=crop", "Cushion": "https://images.unsplash.com/photo-1555041469-a586c61ea9bc?w=400&h=300&fit=crop", "Planter": "https://images.unsplash.com/photo-1459411552884-841db9b3cc2a?w=400&h=300&fit=crop", "Organizer": "https://images.unsplash.com/photo-1507473885765-e6ed057ab6fe?w=400&h=300&fit=crop", "Pour-Over": "https://images.unsplash.com/photo-1495474472287-4d71bcdd2085?w=400&h=300&fit=crop", } DEFAULT_IMG = "https://images.unsplash.com/photo-1542291026-7eec264c27ff?w=400&h=300&fit=crop" # use shoes as fallback so it never 404s def _get_product_image(title: str) -> str: """Map product title → curated Unsplash photo.""" for key, url in IMAGE_MAP.items(): if key.lower() in title.lower(): return url return DEFAULT_IMG # ── Plotly drift chart ──────────────────────────────────────────────────── def _plot_drift() -> go.Figure: series = detector.get_history_series() ewma = detector.get_ewma_scores() fig = go.Figure() colors = {"price_sensitive": "#f59e0b", "summer_shift": "#06b6d4", "eco_trend": "#10b981"} labels = {"price_sensitive": "Price Sensitivity", "summer_shift": "Summer Shift", "eco_trend": "Eco Trend"} for concept in series: data = series[concept][-30:] # last 30 data points fig.add_trace(go.Scatter( y=data, mode="lines", name=labels.get(concept, concept), line=dict(color=colors.get(concept, "#fff"), width=2.5, shape="spline"), fill="tozeroy", fillcolor=colors.get(concept, "#fff").replace(")", ", 0.08)").replace("rgb", "rgba") if "rgb" in colors.get(concept, "") else f"rgba(255,255,255,0.05)", )) # Threshold line fig.add_hline(y=0.38, line_dash="dot", line_color="rgba(255,255,255,0.3)", annotation_text="Threshold", annotation_font_color="rgba(255,255,255,0.4)") fig.update_layout( height=240, margin=dict(l=0, r=0, t=10, b=0), plot_bgcolor="rgba(0,0,0,0)", paper_bgcolor="rgba(0,0,0,0)", font=dict(color="#94a3b8", size=11), legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="center", x=0.5, font=dict(size=10)), xaxis=dict(showgrid=False, showticklabels=False), yaxis=dict(showgrid=True, gridwidth=1, gridcolor="rgba(255,255,255,0.06)", range=[0, 0.8]), ) return fig # ── Product cards HTML ──────────────────────────────────────────────────── def _build_product_html(retrieved: list[dict]) -> str: if not retrieved: return _empty_catalog_html() cards = [] for r in retrieved: p = r["product"] score = r["score"] img = _get_product_image(p["title"]) stars_full = int(p.get("rating", 4)) stars_html = "★" * stars_full + "☆" * (5 - stars_full) reviews = p.get("reviews", 0) score_pct = int(score * 100) tags_html = "".join( f"{t}" for t in p.get("tags", [])[:3] ) cards.append(f"""
""") return f"""Self-Healing LLM · Store Intelligence