# app.py import pandas as pd import gradio as gr from html import escape as esc import re, json # ---------- Load & normalize ---------- DF = pd.read_csv("offers_with_prices.csv") DF.columns = [c.strip().lower() for c in DF.columns] # Ensure required columns exist for col in [ "country","dealer","brand","model","trim_or_variant", "starting_price","heading","offer_description", "vehicle_offer_url","vehcileoffer_url","images" ]: if col not in DF.columns: DF[col] = "" def _first_image(val) -> str: if not val: return "" s = str(val).strip().replace("&", "&") try: if s[:1] in "[{" or (s[:1] == '"' and s[-1:] == '"'): j = json.loads(s) if isinstance(j, list): for item in j: m = re.search(r'https?://[^\s<>()\'"]+', str(item)) if m: return m.group(0) elif isinstance(j, dict): for k in ("image","image_url","url","src"): if k in j: m = re.search(r'https?://[^\s<>()\'"]+', str(j[k])) if m: return m.group(0) except Exception: pass m = re.search(r'https?://[^\s<>()\'"]+', s) if m: return m.group(0) for sep in ("|","\n"," "): if sep in s: for part in s.split(sep): m = re.search(r'https?://[^\s<>()\'"]+', part) if m: return m.group(0) return "" def _card_html(row: pd.Series) -> str: img = _first_image(row.get("images","")) heading = esc(str(row.get("heading","")) or f"{row.get('brand','')} {row.get('model','')}") offer = esc(str(row.get("offer_description",""))) price = esc(str(row.get("starting_price",""))) url = str(row.get("vehicle_offer_url","") or row.get("vehcileoffer_url","") or "#") country = esc(str(row.get("country",""))) brand = esc(str(row.get("brand",""))) dealer = esc(str(row.get("dealer",""))) model = esc(str(row.get("model",""))) trim = esc(str(row.get("trim_or_variant",""))) chips = " ".join( f"{v}" for v in [country, brand, dealer, model, trim] if v ) return f"""
{heading}
Starting Price{price or "—"}
{heading}
{offer}
{chips}
""" def _grid_html(df: pd.DataFrame) -> str: cards = "\n".join(_card_html(r) for _, r in df.iterrows()) return f"""
{cards}
""" # ---------- Options helpers ---------- def _opts_with_all(series: pd.Series): vals = [x for x in series.dropna().astype(str).unique() if str(x).strip()] return ["All"] + sorted(vals) def _brand_and_dealer_options_for_country(country: str): df_c = DF if country == "All" else DF[DF["country"].astype(str) == str(country)] return _opts_with_all(df_c["brand"]), _opts_with_all(df_c["dealer"]) def _dealer_options_for_country_brand(country: str, brand: str): df_c = DF if country == "All" else DF[DF["country"].astype(str) == str(country)] df_cb = df_c if brand == "All" else df_c[df_c["brand"].astype(str) == str(brand)] return _opts_with_all(df_cb["dealer"]) def _brand_options_for_country_dealer(country: str, dealer: str): df_c = DF if country == "All" else DF[DF["country"].astype(str) == str(country)] df_cd = df_c if dealer == "All" else df_c[df_c["dealer"].astype(str) == str(dealer)] return _opts_with_all(df_cd["brand"]) # ---------- Filtering ---------- def filter_offers(country: str, brand: str, dealer: str, limit: int): df_f = DF.copy() if country and country != "All": df_f = df_f[df_f["country"].astype(str) == str(country)] if brand and brand != "All": df_f = df_f[df_f["brand"].astype(str) == str(brand)] if dealer and dealer != "All": df_f = df_f[df_f["dealer"].astype(str) == str(dealer)] df_show = df_f.head(int(limit)) return _grid_html(df_show) # ---------- Gradio event handlers ---------- def init_on_load(country, brand, dealer, limit): brand_opts, dealer_opts = _brand_and_dealer_options_for_country(country) brand_val = brand if brand in brand_opts else "All" dealer_val = dealer if dealer in dealer_opts else "All" html = filter_offers(country, brand_val, dealer_val, limit) return ( gr.update(choices=brand_opts, value=brand_val), gr.update(choices=dealer_opts, value=dealer_val), html ) def on_country_change(country, brand, dealer, limit): brand_opts, dealer_opts = _brand_and_dealer_options_for_country(country) brand_val = brand if brand in brand_opts else "All" dealer_val = dealer if dealer in dealer_opts else "All" html = filter_offers(country, brand_val, dealer_val, limit) return ( gr.update(choices=brand_opts, value=brand_val), gr.update(choices=dealer_opts, value=dealer_val), html ) def on_brand_change(country, brand, dealer, limit): # Country + Brand -> restrict Dealer dealer_opts = _dealer_options_for_country_brand(country, brand) dealer_val = dealer if dealer in dealer_opts else "All" html = filter_offers(country, brand, dealer_val, limit) return ( gr.update(choices=dealer_opts, value=dealer_val), html ) def on_dealer_change(country, brand, dealer, limit): # Country + Dealer -> restrict Brand (fix for your Saudi/Altawkilat => only GMC) brand_opts = _brand_options_for_country_dealer(country, dealer) brand_val = brand if brand in brand_opts else "All" html = filter_offers(country, brand_val, dealer, limit) return ( gr.update(choices=brand_opts, value=brand_val), html ) def on_limit_change(country, brand, dealer, limit): return filter_offers(country, brand, dealer, limit) # ---------- Build Gradio UI ---------- def app(): countries = _opts_with_all(DF["country"]) brands, dealers = _brand_and_dealer_options_for_country("All") custom_css = """ body, .gradio-container, .gr-block, .gr-button, .gr-input, .gr-dropdown, .gradio-container * { font-family: Inter, Segoe UI, Roboto, Helvetica, Arial, sans-serif !important; } #cards-pane { height: 820px; overflow: auto; border-radius: 10px; } """ with gr.Blocks(title="Vehicle Offers", css=custom_css) as demo: gr.Markdown("## 🚗 Vehicle Offers") with gr.Row(): dd_country = gr.Dropdown(choices=countries, label="Country", value="All") dd_brand = gr.Dropdown(choices=brands, label="Brand", value="All") dd_dealer = gr.Dropdown(choices=dealers, label="Dealer", value="All") s_limit = gr.Slider(6, 200, value=50, step=6, label="Max Offers") out_html = gr.HTML(elem_id="cards-pane") # Initialize on load demo.load( init_on_load, [dd_country, dd_brand, dd_dealer, s_limit], [dd_brand, dd_dealer, out_html] ) # Country -> update Brand + Dealer options + grid dd_country.change( on_country_change, [dd_country, dd_brand, dd_dealer, s_limit], [dd_brand, dd_dealer, out_html] ) # Brand -> update Dealer options + grid dd_brand.change( on_brand_change, [dd_country, dd_brand, dd_dealer, s_limit], [dd_dealer, out_html] ) # Dealer -> update Brand options + grid (new) dd_dealer.change( on_dealer_change, [dd_country, dd_brand, dd_dealer, s_limit], [dd_brand, out_html] ) # Limit -> update grid s_limit.change( on_limit_change, [dd_country, dd_brand, dd_dealer, s_limit], out_html ) return demo if __name__ == "__main__": app().launch()