MatanYehudaDataAnalyst commited on
Commit
857b4ce
Β·
verified Β·
1 Parent(s): 896f562

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +39 -28
app.py CHANGED
@@ -9,26 +9,27 @@ from sklearn.metrics.pairwise import cosine_similarity
9
  # ==========================================
10
  # 1. SETUP & DATA LOADING
11
  # ==========================================
12
- # Assuming files are in the same root directory as app.py
13
  CSV_PATH = "cleaned_dataset_10k.csv"
14
  PKL_PATH = "final_embeddings_10k.pkl"
15
 
16
  if not os.path.exists(CSV_PATH) or not os.path.exists(PKL_PATH):
17
- raise FileNotFoundError("Missing required data files. Ensure CSV and PKL are uploaded.")
18
 
19
  # Load the restaurant dataset
20
  df = pd.read_csv(CSV_PATH)
21
  df.columns = [c.strip().lower().replace(' ', '_') for c in df.columns]
22
 
23
- # Load pre-computed embeddings
24
  with open(PKL_PATH, 'rb') as f:
25
  embedding_data = pickle.load(f)
26
  dataset_embeddings = embedding_data['embeddings'] if isinstance(embedding_data, dict) else embedding_data
27
 
28
- # Load the semantic transformer model (MPNet for high fidelity)
 
29
  model = SentenceTransformer('sentence-transformers/all-mpnet-base-v2')
30
 
31
- # Pre-calculate Persona Taste Profiles (Mean Vectors)
32
  persona_profiles = {}
33
  for persona in df['reviewer_persona'].unique():
34
  if pd.isna(persona): continue
@@ -37,14 +38,14 @@ for persona in df['reviewer_persona'].unique():
37
  persona_profiles[persona] = np.mean(persona_vectors, axis=0)
38
 
39
  # ==========================================
40
- # 2. CORE RECOMMENDATION ENGINE (HYBRID)
41
  # ==========================================
42
  def run_ven_engine(budget, dietary, company, purpose, noise):
43
  """
44
- Finds the best restaurant match using Hybrid Scoring:
45
- 70% Semantic Contextual Fit + 30% User Rating.
46
  """
47
- # Construct the user's semantic context
48
  user_context = f"Searching for a {budget} experience, {dietary} friendly. Group: {company}. Occasion: {purpose}. Atmosphere: {noise}."
49
  query_vec = model.encode([user_context])
50
 
@@ -53,25 +54,25 @@ def run_ven_engine(budget, dietary, company, purpose, noise):
53
  for p, v in persona_profiles.items()}
54
  closest_persona = max(persona_sims, key=persona_sims.get)
55
 
56
- # Step B: Filter reviews belonging to that persona
57
  persona_indices = df[df['reviewer_persona'] == closest_persona].index
58
  persona_embeddings = dataset_embeddings[persona_indices]
59
 
60
- # Step C: Calculate Contextual Similarity for specific reviews within that persona
61
  sub_similarities = cosine_similarity(query_vec, persona_embeddings)[0]
62
 
63
  persona_df = df.loc[persona_indices].copy()
64
  persona_df['semantic_fit'] = sub_similarities
65
  persona_df['norm_rating'] = persona_df['rating_score'] / 5.0
66
 
67
- # CALCULATE FINAL SCORE
68
  persona_df['final_score'] = (persona_df['semantic_fit'] * 0.7) + (persona_df['norm_rating'] * 0.3)
69
 
70
- # Select the top re-ranked result
71
  top_match = persona_df.sort_values(by='final_score', ascending=False).iloc[0]
72
  match_pct = int(top_match['final_score'] * 100)
73
 
74
- # Return Styled HTML Result Card
75
  return f"""
76
  <div style="background: white; border-radius: 20px; padding: 25px; color: #0f172a !important; text-align: left; border-left: 10px solid #f97316; box-shadow: 0 10px 25px rgba(0,0,0,0.2);">
77
  <div style="display:flex; justify-content:space-between; align-items: flex-start;">
@@ -96,57 +97,66 @@ def run_ven_engine(budget, dietary, company, purpose, noise):
96
  """
97
 
98
  # ==========================================
99
- # 3. GRADIO UI SETUP (FINAL VISIBILITY FIX)
100
  # ==========================================
 
 
101
  ven_css = """
102
- /* 1. Dark background for the whole app */
103
  .gradio-container { background-color: #0f172a !important; }
104
- h1 { color: white !important; text-align: center; font-weight: 900 !important; font-size: 2.5rem !important; margin-bottom: 20px !important; }
105
 
106
- /* 2. Main Labels (e.g., "3. Social Context") -> MUST BE WHITE */
107
  label span {
108
  color: white !important;
109
  font-weight: 700 !important;
110
- font-size: 15px !important;
 
111
  }
112
 
113
- /* 3. RADIO CHOICE TEXT (e.g., "Solo", "Date") -> MUST BE DARK SATE */
114
- /* Since radio buttons have white backgrounds, white text is invisible. We force it to dark blue/grey. */
115
- .gr-radio label span, .gr-radio span {
 
 
 
116
  color: #1e293b !important;
117
- font-weight: 600 !important;
118
  }
119
 
120
- /* 4. Orange primary button styling */
121
  .ven-button {
122
  background-color: #f97316 !important;
123
  color: white !important;
124
  border: none !important;
125
  font-weight: 900 !important;
126
  font-size: 18px !important;
127
- height: 50px !important;
128
  border-radius: 12px !important;
129
  }
130
 
131
- /* 5. Quick Vibe Starters table colors */
132
  .gr-samples-table { background-color: #1e293b !important; color: white !important; }
133
  """
134
 
135
- with gr.Blocks(css=ven_css, title="VEN - AI Restaurant Match") as demo:
136
  gr.Markdown("# πŸ” VEN: Restaurant Matchmaker")
137
 
138
  with gr.Row():
139
  with gr.Column(scale=1):
140
  with gr.Group():
 
141
  in_budget = gr.Dropdown(["Budget-friendly", "Mid-range", "Premium"], label="1. Wallet Size", value="Mid-range")
142
  in_diet = gr.Dropdown(["Anything", "Vegetarian", "Vegan", "Meat-lover"], label="2. Diet Preference", value="Anything")
143
  in_company = gr.Radio(["Solo", "Date/Couple", "Friends", "Business"], label="3. Social Context", value="Date/Couple")
144
  in_purpose = gr.Dropdown(["Casual dinner", "Special occasion", "Quick bite", "Professional meeting"], label="4. The Mission", value="Casual dinner")
145
  in_noise = gr.Radio(["Quiet/Intimate", "Moderate/Social", "Lively/Music"], label="5. Vibe / Noise", value="Moderate/Social")
 
146
  btn = gr.Button("Find My Table", variant="primary", elem_classes="ven-button")
147
 
148
  with gr.Column(scale=1):
149
- output_ui = gr.HTML("<div style='text-align:center; padding:100px; color:#64748b; font-weight:700; border: 2px dashed #1e293b; border-radius: 20px;'>Your personalized AI match will appear here</div>")
 
150
 
151
  gr.Markdown("### πŸš€ Quick Vibe Starters")
152
  gr.Examples(
@@ -161,6 +171,7 @@ with gr.Blocks(css=ven_css, title="VEN - AI Restaurant Match") as demo:
161
  cache_examples=False,
162
  )
163
 
 
164
  btn.click(run_ven_engine, inputs=[in_budget, in_diet, in_company, in_purpose, in_noise], outputs=output_ui)
165
 
166
  if __name__ == "__main__":
 
9
  # ==========================================
10
  # 1. SETUP & DATA LOADING
11
  # ==========================================
12
+ # Paths for the final 10k dataset and embeddings
13
  CSV_PATH = "cleaned_dataset_10k.csv"
14
  PKL_PATH = "final_embeddings_10k.pkl"
15
 
16
  if not os.path.exists(CSV_PATH) or not os.path.exists(PKL_PATH):
17
+ raise FileNotFoundError("Missing required data files (CSV or PKL) in the root directory.")
18
 
19
  # Load the restaurant dataset
20
  df = pd.read_csv(CSV_PATH)
21
  df.columns = [c.strip().lower().replace(' ', '_') for c in df.columns]
22
 
23
+ # Load pre-computed embeddings (768 dimensions)
24
  with open(PKL_PATH, 'rb') as f:
25
  embedding_data = pickle.load(f)
26
  dataset_embeddings = embedding_data['embeddings'] if isinstance(embedding_data, dict) else embedding_data
27
 
28
+ # Load the semantic transformer model (MPNet)
29
+ # This model converts natural language queries into semantic vectors
30
  model = SentenceTransformer('sentence-transformers/all-mpnet-base-v2')
31
 
32
+ # Pre-calculate Taste Profiles (Mean Vectors) for each of the 6 personas
33
  persona_profiles = {}
34
  for persona in df['reviewer_persona'].unique():
35
  if pd.isna(persona): continue
 
38
  persona_profiles[persona] = np.mean(persona_vectors, axis=0)
39
 
40
  # ==========================================
41
+ # 2. HYBRID RECOMMENDATION ENGINE
42
  # ==========================================
43
  def run_ven_engine(budget, dietary, company, purpose, noise):
44
  """
45
+ Finds the best restaurant match by combining Persona Similarity (General Taste)
46
+ and Review Similarity (Contextual Fit).
47
  """
48
+ # Create the user's specific context string
49
  user_context = f"Searching for a {budget} experience, {dietary} friendly. Group: {company}. Occasion: {purpose}. Atmosphere: {noise}."
50
  query_vec = model.encode([user_context])
51
 
 
54
  for p, v in persona_profiles.items()}
55
  closest_persona = max(persona_sims, key=persona_sims.get)
56
 
57
+ # Step B: Search specifically within that persona's reviews for the best contextual fit
58
  persona_indices = df[df['reviewer_persona'] == closest_persona].index
59
  persona_embeddings = dataset_embeddings[persona_indices]
60
 
61
+ # Compute similarity for every individual review in this persona group
62
  sub_similarities = cosine_similarity(query_vec, persona_embeddings)[0]
63
 
64
  persona_df = df.loc[persona_indices].copy()
65
  persona_df['semantic_fit'] = sub_similarities
66
  persona_df['norm_rating'] = persona_df['rating_score'] / 5.0
67
 
68
+ # CALCULATE HYBRID SCORE (70% context match + 30% original rating)
69
  persona_df['final_score'] = (persona_df['semantic_fit'] * 0.7) + (persona_df['norm_rating'] * 0.3)
70
 
71
+ # Retrieve the top re-ranked result
72
  top_match = persona_df.sort_values(by='final_score', ascending=False).iloc[0]
73
  match_pct = int(top_match['final_score'] * 100)
74
 
75
+ # Return Styled HTML Card (with absolute colors to prevent theme overrides)
76
  return f"""
77
  <div style="background: white; border-radius: 20px; padding: 25px; color: #0f172a !important; text-align: left; border-left: 10px solid #f97316; box-shadow: 0 10px 25px rgba(0,0,0,0.2);">
78
  <div style="display:flex; justify-content:space-between; align-items: flex-start;">
 
97
  """
98
 
99
  # ==========================================
100
+ # 3. GRADIO UI SETUP (VISIBILITY FIX)
101
  # ==========================================
102
+ # We use targeted CSS to ensure labels are white on dark background,
103
+ # but choices inside radio buttons are dark on light background.
104
  ven_css = """
105
+ /* Global Container */
106
  .gradio-container { background-color: #0f172a !important; }
107
+ h1 { color: white !important; text-align: center; font-weight: 950 !important; font-size: 2.5rem !important; margin-bottom: 20px !important; }
108
 
109
+ /* Input Header Labels (The questions above the inputs) */
110
  label span {
111
  color: white !important;
112
  font-weight: 700 !important;
113
+ font-size: 14px !important;
114
+ margin-bottom: 5px !important;
115
  }
116
 
117
+ /* RADIO CHOICE TEXT (The actual options like 'Solo' or 'Lively') */
118
+ /* These elements are usually inside white/grey boxes in Gradio theme,
119
+ so we force the text to be DARK for visibility. */
120
+ .gr-radio label span,
121
+ .gr-radio span,
122
+ [data-testid="block-info"] + div label span {
123
  color: #1e293b !important;
124
+ font-weight: 700 !important;
125
  }
126
 
127
+ /* Style the primary orange button */
128
  .ven-button {
129
  background-color: #f97316 !important;
130
  color: white !important;
131
  border: none !important;
132
  font-weight: 900 !important;
133
  font-size: 18px !important;
134
+ height: 52px !important;
135
  border-radius: 12px !important;
136
  }
137
 
138
+ /* Quick Vibe Starters table styling */
139
  .gr-samples-table { background-color: #1e293b !important; color: white !important; }
140
  """
141
 
142
+ with gr.Blocks(css=ven_css, title="VEN - AI Restaurant Matchmaker") as demo:
143
  gr.Markdown("# πŸ” VEN: Restaurant Matchmaker")
144
 
145
  with gr.Row():
146
  with gr.Column(scale=1):
147
  with gr.Group():
148
+ # Survey inputs for the 5 dimensions
149
  in_budget = gr.Dropdown(["Budget-friendly", "Mid-range", "Premium"], label="1. Wallet Size", value="Mid-range")
150
  in_diet = gr.Dropdown(["Anything", "Vegetarian", "Vegan", "Meat-lover"], label="2. Diet Preference", value="Anything")
151
  in_company = gr.Radio(["Solo", "Date/Couple", "Friends", "Business"], label="3. Social Context", value="Date/Couple")
152
  in_purpose = gr.Dropdown(["Casual dinner", "Special occasion", "Quick bite", "Professional meeting"], label="4. The Mission", value="Casual dinner")
153
  in_noise = gr.Radio(["Quiet/Intimate", "Moderate/Social", "Lively/Music"], label="5. Vibe / Noise", value="Moderate/Social")
154
+
155
  btn = gr.Button("Find My Table", variant="primary", elem_classes="ven-button")
156
 
157
  with gr.Column(scale=1):
158
+ # Placeholder for the AI recommendation card
159
+ output_ui = gr.HTML("<div style='text-align:center; padding:100px; color:#64748b; font-weight:700; border: 2px dashed #1e293b; border-radius: 20px;'>Fill the survey to generate your AI match</div>")
160
 
161
  gr.Markdown("### πŸš€ Quick Vibe Starters")
162
  gr.Examples(
 
171
  cache_examples=False,
172
  )
173
 
174
+ # Event binding
175
  btn.click(run_ven_engine, inputs=[in_budget, in_diet, in_company, in_purpose, in_noise], outputs=output_ui)
176
 
177
  if __name__ == "__main__":