Levimichael4 commited on
Commit
8f44af9
·
verified ·
1 Parent(s): 669b6ed

Delete app_new.py

Browse files
Files changed (1) hide show
  1. app_new.py +0 -286
app_new.py DELETED
@@ -1,286 +0,0 @@
1
-
2
- # app_new.py — RideSearch (brand-correct trims, cross-brand, smart fallbacks, optional photos)
3
- # Drop into your Hugging Face Space and set as the app file.
4
-
5
- import os, glob, urllib.parse
6
- import numpy as np
7
- import pandas as pd
8
- from sklearn.metrics.pairwise import cosine_similarity
9
- from sklearn.preprocessing import StandardScaler
10
- import gradio as gr
11
-
12
- def load_df():
13
- if os.path.exists('RideSearch_dataset.csv'):
14
- return pd.read_csv('RideSearch_dataset.csv')
15
- parts = sorted(glob.glob('RideSearch_part*_small.csv'))
16
- if not parts:
17
- raise FileNotFoundError("Upload RideSearch_dataset.csv OR the 10 parts RideSearch_part*_small.csv.")
18
- df = pd.concat([pd.read_csv(p) for p in parts], ignore_index=True)
19
- df.to_csv('RideSearch_dataset.csv', index=False)
20
- return df
21
-
22
- DF = load_df()
23
-
24
- NUM_COLS = [
25
- 'horsepower','zero_to_100_kmh_s','seats','cargo_liters','price_usd',
26
- 'popularity_score','comfort_score','reliability_score','tech_score',
27
- 'ownership_cost_score','safety_rating'
28
- ]
29
-
30
- def ensure_emb():
31
- txt_ok = os.path.exists('emb_text.npy')
32
- num_ok = os.path.exists('emb_num.npy')
33
- if txt_ok and num_ok:
34
- return np.load('emb_text.npy'), np.load('emb_num.npy')
35
- from sentence_transformers import SentenceTransformer
36
- m = SentenceTransformer('sentence-transformers/all-MiniLM-L6-v2')
37
- texts = DF['text_record'].astype(str).tolist()
38
- Etext = m.encode(texts, batch_size=256, show_progress_bar=True, normalize_embeddings=True)
39
- Etext = np.asarray(Etext, dtype='float32'); np.save('emb_text.npy', Etext)
40
- X = DF[NUM_COLS].copy()
41
- if 'zero_to_100_kmh_s' in X.columns:
42
- # lower time is better -> invert so higher is better
43
- X['zero_to_100_kmh_s'] = -X['zero_to_100_kmh_s'].astype('float32')
44
- Xs = StandardScaler().fit_transform(X.values.astype('float32'))
45
- Enum = Xs.astype('float32'); np.save('emb_num.npy', Enum)
46
- return Etext, Enum
47
-
48
- # ---- trims mapping (loaded from trims_map.json if present) ----
49
- import json
50
- TRIM_CHOICES = {}
51
- TRIM_ALIAS_TO_GENERIC = {}
52
- if os.path.exists('trims_map.json'):
53
- with open('trims_map.json','r',encoding='utf-8') as f:
54
- data = json.load(f)
55
- TRIM_CHOICES = {tuple(k.split('||')): v['display'] for k, v in data.items()}
56
- TRIM_ALIAS_TO_GENERIC = {}
57
- for k, v in data.items():
58
- for alias, generic in v['alias_to_generic'].items():
59
- TRIM_ALIAS_TO_GENERIC[alias] = generic
60
- else:
61
- # minimal fallback so the app still runs
62
- TRIM_CHOICES = {("BMW","3 Series"): ["320i","330i","330e","340i","M3"]}
63
- TRIM_ALIAS_TO_GENERIC = {"320i":"Base","330i":"Sport","330e":"Sport","340i":"Premium","M3":"Performance"}
64
-
65
- def generic_to_display(make, model, generic_trim):
66
- if not generic_trim:
67
- return ""
68
- if (make, model) not in TRIM_CHOICES:
69
- return str(generic_trim)
70
- for alias in TRIM_CHOICES[(make, model)]:
71
- if TRIM_ALIAS_TO_GENERIC.get(alias) == generic_trim:
72
- return alias
73
- return str(generic_trim)
74
-
75
- def models_for(make):
76
- if not make:
77
- return gr.update(choices=[], value=None)
78
- opts = sorted(DF.loc[DF['make'].eq(make), 'model'].dropna().unique().tolist())
79
- return gr.update(choices=opts, value=None)
80
-
81
- def trim_year(make, model):
82
- if make and model and (make, model) in TRIM_CHOICES:
83
- trims = TRIM_CHOICES[(make, model)]
84
- else:
85
- sub = DF
86
- if make: sub = sub[sub['make'] == make]
87
- if model: sub = sub[sub['model'] == model]
88
- trims = sorted(sub['trim'].astype(str).dropna().unique().tolist())[:20]
89
- if make and model:
90
- years = sorted(
91
- DF.loc[(DF['make'].eq(make)) & (DF['model'].eq(model)), 'year']
92
- .dropna().astype(int).unique().tolist()
93
- )
94
- else:
95
- years = []
96
- return trims, years
97
-
98
- def on_model_change(make, model):
99
- trims, years = trim_year(make, model)
100
- return gr.update(choices=trims, value=None), gr.update(choices=years, value=None)
101
-
102
- def apply_filters(df, body, fuel, y_min, y_max, p_min, p_max, safety, reliab):
103
- out = df.copy()
104
- if body != 'Any': out = out[out['body_type'] == body]
105
- if fuel != 'Any': out = out[out['fuel'] == fuel]
106
- out = out[(out['year'] >= y_min) & (out['year'] <= y_max)]
107
- out = out[(out['price_usd'] >= p_min) & (out['price_usd'] <= p_max)]
108
- out = out[(out['safety_rating'] >= safety) & (out['reliability_score'] >= reliab)]
109
- return out
110
-
111
- def placeholder_svg_data_uri(title):
112
- svg = f\"\"\"<svg xmlns='http://www.w3.org/2000/svg' width='480' height='320'>
113
- <rect width='100%' height='100%' fill='#e8eef7'/>
114
- <text x='50%' y='50%' dominant-baseline='middle' text-anchor='middle'
115
- font-family='Arial' font-size='26' fill='#223'>
116
- {title}
117
- </text>
118
- </svg>\"\"\"
119
- return "data:image/svg+xml;utf8," + urllib.parse.quote(svg)
120
-
121
- def build_gallery_html(df_rows):
122
- cards = []
123
- for _, r in df_rows.iterrows():
124
- label = f"{r['make']} {r['model']} {generic_to_display(r['make'], r['model'], r['trim'])}"
125
- img_src = ""
126
- if 'image_url' in r and isinstance(r['image_url'], str) and r['image_url'].strip():
127
- img_src = r['image_url'].strip()
128
- else:
129
- img_src = placeholder_svg_data_uri(f"{r['make']} {r['model']}")
130
- cards.append(f\"\"\"
131
- <div style="width:240px;margin:6px;border:1px solid #ddd;border-radius:12px;overflow:hidden;background:#fff;">
132
- <img src="{img_src}" style="width:240px;height:160px;object-fit:cover;display:block" />
133
- <div style="padding:8px 10px;font:14px/1.3 Arial,sans-serif;color:#111">{label}</div>
134
- </div>
135
- \"\"\")
136
- return f"<div style='display:flex;flex-wrap:wrap'>{''.join(cards)}</div>"
137
-
138
- def find_anchor(make, model, trim_display, year):
139
- # Map display trim to dataset generic using alias mapping if present
140
- def norm(make, model, t):
141
- if not t: return None
142
- # if the alias exists globally, use its generic
143
- return TRIM_ALIAS_TO_GENERIC.get(t, t)
144
-
145
- trim_generic = norm(make, model, trim_display)
146
-
147
- sub = DF.copy()
148
- if make: sub = sub[sub['make'] == make]
149
- if model: sub = sub[sub['model'] == model]
150
-
151
- def pick(df_):
152
- if df_.empty: return None
153
- return df_.sort_values('popularity_score', ascending=False).iloc[0]
154
-
155
- exact = sub.copy()
156
- if trim_generic: exact = exact[exact['trim'] == trim_generic]
157
- if year: exact = exact[exact['year'] == year]
158
- if not exact.empty:
159
- return pick(exact)
160
-
161
- if year:
162
- y_only = sub[sub['year'] == year]
163
- if not y_only.empty: return pick(y_only)
164
-
165
- if trim_generic:
166
- t_only = sub[sub['trim'] == trim_generic]
167
- if not t_only.empty: return pick(t_only)
168
-
169
- return pick(sub)
170
-
171
- def recommend(make, model, trim_display, year, topk, alpha,
172
- body, fuel, y_min, y_max, p_min, p_max, safety, reliab,
173
- cross_brand_only=True, exclude_same_model=True):
174
-
175
- a = find_anchor(make, model, trim_display, year)
176
- if a is None:
177
- return "No match for that combo.", None, "", None
178
-
179
- pool = DF.copy()
180
- if cross_brand_only:
181
- pool = pool[pool['make'] != a['make']]
182
- if exclude_same_model:
183
- pool = pool[~((pool['make'] == a['make']) & (pool['model'] == a['model']))]
184
-
185
- pool = apply_filters(pool, body, fuel, int(y_min), int(y_max), int(p_min), int(p_max), int(safety), int(reliab))
186
- if pool.empty:
187
- return "No cars after filters. Try widening year/price/safety.", None, "", None
188
-
189
- Etext, Enum = ensure_emb()
190
- idx_anchor = int(a.name)
191
- cand_idx = pool.index.values
192
-
193
- st = cosine_similarity(Etext[idx_anchor:idx_anchor+1], Etext[cand_idx])[0]
194
- sn = cosine_similarity(Enum[idx_anchor:idx_anchor+1], Enum[cand_idx])[0]
195
- s = float(alpha)*st + (1-float(alpha))*sn
196
-
197
- order = np.argsort(-s)
198
- seen = set(); chosen = []
199
- for j in order:
200
- r = DF.loc[cand_idx[j]]
201
- key = (r['make'], r['model'])
202
- if key in seen: continue # enforce cross-brand & unique model
203
- seen.add(key)
204
- chosen.append(cand_idx[j])
205
- if len(chosen) >= int(topk): break
206
-
207
- if not chosen:
208
- return "No recommendations found after constraints.", None, "", None
209
-
210
- sel = DF.loc[chosen].copy()
211
- sel['trim_display'] = sel.apply(lambda r: generic_to_display(r['make'], r['model'], r['trim']), axis=1)
212
-
213
- sim_lookup = {cand_idx[j]: round(float(s[j])*100, 1) for j in order}
214
- sel['similarity_%'] = sel.index.map(lambda k: sim_lookup.get(k, 0.0))
215
-
216
- cols = ['name','make','model','trim_display','year','body_type','fuel','engine_type',
217
- 'price_usd','horsepower','zero_to_100_kmh_s','popularity_score','comfort_score',
218
- 'reliability_score','tech_score','ownership_cost_score','safety_rating','similarity_%']
219
-
220
- anchor_text = (f"**{a['make']} {a['model']} {generic_to_display(a['make'], a['model'], a['trim'])} "
221
- f"{int(a['year'])}** \\n"
222
- f"Body: {a['body_type']} • Fuel: {a['fuel']} • Engine: {a['engine_type']} \\n"
223
- f"HP: {int(a['horsepower'])} • 0–100: {a['zero_to_100_kmh_s']}s • Price: ${int(a['price_usd']):,} \\n"
224
- f"Popularity {int(a['popularity_score'])}/10 • Comfort {int(a['comfort_score'])}/10 • "
225
- f"Reliability {int(a['reliability_score'])}/100 • Safety {int(a['safety_rating'])}★")
226
-
227
- note = (f"α = {float(alpha):.2f} (text ↔ numeric) • Cross-brand only = {cross_brand_only} "
228
- f"• Exclude same model = {exclude_same_model}")
229
-
230
- gallery = build_gallery_html(sel)
231
- return anchor_text, sel[cols], note, gallery
232
-
233
- def build_ui():
234
- y_lo, y_hi = int(DF['year'].min()), int(DF['year'].max())
235
- p_lo, p_hi = int(DF['price_usd'].min()), int(DF['price_usd'].max())
236
-
237
- with gr.Blocks() as demo:
238
- gr.Markdown("# RideSearch — cross-brand recommendations with real trims")
239
-
240
- with gr.Tab("Pick & Recommend"):
241
- with gr.Row():
242
- mk = gr.Dropdown(sorted(DF['make'].dropna().unique().tolist()), label="Make", value=None)
243
- md = gr.Dropdown([], label="Model", value=None)
244
- tr = gr.Dropdown([], label="Trim (optional)", value=None)
245
- yr = gr.Dropdown([], label="Year (optional)", value=None)
246
- mk.change(models_for, mk, md)
247
- md.change(lambda a,b: on_model_change(a,b), [mk, md], [tr, yr])
248
-
249
- with gr.Row():
250
- body = gr.Dropdown(['Any'] + sorted(DF['body_type'].dropna().unique().tolist()), value='Any', label='Body')
251
- fuel = gr.Dropdown(['Any'] + sorted(DF['fuel'].dropna().unique().tolist()), value='Any', label='Fuel')
252
- with gr.Row():
253
- y_min = gr.Slider(y_lo, y_hi, value=y_lo, step=1, label='Year min')
254
- y_max = gr.Slider(y_lo, y_hi, value=y_hi, step=1, label='Year max')
255
- with gr.Row():
256
- p_min = gr.Slider(p_lo, p_hi, value=p_lo, step=500, label='Price min (USD)')
257
- p_max = gr.Slider(p_lo, p_hi, value=min(p_hi, 80000), step=500, label='Price max (USD)')
258
- with gr.Row():
259
- safety = gr.Slider(3, 5, value=4, step=1, label='Min Safety ★')
260
- reliab = gr.Slider(55, 99, value=70, step=1, label='Min Reliability')
261
- with gr.Row():
262
- topk = gr.Slider(1, 10, value=5, step=1, label='Recommendations')
263
- alpha = gr.Slider(0, 1, value=0.7, step=0.05, label='α — Text vs Numeric')
264
- with gr.Row():
265
- cross = gr.Checkbox(label="Cross-brand only", value=True)
266
- xmodel = gr.Checkbox(label="Exclude same model family", value=True)
267
-
268
- go = gr.Button("Recommend")
269
- anchor_md = gr.Markdown()
270
- table = gr.Dataframe(interactive=False)
271
- note = gr.Markdown()
272
- gallery = gr.HTML()
273
-
274
- go.click(
275
- recommend,
276
- [mk, md, tr, yr, topk, alpha, body, fuel, y_min, y_max, p_min, p_max, safety, reliab, cross, xmodel],
277
- [anchor_md, table, note, gallery]
278
- )
279
- gr.Markdown("Tip: Add an 'image_url' column in the CSV for real photos.")
280
-
281
- return demo
282
-
283
- demo = build_ui()
284
-
285
- if __name__ == "__main__":
286
- demo.queue().launch(server_name="0.0.0.0", server_port=7860)