-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathrecommendation.py
300 lines (253 loc) · 10.4 KB
/
recommendation.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
# recommendation.py
from geopy.distance import geodesic
import numpy as np
import pandas as pd
from datetime import datetime
from sqlalchemy.orm import Session
from database import get_db
from sqlalchemy import func
import json
from models import Monument, Event, MonumentEvent, DaySlot, MonumentSlot
def get_monuments_data(db: Session):
"""
Fetch all monuments from the database with their related information
"""
return db.query(Monument).all()
def get_events_data(db: Session):
"""
Fetch all events from the database
"""
return db.query(Event).all()
def monument_to_dict(monument):
"""Convert monument object to dictionary"""
return {
'id': monument.monument_id,
'name': monument.name,
'latitude': monument.latitude,
'longitude': monument.longitude,
'type': monument.type,
'popularity': monument.popularity,
'indoor': monument.indoor,
'description': monument.description,
'image_url': monument.image_url,
'location': monument.location
}
def get_monument_events(monument):
"""
Get event names associated with a monument using the relationship
"""
if hasattr(monument, 'monument_events') and monument.monument_events:
return [me.event.name for me in monument.monument_events]
return []
def get_monument_best_time(monument, db: Session):
"""
Get the best time to visit a monument based on its slots
"""
if hasattr(monument, 'slots') and monument.slots:
slot_names = [slot.slot.slot_name for slot in monument.slots]
# Find the most common time slot
if slot_names:
# print(max(set(slot_names), key=slot_names.count))
return max(set(slot_names), key=slot_names.count)
return None # Default if no slots defined
def create_dataframes(db: Session):
"""
Create DataFrames from database data for compatibility with existing code
"""
monuments = get_monuments_data(db)
events = get_events_data(db)
# Process monument data
monuments_data = []
for monument in monuments:
monument_dict = monument_to_dict(monument)
# Add events
monument_dict['events'] = get_monument_events(monument)
# Add best time if available from day slots
best_time = get_monument_best_time(monument, db)
if best_time:
monument_dict['best_time'] = best_time
else:
# Default based on type if no slot info
if monument.type in ['Hindu Temple', 'Buddhist Stupa']:
monument_dict['best_time'] = 'morning'
elif monument.type in ['Museum', 'Historical Monument']:
monument_dict['best_time'] = 'afternoon'
else:
monument_dict['best_time'] = 'afternoon'
# Determine best season (simplified - could be expanded based on your needs)
monument_dict['best_season'] = 'all'
monuments_data.append(monument_dict)
# Create DataFrames
df = pd.DataFrame(monuments_data)
# Process event data
events_data = []
for event in events:
event_dict = {
'name': event.name,
'start_date': event.start_date.strftime('%Y-%m-%d') if event.start_date else None,
'end_date': event.end_date.strftime('%Y-%m-%d') if event.end_date else None,
'related_type': event.related_type
}
events_data.append(event_dict)
df_events = pd.DataFrame(events_data)
return df, df_events
def norm_distance(curr_latitude, curr_longitude, df):
"""Calculate normalized distance from current location to monuments"""
curr_point = (curr_latitude, curr_longitude)
distance = np.zeros(len(df))
max_distance = -1
for i in range(len(df)):
point = (df['latitude'].iloc[i], df['longitude'].iloc[i])
distance[i] = geodesic(curr_point, point).km
if max_distance < distance[i]:
max_distance = distance[i]
# Handle edge case where all monuments are at the same location
if max_distance == 0:
return np.ones(len(df))
normalized_distance = (1 - distance/max_distance)
return normalized_distance
def type_match(type_of_monument, df):
"""Score monuments based on matching monument type"""
type_match_hotcoded = (type_of_monument == df['type']).astype(int)
return np.array(type_match_hotcoded)
def single_event_score(event, current_date):
"""Score an event based on how close or current it is"""
if not event.empty:
s_d = event['start_date'].iloc[0]
e_d = event['end_date'].iloc[0]
if s_d and e_d: # Check if dates are not None
try:
start_date = datetime.strptime(s_d, "%Y-%m-%d")
end_date = datetime.strptime(e_d, "%Y-%m-%d")
if current_date >= start_date and current_date <= end_date:
return 1
elif current_date < start_date:
if abs((current_date - start_date)).days <= 7:
return 0.5
if abs((current_date - start_date)).days <= 14:
return 0.2
return 0
else:
return 0
except (ValueError, TypeError):
return 0
return 0
def calculate_date_score(current_date, df, df_events):
"""Calculate score for monuments based on upcoming or current events"""
date_scores = np.zeros(len(df))
for i in range(len(df)):
max_score = 0
events = df['events'].iloc[i]
if events and isinstance(events, list): # Check if events list is not empty
for event in events:
event_data = df_events[df_events['name'] == event]
if not event_data.empty:
score = single_event_score(event_data, current_date)
if score > max_score:
max_score = score
date_scores[i] = max_score
return date_scores
def scores_time_of_day(current_hour, df):
"""Score monuments based on recommended visiting time"""
# If best_time column doesn't exist, return zeros
if 'best_time' not in df.columns:
return np.zeros(len(df))
# Initialize scores
time_scores = np.zeros(len(df))
# Morning: 6-12, Afternoon: 12-17, Evening: 17-21
if 6 <= current_hour < 12:
# Morning hours - higher score for morning attractions
for i in range(len(df)):
if df['best_time'].iloc[i] == 'morning':
time_scores[i] = 1.0
elif df['best_time'].iloc[i] == 'afternoon':
time_scores[i] = 0.5
else:
time_scores[i] = 0.2
elif 12 <= current_hour < 17:
# Afternoon hours - higher score for afternoon attractions
for i in range(len(df)):
if df['best_time'].iloc[i] == 'afternoon':
time_scores[i] = 1.0
elif df['best_time'].iloc[i] in ['morning', 'evening']:
time_scores[i] = 0.5
else:
time_scores[i] = 0.2
else:
# Evening hours - higher score for evening attractions
for i in range(len(df)):
if df['best_time'].iloc[i] == 'evening':
time_scores[i] = 1.0
elif df['best_time'].iloc[i] == 'afternoon':
time_scores[i] = 0.7
else:
time_scores[i] = 0.3
return time_scores
def final_weight_sum(user, df, df_events):
"""Calculate final recommendation weights for all monuments"""
# Calculate individual factors
distance = norm_distance(user['latitude'], user['longitude'], df)
type_matched = type_match(user['likes'], df)
popularity = np.array(df['popularity'])
seasonal_event = calculate_date_score(datetime.now(), df, df_events)
time_of_day = scores_time_of_day(datetime.now().hour, df)
# Combine factors with weights
total = 0.4 * distance + 0.2 * type_matched + 0.15 * popularity + 0.2 * seasonal_event + 0.05 * time_of_day
return total
def recommend_monuments(user_lat=27.7104, user_long=85.3487, preferred_type="Hindu Temple"):
"""
Main function to recommend monuments based on user preferences.
Returns a sorted list of monument objects.
Parameters:
- user_lat: float - user's latitude
- user_long: float - user's longitude
- preferred_type: str - type of monument the user prefers
Returns:
- List of monument objects sorted by recommendation score
"""
# User preferences
user = {
'latitude': user_lat,
'longitude': user_long,
'likes': preferred_type,
'current_time': datetime.now().hour,
'current_date': datetime.now()
}
# Get database session
db = next(get_db())
try:
# Create dataframes from database data
df, df_events = create_dataframes(db)
# Check if dataframes are empty
if df.empty:
return []
# Calculate weights and recommendations
final_weights = final_weight_sum(user, df, df_events)
# Create a list of monuments with their weights
monuments_with_weights = []
for i in range(len(df)):
monument = {
'id': int(df['id'].iloc[i]),
'name': df['name'].iloc[i],
'latitude': float(df['latitude'].iloc[i]),
'longitude': float(df['longitude'].iloc[i]),
'type': df['type'].iloc[i],
'popularity': float(df['popularity'].iloc[i]),
'indoor': bool(df['indoor'].iloc[i]),
'description': df['description'].iloc[i],
'image_url': df['image_url'].iloc[i],
'location': df['location'].iloc[i],
'weight': float(final_weights[i]) # Add the calculated weight
}
monuments_with_weights.append(monument)
# Sort monuments by weight in descending order
sorted_monuments = sorted(monuments_with_weights, key=lambda x: x['weight'], reverse=True)
# Remove the weight field from the final response
for monument in sorted_monuments:
del monument['weight']
return sorted_monuments
except Exception as e:
print(f"Error in recommendation: {str(e)}")
return []
finally:
db.close()