Gordon Li commited on
Commit
46e8dfb
·
1 Parent(s): be09ab2

highlight and review relevance

Browse files
Files changed (3) hide show
  1. AirbnbMapVisualiser.py +222 -167
  2. app.py +55 -26
  3. style.css +7 -0
AirbnbMapVisualiser.py CHANGED
@@ -2,7 +2,6 @@ import oracledb
2
  import pandas as pd
3
  import folium
4
  from html import escape
5
- from transformers import AutoTokenizer
6
  import numpy as np
7
  from TrafficSpot import TrafficSpotManager
8
 
@@ -23,13 +22,10 @@ class AirbnbMapVisualiser:
23
  increment=1,
24
  getmode=oracledb.SPOOL_ATTRVAL_WAIT
25
  )
26
- self.tokenizer = AutoTokenizer.from_pretrained('bert-base-uncased')
27
  self.traffic_manager = TrafficSpotManager(self.connection_params)
28
 
29
  try:
30
- # Get all neighborhoods first (this is a small query)
31
  self.neighborhoods = self.get_all_neighborhoods()
32
- # Initialize with Sha Tin listings
33
  self.cached_listings = {}
34
  self.cached_listings["Sha Tin"] = self.get_neighborhood_listings("Sha Tin")
35
  except Exception as e:
@@ -40,17 +36,17 @@ class AirbnbMapVisualiser:
40
  def get_all_neighborhoods(self):
41
  connection = self.pool.acquire()
42
  try:
43
- cursor = connection.cursor()
44
- cursor.prefetchrows = 50
45
- cursor.arraysize = 50
46
- cursor.execute("""
47
- SELECT DISTINCT NEIGHBOURHOOD
48
- FROM airbnb_master_data
49
- WHERE NEIGHBOURHOOD IS NOT NULL
50
- ORDER BY NEIGHBOURHOOD
51
- """)
52
- neighborhoods = [row[0] for row in cursor.fetchall()]
53
- return neighborhoods
54
  except Exception as e:
55
  print(f"Database error getting neighborhoods: {str(e)}")
56
  return []
@@ -58,30 +54,33 @@ class AirbnbMapVisualiser:
58
  self.pool.release(connection)
59
 
60
  def get_neighborhood_listings(self, neighborhood):
61
- connection = self.pool.acquire()
62
  if neighborhood in self.cached_listings:
63
  return self.cached_listings[neighborhood]
 
 
64
  try:
65
- cursor = connection.cursor()
66
- cursor.prefetchrows = 50
67
- cursor.arraysize = 50
68
- cursor.execute("""
69
- SELECT m.ID, m.NAME, m.HOST_NAME, m.NEIGHBOURHOOD,
70
- m.LATITUDE, m.LONGITUDE, m.ROOM_TYPE, m.PRICE,
71
- COUNT(r.LISTING_ID) as NUMBER_OF_REVIEWS, m.REVIEWS_PER_MONTH,
72
- m.MINIMUM_NIGHTS, m.AVAILABILITY_365
73
- FROM airbnb_master_data m
74
- LEFT JOIN airbnb_reviews_data r ON m.ID = r.LISTING_ID
75
- WHERE m.LATITUDE IS NOT NULL AND m.LONGITUDE IS NOT NULL AND NEIGHBOURHOOD = :neighborhood AND ROWNUM <= 150
76
- GROUP BY m.ID, m.NAME, m.HOST_NAME, m.NEIGHBOURHOOD,
77
- m.LATITUDE, m.LONGITUDE, m.ROOM_TYPE, m.PRICE,
78
- m.REVIEWS_PER_MONTH, m.MINIMUM_NIGHTS, m.AVAILABILITY_365
79
-
80
- """, neighborhood=neighborhood)
81
- listings = cursor.fetchall()
82
- # Cache the results
83
- self.cached_listings[neighborhood] = listings
84
- return listings
 
 
85
  except Exception as e:
86
  print(f"Database error: {str(e)}")
87
  return []
@@ -89,112 +88,150 @@ m.REVIEWS_PER_MONTH, m.MINIMUM_NIGHTS, m.AVAILABILITY_365
89
  self.pool.release(connection)
90
 
91
  def get_listing_reviews(self, listing_id):
 
92
  try:
93
- with oracledb.connect(**self.connection_params) as conn:
94
- cursor = conn.cursor()
95
- cursor.execute("""
96
- SELECT REVIEW_DATE, REVIEWER_NAME,
97
- CASE
98
- WHEN LENGTH(COMMENTS) > 200
99
- THEN SUBSTR(COMMENTS, 1, 200) || '...'
100
- ELSE COMMENTS
101
- END as COMMENTS
102
- FROM AIRBNB_REVIEWS_DATA
103
- WHERE LISTING_ID = :listing_id AND ROWNUM <= 50
104
- ORDER BY REVIEW_DATE DESC
105
- """, listing_id=str(listing_id))
106
- reviews = cursor.fetchall()
107
-
108
- formatted_reviews = []
109
- for review in reviews:
110
- review_date, reviewer_name, comments = review
111
- formatted_review = (
112
- str(review_date) if review_date else '',
113
- str(reviewer_name) if reviewer_name else '',
114
- str(comments) if comments else ''
115
- )
116
- formatted_reviews.append(formatted_review)
 
117
 
118
- return formatted_reviews
119
- except oracledb.DatabaseError as e:
120
- print(f"Database error fetching reviews: {str(e)}")
121
- return []
122
  except Exception as e:
123
  print(f"Error fetching reviews: {str(e)}")
124
  return []
 
 
125
 
126
- def preprocess_text(self, text):
127
- if not isinstance(text, str):
128
- return ""
129
- tokens = self.tokenizer.tokenize(text)
130
- cleaned_text = " ".join(tokens)
131
- return cleaned_text
132
-
133
- def create_listing_description(self, row):
134
- description = f"""{row['name']}"""
135
- return self.preprocess_text(description)
136
-
137
- def compute_token_based_similarity(self, text1, text2):
138
- tokens1 = set(self.tokenizer.tokenize(text1.lower()))
139
- tokens2 = set(self.tokenizer.tokenize(text2.lower()))
140
- intersection = len(tokens1.intersection(tokens2))
141
- normalization_factor = min(len(tokens1), len(tokens2))
142
-
143
- if normalization_factor == 0:
144
- return 0
145
 
146
- base_similarity = intersection / normalization_factor
147
- boosted_score = 1 / (1 + np.exp(-6 * (base_similarity - 0.5)))
148
- return boosted_score
 
 
149
 
150
  def compute_search_scores(self, df, search_query):
151
- processed_query = self.preprocess_text(search_query)
152
- query_terms = set(processed_query.lower().split())
 
153
 
 
 
154
  scores = []
155
- for _, row in df.iterrows():
156
- listing_desc = self.create_listing_description(row)
157
- desc_terms = set(listing_desc.lower().split())
158
-
159
- token_sim = self.compute_token_based_similarity(processed_query, listing_desc)
160
-
161
- exact_match_bonus = 0
162
- partial_match_bonus = 0
163
-
164
- for term in query_terms:
165
- if term in desc_terms:
166
- exact_match_bonus += 0.2
167
-
168
- for desc_term in desc_terms:
169
- if term in desc_term or desc_term in term:
170
- partial_match_bonus += 0.1
171
-
172
- category_keywords = {
173
- 'location': ['near', 'close', 'located', 'area', 'district'],
174
- 'amenities': ['wifi', 'kitchen', 'bathroom', 'furnished'],
175
- 'type': ['apartment', 'house', 'room', 'studio'],
176
- 'price': ['cheap', 'affordable', 'expensive', 'luxury']
177
- }
178
-
179
- category_bonus = 0
180
- for category, keywords in category_keywords.items():
181
- if any(keyword in processed_query.lower() for keyword in keywords):
182
- if any(keyword in listing_desc.lower() for keyword in keywords):
183
- category_bonus += 0.15
184
-
185
- final_score = min(1.0, (
186
- token_sim * 0.4 +
187
- exact_match_bonus * 0.3 +
188
- partial_match_bonus * 0.2 +
189
- category_bonus * 0.1
190
- ))
191
-
192
- boosted_score = np.power(final_score, 0.7)
193
- scores.append(boosted_score)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
194
 
195
  return np.array(scores)
196
 
197
  def sort_by_relevance(self, df, search_query):
 
198
  if not search_query:
199
  return df
200
 
@@ -203,43 +240,58 @@ m.REVIEWS_PER_MONTH, m.MINIMUM_NIGHTS, m.AVAILABILITY_365
203
  df['relevance_percentage'] = df['relevance_score'] * 100
204
 
205
  def get_relevance_description(score):
206
- if score >= 70:
207
  return "Perfect match"
208
- elif score >= 55:
209
- return "Very relevant"
210
  elif score >= 40:
211
- return "Relevant"
212
- elif score >= 25:
213
- return "Somewhat relevant"
214
- elif score >= 15:
215
- return "Slightly relevant"
216
  else:
217
- return "Less relevant"
218
 
219
  df['relevance_features'] = df['relevance_percentage'].apply(get_relevance_description)
220
 
221
  def get_matching_features(row):
222
- query_tokens = set(self.tokenizer.tokenize(search_query.lower()))
223
- desc_tokens = set(self.tokenizer.tokenize(self.create_listing_description(row).lower()))
224
- matching_tokens = query_tokens.intersection(desc_tokens)
225
-
226
- if not matching_tokens:
227
- return "No direct matches"
228
-
229
- grouped_matches = []
230
- processed_tokens = set()
231
-
232
- for token in matching_tokens:
233
- if token in processed_tokens:
234
- continue
235
-
236
- similar_tokens = {t for t in matching_tokens if (token in t or t in token)}
237
- processed_tokens.update(similar_tokens)
238
-
239
- if similar_tokens:
240
- grouped_matches.append("/".join(sorted(similar_tokens)))
241
-
242
- return ", ".join(grouped_matches)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
243
 
244
  df['matching_features'] = df.apply(get_matching_features, axis=1)
245
  return df.sort_values('relevance_score', ascending=False)
@@ -287,16 +339,20 @@ m.REVIEWS_PER_MONTH, m.MINIMUM_NIGHTS, m.AVAILABILITY_365
287
  relevance_info = ""
288
  if search_query and 'relevance_percentage' in row:
289
  relevance_info = f"""
290
- <p style='margin: 5px 0;'>
291
- <strong>Relevance:</strong> {row['relevance_percentage']:.0f}%
292
- <br/>
293
- <em>{row['relevance_features']}</em>
294
- </p>
 
 
 
 
295
  """
296
 
297
  popup_content = f"""
298
- <div style='min-width: 200px; padding: 10px;'>
299
- <h4 style='margin: 0 0 10px 0;'>{escape(str(row['name']))}</h4>
300
  <p style='margin: 5px 0;'><strong>Host:</strong> {escape(str(row['host_name']))}</p>
301
  <p style='margin: 5px 0;'><strong>Room Type:</strong> {escape(str(row['room_type']))}</p>
302
  <p style='margin: 5px 0;'><strong>Price:</strong> ${row['price']:.0f}</p>
@@ -304,20 +360,18 @@ m.REVIEWS_PER_MONTH, m.MINIMUM_NIGHTS, m.AVAILABILITY_365
304
  {relevance_info}
305
  <button onclick="streamlit_click('{review_button_key}')"
306
  style="background-color: #4CAF50; color: white; padding: 8px 15px; border: none;
307
- border-radius: 4px; cursor: pointer; margin-top: 10px;">
308
  View Reviews ({len(reviews)})
309
  </button>
310
  </div>
311
  """
312
 
313
  marker_color = 'green' if selected_id == row['id'] else 'red'
314
-
315
  marker = folium.Marker(
316
  location=[row['latitude'], row['longitude']],
317
  popup=popup_content,
318
  icon=folium.Icon(color=marker_color, icon='home'),
319
  )
320
-
321
  marker.add_to(m)
322
 
323
  if selected_id is not None and row['id'] == selected_id:
@@ -325,4 +379,5 @@ m.REVIEWS_PER_MONTH, m.MINIMUM_NIGHTS, m.AVAILABILITY_365
325
 
326
  if show_traffic:
327
  self.traffic_manager.add_spots_to_map(m)
 
328
  return m, df
 
2
  import pandas as pd
3
  import folium
4
  from html import escape
 
5
  import numpy as np
6
  from TrafficSpot import TrafficSpotManager
7
 
 
22
  increment=1,
23
  getmode=oracledb.SPOOL_ATTRVAL_WAIT
24
  )
 
25
  self.traffic_manager = TrafficSpotManager(self.connection_params)
26
 
27
  try:
 
28
  self.neighborhoods = self.get_all_neighborhoods()
 
29
  self.cached_listings = {}
30
  self.cached_listings["Sha Tin"] = self.get_neighborhood_listings("Sha Tin")
31
  except Exception as e:
 
36
  def get_all_neighborhoods(self):
37
  connection = self.pool.acquire()
38
  try:
39
+ cursor = connection.cursor()
40
+ cursor.prefetchrows = 50
41
+ cursor.arraysize = 50
42
+ cursor.execute("""
43
+ SELECT DISTINCT NEIGHBOURHOOD
44
+ FROM airbnb_master_data
45
+ WHERE NEIGHBOURHOOD IS NOT NULL
46
+ ORDER BY NEIGHBOURHOOD
47
+ """)
48
+ neighborhoods = [row[0] for row in cursor.fetchall()]
49
+ return neighborhoods
50
  except Exception as e:
51
  print(f"Database error getting neighborhoods: {str(e)}")
52
  return []
 
54
  self.pool.release(connection)
55
 
56
  def get_neighborhood_listings(self, neighborhood):
 
57
  if neighborhood in self.cached_listings:
58
  return self.cached_listings[neighborhood]
59
+
60
+ connection = self.pool.acquire()
61
  try:
62
+ cursor = connection.cursor()
63
+ cursor.prefetchrows = 50
64
+ cursor.arraysize = 50
65
+ cursor.execute("""
66
+ SELECT m.ID, m.NAME, m.HOST_NAME, m.NEIGHBOURHOOD,
67
+ m.LATITUDE, m.LONGITUDE, m.ROOM_TYPE, m.PRICE,
68
+ COUNT(r.LISTING_ID) as NUMBER_OF_REVIEWS, m.REVIEWS_PER_MONTH,
69
+ m.MINIMUM_NIGHTS, m.AVAILABILITY_365
70
+ FROM airbnb_master_data m
71
+ LEFT JOIN airbnb_reviews_data r ON m.ID = r.LISTING_ID
72
+ WHERE m.LATITUDE IS NOT NULL
73
+ AND m.LONGITUDE IS NOT NULL
74
+ AND m.NEIGHBOURHOOD = :neighborhood
75
+ AND ROWNUM <= 150
76
+ GROUP BY m.ID, m.NAME, m.HOST_NAME, m.NEIGHBOURHOOD,
77
+ m.LATITUDE, m.LONGITUDE, m.ROOM_TYPE, m.PRICE,
78
+ m.REVIEWS_PER_MONTH, m.MINIMUM_NIGHTS, m.AVAILABILITY_365
79
+ """, neighborhood=neighborhood)
80
+
81
+ listings = cursor.fetchall()
82
+ self.cached_listings[neighborhood] = listings
83
+ return listings
84
  except Exception as e:
85
  print(f"Database error: {str(e)}")
86
  return []
 
88
  self.pool.release(connection)
89
 
90
  def get_listing_reviews(self, listing_id):
91
+ connection = self.pool.acquire()
92
  try:
93
+ cursor = connection.cursor()
94
+ cursor.execute("""
95
+ SELECT REVIEW_DATE, REVIEWER_NAME,
96
+ CASE
97
+ WHEN LENGTH(COMMENTS) > 200
98
+ THEN SUBSTR(COMMENTS, 1, 200) || '...'
99
+ ELSE COMMENTS
100
+ END as COMMENTS
101
+ FROM AIRBNB_REVIEWS_DATA
102
+ WHERE LISTING_ID = :listing_id
103
+ AND ROWNUM <= 50
104
+ ORDER BY REVIEW_DATE DESC
105
+ """, listing_id=int(listing_id))
106
+
107
+ reviews = cursor.fetchall()
108
+ formatted_reviews = []
109
+ for review in reviews:
110
+ review_date, reviewer_name, comments = review
111
+ formatted_review = (
112
+ str(review_date) if review_date else '',
113
+ str(reviewer_name) if reviewer_name else '',
114
+ str(comments) if comments else ''
115
+ )
116
+ formatted_reviews.append(formatted_review)
117
+ return formatted_reviews
118
 
 
 
 
 
119
  except Exception as e:
120
  print(f"Error fetching reviews: {str(e)}")
121
  return []
122
+ finally:
123
+ self.pool.release(connection)
124
 
125
+ def get_listing_reviews_for_search(self, listing_id):
126
+ """Get reviews for search analysis"""
127
+ connection = self.pool.acquire()
128
+ try:
129
+ cursor = connection.cursor()
130
+ cursor.execute("""
131
+ SELECT COMMENTS
132
+ FROM AIRBNB_REVIEWS_DATA
133
+ WHERE LISTING_ID = :listing_id
134
+ AND COMMENTS IS NOT NULL
135
+ AND ROWNUM <= 10
136
+ ORDER BY REVIEW_DATE DESC
137
+ """, listing_id=int(listing_id))
138
+
139
+ reviews = cursor.fetchall()
140
+ return [review[0] for review in reviews if review[0]]
 
 
 
141
 
142
+ except Exception as e:
143
+ print(f"Error fetching reviews for search: {str(e)}")
144
+ return []
145
+ finally:
146
+ self.pool.release(connection)
147
 
148
  def compute_search_scores(self, df, search_query):
149
+ """Compute search scores based on name and review content"""
150
+ if not search_query:
151
+ return np.zeros(len(df))
152
 
153
+ search_query = search_query.lower()
154
+ search_terms = search_query.split()
155
  scores = []
156
+
157
+ for idx, row in df.iterrows():
158
+ try:
159
+ name_score = 0
160
+ review_score = 0
161
+
162
+ # Name matching
163
+ name = str(row['name']).lower()
164
+
165
+ # Exact phrase match in name
166
+ if search_query in name:
167
+ name_score += 1.0
168
+
169
+ # Individual term matches in name
170
+ name_term_matches = sum(term in name for term in search_terms)
171
+ name_score += (name_term_matches / len(search_terms)) * 0.5
172
+
173
+ # Get reviews for content matching
174
+ reviews = self.get_listing_reviews_for_search(row['id'])
175
+ if reviews:
176
+ review_texts = [str(review).lower() for review in reviews]
177
+
178
+ # Search for exact phrase in reviews
179
+ phrase_matches = sum(search_query in review for review in review_texts)
180
+ if phrase_matches > 0:
181
+ review_score += min(phrase_matches * 0.2, 0.6)
182
+
183
+ # Search for individual terms in reviews
184
+ term_matches = sum(
185
+ sum(term in review for term in search_terms)
186
+ for review in review_texts
187
+ )
188
+ review_score += min(term_matches * 0.1, 0.4)
189
+
190
+ # Additional relevance factors
191
+ boost = 1.0
192
+
193
+ # Price relevance
194
+ if any(word in search_query for word in ['cheap', 'budget', 'affordable']):
195
+ if row['price'] < df['price'].mean() * 0.8:
196
+ boost += 0.2
197
+ elif any(word in search_query for word in ['expensive', 'luxury', 'high-end']):
198
+ if row['price'] > df['price'].mean() * 1.2:
199
+ boost += 0.2
200
+
201
+ # Room type relevance
202
+ room_type = str(row['room_type']).lower()
203
+ room_type_terms = {
204
+ 'private': ['private', 'own'],
205
+ 'shared': ['shared', 'share', 'sharing'],
206
+ 'entire': ['entire', 'whole', 'full']
207
+ }
208
+ for type_key, terms in room_type_terms.items():
209
+ if any(term in search_query for term in terms) and type_key in room_type:
210
+ boost += 0.2
211
+ break
212
+
213
+ # Location mentions
214
+ neighborhood = str(row['neighbourhood']).lower()
215
+ if neighborhood in search_query:
216
+ boost += 0.2
217
+
218
+ # Reviews quantity relevance
219
+ if any(term in search_query for term in ['popular', 'reviewed', 'recommended']):
220
+ if row['number_of_reviews'] > df['number_of_reviews'].mean():
221
+ boost += 0.2
222
+
223
+ # Combine scores with weights
224
+ final_score = ((name_score * 0.6) + (review_score * 0.4)) * boost
225
+ scores.append(min(1.0, final_score))
226
+
227
+ except Exception as e:
228
+ print(f"Error computing score for listing {row['id']}: {str(e)}")
229
+ scores.append(0.0)
230
 
231
  return np.array(scores)
232
 
233
  def sort_by_relevance(self, df, search_query):
234
+ """Sort listings by relevance using improved scoring system"""
235
  if not search_query:
236
  return df
237
 
 
240
  df['relevance_percentage'] = df['relevance_score'] * 100
241
 
242
  def get_relevance_description(score):
243
+ if score >= 80:
244
  return "Perfect match"
245
+ elif score >= 60:
246
+ return "Excellent match"
247
  elif score >= 40:
248
+ return "Good match"
249
+ elif score >= 20:
250
+ return "Partial match"
 
 
251
  else:
252
+ return "Low relevance"
253
 
254
  df['relevance_features'] = df['relevance_percentage'].apply(get_relevance_description)
255
 
256
  def get_matching_features(row):
257
+ try:
258
+ features = []
259
+ search_terms = search_query.lower().split()
260
+ name = str(row['name']).lower()
261
+
262
+ # Name matches
263
+ matching_terms = [term for term in search_terms if term in name]
264
+ if matching_terms:
265
+ features.append(f"Name matches: {', '.join(matching_terms)}")
266
+
267
+ # Review content matches
268
+ reviews = self.get_listing_reviews_for_search(row['id'])
269
+ if reviews:
270
+ review_matches = {}
271
+ # Initialize count for each search term
272
+ for term in search_terms:
273
+ review_matches[term] = set() # Use set to store unique review indices
274
+
275
+ # Count matches in each review
276
+ for i, review in enumerate(reviews):
277
+ review_text = str(review).lower()
278
+ for term in search_terms:
279
+ if term in review_text:
280
+ review_matches[term].add(i) # Add review index to set
281
+
282
+ # Format matches for display
283
+ formatted_matches = []
284
+ for term, matching_indices in review_matches.items():
285
+ if matching_indices: # If there are matches for this term
286
+ formatted_matches.append(f"{term} ({len(matching_indices)} reviews)")
287
+
288
+ if formatted_matches:
289
+ features.append(f"Matched based on High relevance , Keyword found in Review")
290
+ return " | ".join(features) if features else "Matched based on Low relevance"
291
+
292
+ except Exception as e:
293
+ print(f"Error in get_matching_features: {str(e)}")
294
+ return "Unable to determine matches"
295
 
296
  df['matching_features'] = df.apply(get_matching_features, axis=1)
297
  return df.sort_values('relevance_score', ascending=False)
 
339
  relevance_info = ""
340
  if search_query and 'relevance_percentage' in row:
341
  relevance_info = f"""
342
+ <div class='relevance-info' style='margin: 10px 0; padding: 8px; background-color: #f8f9fa; border-radius: 4px;'>
343
+ <p style='margin: 5px 0;'>
344
+ <strong>Match Score:</strong> {row['relevance_percentage']:.0f}%
345
+ <br/>
346
+ <strong>Relevance:</strong> {row['relevance_features']}
347
+ <br/>
348
+ <strong>Matching Features:</strong> {row['matching_features']}
349
+ </p>
350
+ </div>
351
  """
352
 
353
  popup_content = f"""
354
+ <div style='min-width: 280px; max-width: 320px; padding: 15px;'>
355
+ <h4 style='margin: 0 0 10px 0; color: #2c3e50;'>{escape(str(row['name']))}</h4>
356
  <p style='margin: 5px 0;'><strong>Host:</strong> {escape(str(row['host_name']))}</p>
357
  <p style='margin: 5px 0;'><strong>Room Type:</strong> {escape(str(row['room_type']))}</p>
358
  <p style='margin: 5px 0;'><strong>Price:</strong> ${row['price']:.0f}</p>
 
360
  {relevance_info}
361
  <button onclick="streamlit_click('{review_button_key}')"
362
  style="background-color: #4CAF50; color: white; padding: 8px 15px; border: none;
363
+ border-radius: 4px; cursor: pointer; margin-top: 10px; width: 100%;">
364
  View Reviews ({len(reviews)})
365
  </button>
366
  </div>
367
  """
368
 
369
  marker_color = 'green' if selected_id == row['id'] else 'red'
 
370
  marker = folium.Marker(
371
  location=[row['latitude'], row['longitude']],
372
  popup=popup_content,
373
  icon=folium.Icon(color=marker_color, icon='home'),
374
  )
 
375
  marker.add_to(m)
376
 
377
  if selected_id is not None and row['id'] == selected_id:
 
379
 
380
  if show_traffic:
381
  self.traffic_manager.add_spots_to_map(m)
382
+
383
  return m, df
app.py CHANGED
@@ -1,3 +1,4 @@
 
1
  import streamlit as st
2
  from html import escape
3
  from streamlit_folium import st_folium, folium_static
@@ -10,34 +11,62 @@ def load_css(css_file):
10
  st.markdown(f'<style>{f.read()}</style>', unsafe_allow_html=True)
11
 
12
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  def render_review_dialog():
14
- # Display reviews
15
- with st.container():
16
- col_title = st.columns([5, 1])
17
- with col_title[0]:
18
- st.markdown(f"### Reviews for {st.session_state.current_review_listing_name}")
19
- reviews = st.session_state.visualizer.get_listing_reviews(st.session_state.current_review_listing)
20
- if reviews:
21
- for review in reviews:
22
- try:
23
- review_date, reviewer_name, comments = review
24
- st.markdown(f"""
25
- <div class="review-card">
26
- <div class="review-header">
27
- {escape(str(reviewer_name))} - {escape(str(review_date))}
28
- </div>
29
- <div class="review-content">
30
- {escape(str(comments))}
31
- </div>
32
- </div>""", unsafe_allow_html=True)
33
- except Exception as e:
34
- st.error(f"Error displaying review: {str(e)}")
35
- else:
36
- st.info("No reviews available for this listing.")
 
 
 
 
 
 
 
 
 
 
37
  def main():
38
  st.set_page_config(
39
  layout="wide",
40
- page_title="HKUST AirBNB+",
41
  initial_sidebar_state="expanded"
42
  )
43
  load_css('style.css')
@@ -81,13 +110,13 @@ def main():
81
 
82
  with st.sidebar:
83
  st.markdown(
84
- '<p class="sidebar-header">HKUST AirBNB+ (External Hall Scheme for Non Local PG Students) <BR/></p>',
85
  unsafe_allow_html=True)
86
 
87
  search_query = st.text_input(
88
  "🔍 Search listings",
89
  value=st.session_state.search_query,
90
- placeholder="Try: 'cozy apartment near MTR' or 'quiet room with view'"
91
  )
92
 
93
  if search_query != st.session_state.search_query:
 
1
+ import re
2
  import streamlit as st
3
  from html import escape
4
  from streamlit_folium import st_folium, folium_static
 
11
  st.markdown(f'<style>{f.read()}</style>', unsafe_allow_html=True)
12
 
13
 
14
+ def highlight_search_terms(text, search_query):
15
+ """Highlight search terms in text"""
16
+ if not search_query:
17
+ return text
18
+
19
+ highlighted_text = text
20
+ search_terms = search_query.lower().split()
21
+
22
+ for term in search_terms:
23
+ if term.strip():
24
+ # Case-insensitive replacement with word boundaries
25
+ pattern = f'(?i)\\b{term}\\b'
26
+ replacement = f'<span class="highlight">{term}</span>'
27
+ highlighted_text = re.sub(pattern, replacement, highlighted_text)
28
+
29
+ return highlighted_text
30
+
31
+
32
  def render_review_dialog():
33
+ # Display reviews
34
+ with st.container():
35
+ col_title = st.columns([5, 1])
36
+ with col_title[0]:
37
+ st.markdown(f"### Reviews for {st.session_state.current_review_listing_name}")
38
+
39
+ reviews = st.session_state.visualizer.get_listing_reviews(st.session_state.current_review_listing)
40
+ if reviews:
41
+ for review in reviews:
42
+ try:
43
+ review_date, reviewer_name, comments = review
44
+
45
+ # Highlight search terms in comments if search query exists
46
+ highlighted_comments = highlight_search_terms(
47
+ str(comments),
48
+ st.session_state.search_query
49
+ )
50
+
51
+ st.markdown(f"""
52
+ <div class="review-card">
53
+ <div class="review-header">
54
+ {escape(str(reviewer_name))} - {escape(str(review_date))}
55
+ </div>
56
+ <div class="review-content">
57
+ {highlighted_comments}
58
+ </div>
59
+ </div>
60
+ """, unsafe_allow_html=True)
61
+ except Exception as e:
62
+ st.error(f"Error displaying review: {str(e)}")
63
+ else:
64
+ st.info("No reviews available for this listing.")
65
+
66
  def main():
67
  st.set_page_config(
68
  layout="wide",
69
+ page_title="HKUST BNB+ | Platform for BNB Matching for HKUST PG Student",
70
  initial_sidebar_state="expanded"
71
  )
72
  load_css('style.css')
 
110
 
111
  with st.sidebar:
112
  st.markdown(
113
+ '<p class="sidebar-header">HKUST BNB+<BR/></p>',
114
  unsafe_allow_html=True)
115
 
116
  search_query = st.text_input(
117
  "🔍 Search listings",
118
  value=st.session_state.search_query,
119
+ placeholder="Try: 'cozy , quiet '"
120
  )
121
 
122
  if search_query != st.session_state.search_query:
style.css CHANGED
@@ -411,3 +411,10 @@
411
  ::-webkit-scrollbar-thumb:hover {
412
  background: #555;
413
  }
 
 
 
 
 
 
 
 
411
  ::-webkit-scrollbar-thumb:hover {
412
  background: #555;
413
  }
414
+
415
+ .highlight {
416
+ background-color: #FFEB3B;
417
+ padding: 0 2px;
418
+ border-radius: 2px;
419
+ font-weight: bold;
420
+ }