Spaces:
Sleeping
Sleeping
Gordon Li
commited on
Commit
·
af1d6b2
1
Parent(s):
4a995f4
Discount for near traffic spot
Browse files- AirbnbMapVisualiser.py +62 -14
- TrafficSpot.py +158 -13
- app.py +74 -1
- style.css +1 -1
AirbnbMapVisualiser.py
CHANGED
@@ -6,8 +6,10 @@ from sentence_transformers import SentenceTransformer, util
|
|
6 |
from geopy.distance import geodesic
|
7 |
import logging
|
8 |
|
|
|
9 |
from TrafficSpot import TrafficSpotManager
|
10 |
|
|
|
11 |
class AirbnbMapVisualiser:
|
12 |
def __init__(self):
|
13 |
self.connection_params = {
|
@@ -25,7 +27,7 @@ class AirbnbMapVisualiser:
|
|
25 |
getmode=oracledb.SPOOL_ATTRVAL_WAIT
|
26 |
)
|
27 |
|
28 |
-
# Initialize TrafficSpotManager
|
29 |
self.traffic_manager = TrafficSpotManager(self.connection_params)
|
30 |
logging.info(f"Traffic spots initialized, {len(self.traffic_manager.traffic_spots)} spots loaded")
|
31 |
|
@@ -298,6 +300,7 @@ class AirbnbMapVisualiser:
|
|
298 |
return [0.0] * len(df)
|
299 |
|
300 |
def sort_by_relevance(self, df, search_query):
|
|
|
301 |
if not search_query:
|
302 |
return df
|
303 |
|
@@ -365,7 +368,7 @@ class AirbnbMapVisualiser:
|
|
365 |
return df.sort_values('relevance_score', ascending=False)
|
366 |
|
367 |
def create_map_and_data(self, neighborhood="Sha Tin", show_traffic=True, center_lat=None, center_lng=None,
|
368 |
-
selected_id=None, search_query=None):
|
369 |
listings = self.get_neighborhood_listings(neighborhood)
|
370 |
|
371 |
if not listings:
|
@@ -400,26 +403,65 @@ class AirbnbMapVisualiser:
|
|
400 |
tiles='OpenStreetMap'
|
401 |
)
|
402 |
|
403 |
-
#
|
404 |
-
|
405 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
406 |
|
407 |
# Create a feature group for connection lines
|
408 |
lines_group = folium.FeatureGroup(name="Connection Lines")
|
409 |
m.add_child(lines_group)
|
410 |
|
411 |
-
#
|
|
|
|
|
|
|
|
|
412 |
for idx, row in df.iterrows():
|
413 |
marker_id = f"marker_{row['id']}"
|
414 |
reviews = self.get_listing_reviews(row['id'])
|
415 |
review_button_key = f"review_btn_{row['id']}"
|
416 |
|
417 |
-
#
|
418 |
-
nearest_spot, distance = self.find_nearest_traffic_spot(row['latitude'], row['longitude'])
|
419 |
-
|
420 |
-
# Add traffic spot information to popup if found
|
421 |
traffic_spot_info = ""
|
422 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
423 |
# Format distance for display (convert to meters if less than 1km)
|
424 |
distance_str = f"{distance:.2f} km" if distance >= 0.1 else f"{distance * 1000:.0f} meters"
|
425 |
|
@@ -433,7 +475,7 @@ class AirbnbMapVisualiser:
|
|
433 |
</div>
|
434 |
"""
|
435 |
|
436 |
-
# Add
|
437 |
folium.PolyLine(
|
438 |
locations=[
|
439 |
[row['latitude'], row['longitude']],
|
@@ -460,13 +502,19 @@ class AirbnbMapVisualiser:
|
|
460 |
</div>
|
461 |
"""
|
462 |
|
|
|
|
|
|
|
|
|
|
|
463 |
popup_content = f"""
|
464 |
<div style='min-width: 280px; max-width: 320px; padding: 15px;'>
|
465 |
<h4 style='margin: 0 0 10px 0; color: #2c3e50;'>{escape(str(row['name']))}</h4>
|
466 |
<p style='margin: 5px 0;'><strong>Host:</strong> {escape(str(row['host_name']))}</p>
|
467 |
<p style='margin: 5px 0;'><strong>Room Type:</strong> {escape(str(row['room_type']))}</p>
|
468 |
-
<p style='margin: 5px 0;'
|
469 |
<p style='margin: 5px 0;'><strong>Reviews:</strong> {row['number_of_reviews']:.0f}</p>
|
|
|
470 |
{traffic_spot_info}
|
471 |
{relevance_info}
|
472 |
</div>
|
@@ -506,7 +554,7 @@ class AirbnbMapVisualiser:
|
|
506 |
</script>
|
507 |
""").add_to(m)
|
508 |
|
509 |
-
# Add layer control to toggle
|
510 |
folium.LayerControl().add_to(m)
|
511 |
|
512 |
return m, df
|
|
|
6 |
from geopy.distance import geodesic
|
7 |
import logging
|
8 |
|
9 |
+
# Import the TrafficSpotManager from TrafficSpot module
|
10 |
from TrafficSpot import TrafficSpotManager
|
11 |
|
12 |
+
|
13 |
class AirbnbMapVisualiser:
|
14 |
def __init__(self):
|
15 |
self.connection_params = {
|
|
|
27 |
getmode=oracledb.SPOOL_ATTRVAL_WAIT
|
28 |
)
|
29 |
|
30 |
+
# Initialize TrafficSpotManager with minimal data
|
31 |
self.traffic_manager = TrafficSpotManager(self.connection_params)
|
32 |
logging.info(f"Traffic spots initialized, {len(self.traffic_manager.traffic_spots)} spots loaded")
|
33 |
|
|
|
300 |
return [0.0] * len(df)
|
301 |
|
302 |
def sort_by_relevance(self, df, search_query):
|
303 |
+
"""Sort listings by relevance using sentence transformer comparison"""
|
304 |
if not search_query:
|
305 |
return df
|
306 |
|
|
|
368 |
return df.sort_values('relevance_score', ascending=False)
|
369 |
|
370 |
def create_map_and_data(self, neighborhood="Sha Tin", show_traffic=True, center_lat=None, center_lng=None,
|
371 |
+
selected_id=None, search_query=None, current_page=1, items_per_page=3):
|
372 |
listings = self.get_neighborhood_listings(neighborhood)
|
373 |
|
374 |
if not listings:
|
|
|
403 |
tiles='OpenStreetMap'
|
404 |
)
|
405 |
|
406 |
+
# Calculate pagination indices
|
407 |
+
total_items = len(df)
|
408 |
+
start_idx = (current_page - 1) * items_per_page
|
409 |
+
end_idx = min(start_idx + items_per_page, total_items)
|
410 |
+
|
411 |
+
# Get the current page's listings
|
412 |
+
current_page_df = df.iloc[start_idx:end_idx]
|
413 |
+
|
414 |
+
# Create a list to store all traffic spots we need to display
|
415 |
+
all_traffic_spots_to_display = set()
|
416 |
+
|
417 |
+
# Find nearest traffic spots for ALL listings
|
418 |
+
all_nearest_traffic_spots = {}
|
419 |
+
|
420 |
+
# First find all nearest traffic spots
|
421 |
+
for idx, row in df.iterrows():
|
422 |
+
nearest_spot, distance = self.find_nearest_traffic_spot(row['latitude'], row['longitude'])
|
423 |
+
if nearest_spot:
|
424 |
+
all_nearest_traffic_spots[row['id']] = (nearest_spot, distance)
|
425 |
+
all_traffic_spots_to_display.add(nearest_spot.key)
|
426 |
|
427 |
# Create a feature group for connection lines
|
428 |
lines_group = folium.FeatureGroup(name="Connection Lines")
|
429 |
m.add_child(lines_group)
|
430 |
|
431 |
+
# Display all traffic spots
|
432 |
+
if show_traffic and all_traffic_spots_to_display:
|
433 |
+
self.traffic_manager.add_spots_to_map(m, all_traffic_spots_to_display)
|
434 |
+
|
435 |
+
# Add all Airbnb markers and connection lines
|
436 |
for idx, row in df.iterrows():
|
437 |
marker_id = f"marker_{row['id']}"
|
438 |
reviews = self.get_listing_reviews(row['id'])
|
439 |
review_button_key = f"review_btn_{row['id']}"
|
440 |
|
441 |
+
# Get traffic spot info if available for this listing
|
|
|
|
|
|
|
442 |
traffic_spot_info = ""
|
443 |
+
discount_info = ""
|
444 |
+
discounted_price = row['price']
|
445 |
+
|
446 |
+
# Check if this listing has a nearest traffic spot
|
447 |
+
if row['id'] in all_nearest_traffic_spots:
|
448 |
+
nearest_spot, distance = all_nearest_traffic_spots[row['id']]
|
449 |
+
|
450 |
+
# Get discount rate and apply to price
|
451 |
+
discount_rate = nearest_spot.get_discount_rate()
|
452 |
+
if discount_rate > 0:
|
453 |
+
discounted_price = row['price'] * (1 - discount_rate)
|
454 |
+
discount_percentage = int(discount_rate * 100)
|
455 |
+
|
456 |
+
# Format discount info
|
457 |
+
discount_info = f"""
|
458 |
+
<div style='background-color: #e8f5e9; padding: 8px; margin: 10px 0; border-radius: 4px; border-left: 4px solid #4caf50;'>
|
459 |
+
<p style='margin: 2px 0; font-weight: bold; color: #2e7d32;'>🎉 {discount_percentage}% ENV PROTECTION DISCOUNT!</p>
|
460 |
+
<p style='margin: 2px 0;'>Original: ${row['price']:.0f} → Now: ${discounted_price:.0f}</p>
|
461 |
+
<p style='margin: 2px 0; font-size: 0.85em;'>Avg. {nearest_spot.avg_vehicle_count:.1f} vehicles per observation</p>
|
462 |
+
</div>
|
463 |
+
"""
|
464 |
+
|
465 |
# Format distance for display (convert to meters if less than 1km)
|
466 |
distance_str = f"{distance:.2f} km" if distance >= 0.1 else f"{distance * 1000:.0f} meters"
|
467 |
|
|
|
475 |
</div>
|
476 |
"""
|
477 |
|
478 |
+
# Add connection lines for ALL listings with nearby traffic spots
|
479 |
folium.PolyLine(
|
480 |
locations=[
|
481 |
[row['latitude'], row['longitude']],
|
|
|
502 |
</div>
|
503 |
"""
|
504 |
|
505 |
+
# Show price with strikethrough if discounted
|
506 |
+
price_display = f"<strong>Price:</strong> ${row['price']:.0f}"
|
507 |
+
if discount_info:
|
508 |
+
price_display = f"<strong>Price:</strong> <span style='text-decoration: line-through;'>${row['price']:.0f}</span> <span style='color: #2e7d32; font-weight: bold;'>${discounted_price:.0f}</span>"
|
509 |
+
|
510 |
popup_content = f"""
|
511 |
<div style='min-width: 280px; max-width: 320px; padding: 15px;'>
|
512 |
<h4 style='margin: 0 0 10px 0; color: #2c3e50;'>{escape(str(row['name']))}</h4>
|
513 |
<p style='margin: 5px 0;'><strong>Host:</strong> {escape(str(row['host_name']))}</p>
|
514 |
<p style='margin: 5px 0;'><strong>Room Type:</strong> {escape(str(row['room_type']))}</p>
|
515 |
+
<p style='margin: 5px 0;'>{price_display}</p>
|
516 |
<p style='margin: 5px 0;'><strong>Reviews:</strong> {row['number_of_reviews']:.0f}</p>
|
517 |
+
{discount_info}
|
518 |
{traffic_spot_info}
|
519 |
{relevance_info}
|
520 |
</div>
|
|
|
554 |
</script>
|
555 |
""").add_to(m)
|
556 |
|
557 |
+
# Add layer control to toggle connection lines
|
558 |
folium.LayerControl().add_to(m)
|
559 |
|
560 |
return m, df
|
TrafficSpot.py
CHANGED
@@ -2,8 +2,10 @@ import folium
|
|
2 |
import oracledb
|
3 |
import logging
|
4 |
import base64
|
|
|
5 |
from html import escape
|
6 |
from datasets import load_dataset
|
|
|
7 |
|
8 |
|
9 |
class TrafficSpot:
|
@@ -12,14 +14,60 @@ class TrafficSpot:
|
|
12 |
self.latitude = float(latitude) if latitude is not None else None
|
13 |
self.longitude = float(longitude) if longitude is not None else None
|
14 |
self.dataset_rows = dataset_rows or [] # List of matching dataset rows (up to 5)
|
|
|
15 |
|
16 |
def is_valid(self):
|
17 |
return self.latitude is not None and self.longitude is not None
|
18 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
19 |
def create_popup_content(self):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
20 |
html = f"""
|
21 |
<div style='min-width: 150px; padding: 10px;'>
|
22 |
<p style='margin: 5px 0;'><strong>Location ID:</strong> {escape(str(self.key))}</p>
|
|
|
23 |
"""
|
24 |
|
25 |
if self.dataset_rows:
|
@@ -58,10 +106,18 @@ class TrafficSpot:
|
|
58 |
|
59 |
def add_to_map(self, folium_map):
|
60 |
if self.is_valid():
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
61 |
folium.Marker(
|
62 |
location=[self.latitude, self.longitude],
|
63 |
popup=self.create_popup_content(),
|
64 |
-
icon=folium.Icon(color=
|
65 |
).add_to(folium_map)
|
66 |
|
67 |
|
@@ -69,20 +125,30 @@ class TrafficSpotManager:
|
|
69 |
def __init__(self, connection_params):
|
70 |
self.connection_params = connection_params
|
71 |
self.traffic_spots = []
|
72 |
-
self.
|
|
|
|
|
73 |
|
74 |
-
def
|
|
|
75 |
try:
|
76 |
dataset = load_dataset("slliac/traffic-analysis", split="train")
|
77 |
dataset_list = [row for row in dataset]
|
78 |
dataset_list.sort(key=lambda x: x['capture_time'], reverse=True)
|
79 |
|
|
|
80 |
dataset_dict = {}
|
|
|
81 |
for row in dataset_list:
|
82 |
loc_id = row['location_id']
|
|
|
|
|
|
|
83 |
if loc_id not in dataset_dict:
|
84 |
dataset_dict[loc_id] = []
|
85 |
-
|
|
|
|
|
86 |
dataset_dict[loc_id].append(row)
|
87 |
|
88 |
unique_locations = list(dataset_dict.keys())
|
@@ -111,20 +177,99 @@ class TrafficSpotManager:
|
|
111 |
for spot in spots
|
112 |
]
|
113 |
|
|
|
|
|
|
|
|
|
114 |
conn.commit()
|
115 |
-
logging.info(f"Loaded {len(self.traffic_spots)} traffic spots")
|
116 |
|
117 |
except Exception as e:
|
118 |
logging.error(f"Error loading traffic spots: {str(e)}")
|
119 |
self.traffic_spots = []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
120 |
|
121 |
-
|
122 |
-
|
123 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
124 |
|
125 |
def get_spot_by_key(self, key):
|
126 |
-
"""Get a traffic spot by its key"""
|
127 |
-
|
128 |
-
|
129 |
-
|
130 |
-
|
|
|
|
|
|
|
|
|
|
2 |
import oracledb
|
3 |
import logging
|
4 |
import base64
|
5 |
+
import numpy as np
|
6 |
from html import escape
|
7 |
from datasets import load_dataset
|
8 |
+
from datetime import datetime, timedelta
|
9 |
|
10 |
|
11 |
class TrafficSpot:
|
|
|
14 |
self.latitude = float(latitude) if latitude is not None else None
|
15 |
self.longitude = float(longitude) if longitude is not None else None
|
16 |
self.dataset_rows = dataset_rows or [] # List of matching dataset rows (up to 5)
|
17 |
+
self.avg_vehicle_count = self.calculate_avg_vehicle_count()
|
18 |
|
19 |
def is_valid(self):
|
20 |
return self.latitude is not None and self.longitude is not None
|
21 |
|
22 |
+
def calculate_avg_vehicle_count(self):
|
23 |
+
"""Calculate average vehicle count from the recent data"""
|
24 |
+
if not self.dataset_rows:
|
25 |
+
return 0
|
26 |
+
|
27 |
+
# Extract vehicle counts from dataset rows
|
28 |
+
vehicle_counts = [row.get('vehicle_count', 0) for row in self.dataset_rows if 'vehicle_count' in row]
|
29 |
+
|
30 |
+
# If no valid counts are found, return 0
|
31 |
+
if not vehicle_counts:
|
32 |
+
return 0
|
33 |
+
|
34 |
+
# Calculate and return the average
|
35 |
+
return np.mean(vehicle_counts)
|
36 |
+
|
37 |
+
def get_discount_rate(self):
|
38 |
+
"""Calculate discount rate based on average vehicle count"""
|
39 |
+
if self.avg_vehicle_count < 2:
|
40 |
+
return 0.20 # 20% discount
|
41 |
+
elif self.avg_vehicle_count < 5:
|
42 |
+
return 0.10 # 10% discount
|
43 |
+
else:
|
44 |
+
return 0.0 # No discount
|
45 |
+
|
46 |
+
def get_discount_info(self):
|
47 |
+
"""Get discount information as a formatted string"""
|
48 |
+
discount_rate = self.get_discount_rate()
|
49 |
+
|
50 |
+
if discount_rate <= 0:
|
51 |
+
return "No traffic discount available"
|
52 |
+
|
53 |
+
return f"{int(discount_rate * 100)}% discount! Low traffic area"
|
54 |
+
|
55 |
def create_popup_content(self):
|
56 |
+
discount_info = self.get_discount_info()
|
57 |
+
discount_display = ""
|
58 |
+
|
59 |
+
if "discount" in discount_info.lower() and "no" not in discount_info.lower():
|
60 |
+
discount_display = f"""
|
61 |
+
<div style='background-color: #e8f5e9; padding: 5px; margin: 5px 0; border-radius: 4px; border-left: 3px solid #4caf50;'>
|
62 |
+
<p style='margin: 2px 0; color: #2e7d32;'><strong>🎉 {discount_info}</strong></p>
|
63 |
+
<p style='margin: 2px 0; font-size: 0.9em;'>Avg. {self.avg_vehicle_count:.1f} vehicles per observation</p>
|
64 |
+
</div>
|
65 |
+
"""
|
66 |
+
|
67 |
html = f"""
|
68 |
<div style='min-width: 150px; padding: 10px;'>
|
69 |
<p style='margin: 5px 0;'><strong>Location ID:</strong> {escape(str(self.key))}</p>
|
70 |
+
{discount_display}
|
71 |
"""
|
72 |
|
73 |
if self.dataset_rows:
|
|
|
106 |
|
107 |
def add_to_map(self, folium_map):
|
108 |
if self.is_valid():
|
109 |
+
# Choose color based on traffic level
|
110 |
+
if self.avg_vehicle_count < 2:
|
111 |
+
color = 'blue' # Low traffic - 20% discount
|
112 |
+
elif self.avg_vehicle_count < 5:
|
113 |
+
color = 'orange' # Medium traffic - 10% discount
|
114 |
+
else:
|
115 |
+
color = 'purple' # High traffic - no discount
|
116 |
+
|
117 |
folium.Marker(
|
118 |
location=[self.latitude, self.longitude],
|
119 |
popup=self.create_popup_content(),
|
120 |
+
icon=folium.Icon(color=color, icon='camera'),
|
121 |
).add_to(folium_map)
|
122 |
|
123 |
|
|
|
125 |
def __init__(self, connection_params):
|
126 |
self.connection_params = connection_params
|
127 |
self.traffic_spots = []
|
128 |
+
self.spot_dict = {} # For quick lookup by key
|
129 |
+
# Only load limited spots when initialized
|
130 |
+
self.load_limited_traffic_spots()
|
131 |
|
132 |
+
def load_limited_traffic_spots(self, limit=10):
|
133 |
+
"""Load only a very limited set of traffic spots initially"""
|
134 |
try:
|
135 |
dataset = load_dataset("slliac/traffic-analysis", split="train")
|
136 |
dataset_list = [row for row in dataset]
|
137 |
dataset_list.sort(key=lambda x: x['capture_time'], reverse=True)
|
138 |
|
139 |
+
# Limit to just a few samples
|
140 |
dataset_dict = {}
|
141 |
+
unique_count = 0
|
142 |
for row in dataset_list:
|
143 |
loc_id = row['location_id']
|
144 |
+
if unique_count >= limit:
|
145 |
+
break
|
146 |
+
|
147 |
if loc_id not in dataset_dict:
|
148 |
dataset_dict[loc_id] = []
|
149 |
+
unique_count += 1
|
150 |
+
|
151 |
+
if len(dataset_dict[loc_id]) < 10: # Store up to 10 records for averaging
|
152 |
dataset_dict[loc_id].append(row)
|
153 |
|
154 |
unique_locations = list(dataset_dict.keys())
|
|
|
177 |
for spot in spots
|
178 |
]
|
179 |
|
180 |
+
# Build lookup dictionary
|
181 |
+
for spot in self.traffic_spots:
|
182 |
+
self.spot_dict[spot.key] = spot
|
183 |
+
|
184 |
conn.commit()
|
185 |
+
logging.info(f"Loaded {len(self.traffic_spots)} limited traffic spots")
|
186 |
|
187 |
except Exception as e:
|
188 |
logging.error(f"Error loading traffic spots: {str(e)}")
|
189 |
self.traffic_spots = []
|
190 |
+
self.spot_dict = {}
|
191 |
+
|
192 |
+
def load_specific_traffic_spots(self, keys):
|
193 |
+
"""Load specific traffic spots by their keys"""
|
194 |
+
# Filter out keys we already have
|
195 |
+
needed_keys = [key for key in keys if key not in self.spot_dict]
|
196 |
+
|
197 |
+
if not needed_keys:
|
198 |
+
return
|
199 |
+
|
200 |
+
try:
|
201 |
+
dataset = load_dataset("slliac/traffic-analysis", split="train")
|
202 |
+
dataset_list = [row for row in dataset]
|
203 |
+
dataset_list.sort(key=lambda x: x['capture_time'], reverse=True)
|
204 |
+
|
205 |
+
dataset_dict = {}
|
206 |
+
for row in dataset_list:
|
207 |
+
loc_id = row['location_id']
|
208 |
+
if loc_id in needed_keys:
|
209 |
+
if loc_id not in dataset_dict:
|
210 |
+
dataset_dict[loc_id] = []
|
211 |
+
if len(dataset_dict[loc_id]) < 10: # Store up to 10 records for averaging
|
212 |
+
dataset_dict[loc_id].append(row)
|
213 |
+
|
214 |
+
# Only load if we have keys to load
|
215 |
+
if needed_keys:
|
216 |
+
with oracledb.connect(**self.connection_params) as conn:
|
217 |
+
cursor = conn.cursor()
|
218 |
+
|
219 |
+
# Prepare placeholders for the IN clause
|
220 |
+
placeholders = ','.join([':' + str(i + 1) for i in range(len(needed_keys))])
|
221 |
+
|
222 |
+
query = f"""
|
223 |
+
SELECT KEY, LATITUDE, LONGITUDE
|
224 |
+
FROM TD_TRAFFIC_CAMERA_LOCATION
|
225 |
+
WHERE KEY IN ({placeholders})
|
226 |
+
AND LATITUDE IS NOT NULL
|
227 |
+
AND LONGITUDE IS NOT NULL
|
228 |
+
"""
|
229 |
|
230 |
+
cursor.execute(query, tuple(needed_keys))
|
231 |
+
spots = cursor.fetchall()
|
232 |
+
|
233 |
+
new_spots = [
|
234 |
+
TrafficSpot(
|
235 |
+
spot[0],
|
236 |
+
spot[1],
|
237 |
+
spot[2],
|
238 |
+
dataset_dict.get(spot[0])
|
239 |
+
)
|
240 |
+
for spot in spots
|
241 |
+
]
|
242 |
+
|
243 |
+
# Add to our collections
|
244 |
+
for spot in new_spots:
|
245 |
+
self.spot_dict[spot.key] = spot
|
246 |
+
self.traffic_spots.append(spot)
|
247 |
+
|
248 |
+
conn.commit()
|
249 |
+
logging.info(f"Loaded {len(new_spots)} additional traffic spots")
|
250 |
+
|
251 |
+
except Exception as e:
|
252 |
+
logging.error(f"Error loading specific traffic spots: {str(e)}")
|
253 |
+
|
254 |
+
def add_spots_to_map(self, folium_map, spot_keys=None):
|
255 |
+
"""Add only specific spots to map"""
|
256 |
+
if spot_keys is None:
|
257 |
+
# If no keys specified, add all loaded spots
|
258 |
+
for spot in self.traffic_spots:
|
259 |
+
spot.add_to_map(folium_map)
|
260 |
+
else:
|
261 |
+
# Add only the specified spots
|
262 |
+
for key in spot_keys:
|
263 |
+
if key in self.spot_dict:
|
264 |
+
self.spot_dict[key].add_to_map(folium_map)
|
265 |
|
266 |
def get_spot_by_key(self, key):
|
267 |
+
"""Get a traffic spot by its key, loading it if necessary"""
|
268 |
+
if key in self.spot_dict:
|
269 |
+
return self.spot_dict[key]
|
270 |
+
|
271 |
+
# Try to load it if we don't have it
|
272 |
+
self.load_specific_traffic_spots([key])
|
273 |
+
|
274 |
+
# Return if found, None otherwise
|
275 |
+
return self.spot_dict.get(key)
|
app.py
CHANGED
@@ -63,6 +63,7 @@ def render_review_dialog():
|
|
63 |
else:
|
64 |
st.info("No reviews available for this listing.")
|
65 |
|
|
|
66 |
def main():
|
67 |
st.set_page_config(
|
68 |
layout="wide",
|
@@ -94,6 +95,10 @@ def main():
|
|
94 |
st.session_state.current_review_listing = None
|
95 |
if 'current_review_listing_name' not in st.session_state:
|
96 |
st.session_state.current_review_listing_name = None
|
|
|
|
|
|
|
|
|
97 |
|
98 |
# Initialize visualizer with loading message for tokenizer
|
99 |
if 'visualizer' not in st.session_state:
|
@@ -108,6 +113,57 @@ def main():
|
|
108 |
st.error("Error initializing the application. Please refresh the page.")
|
109 |
return
|
110 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
111 |
with st.sidebar:
|
112 |
st.markdown(
|
113 |
'<p class="sidebar-header">HKUST BNB+<BR/></p>',
|
@@ -133,6 +189,22 @@ def main():
|
|
133 |
)
|
134 |
show_traffic = st.checkbox("Show Traffic Cameras", value=True)
|
135 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
136 |
if st.button("Reset All", key="reset_btn"):
|
137 |
st.session_state.center_lat = None
|
138 |
st.session_state.center_lng = None
|
@@ -140,6 +212,8 @@ def main():
|
|
140 |
st.session_state.current_page = 1
|
141 |
st.session_state.search_query = ""
|
142 |
st.session_state.show_review_dialog = False
|
|
|
|
|
143 |
st.rerun()
|
144 |
|
145 |
# Create map and get data - pass current page information
|
@@ -221,7 +295,6 @@ def main():
|
|
221 |
st.session_state.scroll_to_review = True
|
222 |
st.rerun()
|
223 |
|
224 |
-
|
225 |
st.markdown('</div>', unsafe_allow_html=True)
|
226 |
|
227 |
# Pagination controls
|
|
|
63 |
else:
|
64 |
st.info("No reviews available for this listing.")
|
65 |
|
66 |
+
|
67 |
def main():
|
68 |
st.set_page_config(
|
69 |
layout="wide",
|
|
|
95 |
st.session_state.current_review_listing = None
|
96 |
if 'current_review_listing_name' not in st.session_state:
|
97 |
st.session_state.current_review_listing_name = None
|
98 |
+
if 'show_traffic_explanation' not in st.session_state:
|
99 |
+
st.session_state.show_traffic_explanation = False
|
100 |
+
if 'show_search_explanation' not in st.session_state:
|
101 |
+
st.session_state.show_search_explanation = False
|
102 |
|
103 |
# Initialize visualizer with loading message for tokenizer
|
104 |
if 'visualizer' not in st.session_state:
|
|
|
113 |
st.error("Error initializing the application. Please refresh the page.")
|
114 |
return
|
115 |
|
116 |
+
# Show explanations if requested
|
117 |
+
if st.session_state.show_traffic_explanation:
|
118 |
+
with st.expander("📊 Traffic-Based Discount System", expanded=True):
|
119 |
+
st.markdown("""
|
120 |
+
### How HKUST BNB+ Acheived (E)SG , use Traffic Spot from Department of Transport and do traffic analysis hence provided discount according
|
121 |
+
to the average traffic on the previous days.
|
122 |
+
|
123 |
+
We use real-time traffic data to offer you the best possible rates:
|
124 |
+
|
125 |
+
* **Blue Camera Icons**: Areas with very low traffic (less than 2 vehicles detected)
|
126 |
+
* Enjoy a peaceful stay with **20% DISCOUNT** on these properties!
|
127 |
+
|
128 |
+
* **Orange Camera Icons**: Areas with moderate traffic (2-5 vehicles detected)
|
129 |
+
* Get a **10% DISCOUNT** on these properties!
|
130 |
+
|
131 |
+
* **Purple Camera Icons**: Areas with heavier traffic (more than 5 vehicles)
|
132 |
+
* Standard rates apply for these properties
|
133 |
+
|
134 |
+
Look for the blue connecting lines on the map to see which traffic spot affects each property!
|
135 |
+
|
136 |
+
Remark : Currently only few traffic spot avaliable, in the future will provide more.
|
137 |
+
""")
|
138 |
+
if st.button("Close", key="close_traffic_btn"):
|
139 |
+
st.session_state.show_traffic_explanation = False
|
140 |
+
st.rerun()
|
141 |
+
|
142 |
+
if st.session_state.show_search_explanation:
|
143 |
+
with st.expander("🔍 Smart Search System", expanded=True):
|
144 |
+
st.markdown("""
|
145 |
+
### How HKUST BNB+ Acheived E(S)G , use keyword to provided semantic relevance analysis to matches the require need from HKUST Student
|
146 |
+
|
147 |
+
Our advanced search technology goes beyond simple keyword matching to understand the meaning behind your search terms:
|
148 |
+
|
149 |
+
When you search for terms like "quiet," "convenient," or "spacious," our system:
|
150 |
+
1. Analyzes both listing titles and actual guest reviews
|
151 |
+
2. Understands the context and meaning (not just matching exact words)
|
152 |
+
3. Ranks listings based on overall relevance to your search
|
153 |
+
|
154 |
+
**Search Match Types:**
|
155 |
+
* **"Strong match in title and reviews"** - Perfect matches in both property description and guest experiences
|
156 |
+
* **"Strong match in listing title"** - Property description matches your needs very well
|
157 |
+
* **"Strong match in reviews"** - Guest experiences align perfectly with what you're looking for
|
158 |
+
* **"Better match in listing title/reviews"** - One source is more relevant than the other
|
159 |
+
* **"Moderate semantic match"** - Some relevance but not a perfect match
|
160 |
+
|
161 |
+
This helps you find properties that truly match what you're looking for, even if they don't use the exact words in your search!
|
162 |
+
""")
|
163 |
+
if st.button("Close", key="close_search_btn"):
|
164 |
+
st.session_state.show_search_explanation = False
|
165 |
+
st.rerun()
|
166 |
+
|
167 |
with st.sidebar:
|
168 |
st.markdown(
|
169 |
'<p class="sidebar-header">HKUST BNB+<BR/></p>',
|
|
|
189 |
)
|
190 |
show_traffic = st.checkbox("Show Traffic Cameras", value=True)
|
191 |
|
192 |
+
st.markdown('<hr style="margin: 20px 0; border: none; border-top: 1px solid #e0e0e0;">', unsafe_allow_html=True)
|
193 |
+
|
194 |
+
# Help section in sidebar
|
195 |
+
st.markdown("### 💡 Help & Information")
|
196 |
+
|
197 |
+
col1, col2 = st.columns(2)
|
198 |
+
with col1:
|
199 |
+
if st.button("Green Discount", key="traffic_info_btn"):
|
200 |
+
st.session_state.show_traffic_explanation = True
|
201 |
+
st.rerun()
|
202 |
+
|
203 |
+
with col2:
|
204 |
+
if st.button("Semantic Search", key="search_info_btn"):
|
205 |
+
st.session_state.show_search_explanation = True
|
206 |
+
st.rerun()
|
207 |
+
|
208 |
if st.button("Reset All", key="reset_btn"):
|
209 |
st.session_state.center_lat = None
|
210 |
st.session_state.center_lng = None
|
|
|
212 |
st.session_state.current_page = 1
|
213 |
st.session_state.search_query = ""
|
214 |
st.session_state.show_review_dialog = False
|
215 |
+
st.session_state.show_traffic_explanation = False
|
216 |
+
st.session_state.show_search_explanation = False
|
217 |
st.rerun()
|
218 |
|
219 |
# Create map and get data - pass current page information
|
|
|
295 |
st.session_state.scroll_to_review = True
|
296 |
st.rerun()
|
297 |
|
|
|
298 |
st.markdown('</div>', unsafe_allow_html=True)
|
299 |
|
300 |
# Pagination controls
|
style.css
CHANGED
@@ -417,4 +417,4 @@
|
|
417 |
padding: 0 2px;
|
418 |
border-radius: 2px;
|
419 |
font-weight: bold;
|
420 |
-
}
|
|
|
417 |
padding: 0 2px;
|
418 |
border-radius: 2px;
|
419 |
font-weight: bold;
|
420 |
+
}
|