# 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"""
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()