VehiclesOffers / app.py
ravim254's picture
Upload app.py
7987377 verified
# 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"<span class='chip'>{v}</span>"
for v in [country, brand, dealer, model, trim] if v
)
return f"""
<div class="card" onclick="window.open('{url}','_blank')">
<div class="thumb"><img src="{img}" alt="{heading}" onerror="this.style.opacity=0.2"></div>
<div class="body">
<div class="sp">Starting Price<strong>{price or "—"}</strong></div>
<div class="heading">{heading}</div>
<div class="offer">{offer}</div>
</div>
<div class="meta">{chips}</div>
</div>
"""
def _grid_html(df: pd.DataFrame) -> str:
cards = "\n".join(_card_html(r) for _, r in df.iterrows())
return f"""
<html>
<head>
<meta charset="utf-8" />
<style>
:root{{ --bg:#f6f7fb; --card:#ffffff; --ink:#111827; --muted:#6b7280; --line:#e5e7eb; --chip:#eef2f7; }}
body{{background:var(--bg);margin:0;font-family:Inter, Segoe UI, Roboto, Helvetica, Arial, sans-serif;}}
.cards{{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:18px;padding:6px;}}
.card{{background:var(--card);border:1px solid var(--line);border-radius:16px;overflow:hidden;
box-shadow:0 4px 12px rgba(0,0,0,.06);cursor:pointer;
transition:transform .08s ease,box-shadow .2s ease;}}
.card:hover{{transform:translateY(-2px);box-shadow:0 8px 20px rgba(0,0,0,.12);}}
.thumb{{height:160px;overflow:hidden;background:#000;}}
.thumb img{{width:100%;height:100%;object-fit:cover;}}
.body{{padding:12px;display:flex;flex-direction:column;gap:6px;}}
.sp{{font-size:13px;color:var(--muted);}}
.sp strong{{display:block;font-size:20px;color:var(--ink);margin-top:2px;}}
.heading{{font-weight:700;color:var(--ink);font-size:15px;line-height:1.25;}}
.offer{{color:var(--ink);font-size:13px;line-height:1.45;opacity:.9;
display:-webkit-box;-webkit-line-clamp:4;-webkit-box-orient:vertical;overflow:hidden;}}
.meta{{display:flex;gap:6px;flex-wrap:wrap;padding:0 12px 12px;}}
.chip{{font-size:11px;color:#374151;background:var(--chip);border-radius:999px;padding:4px 8px;}}
</style>
</head>
<body>
<div class="cards">{cards}</div>
</body>
</html>
"""
# ---------- 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()