2023-05-21 13:37:21 +03:00
|
|
|
from annoy import AnnoyIndex
|
|
|
|
from .mapping.mapping import *
|
|
|
|
from .models.models import *
|
2023-05-23 17:51:01 +03:00
|
|
|
from passfinder.events.models import Event, Region, Hotel, BasePoint, City
|
|
|
|
from passfinder.recomendations.models import UserPreferences, NearestEvent, NearestHotel
|
2023-05-21 17:13:16 +03:00
|
|
|
from random import choice
|
|
|
|
from collections import Counter
|
2023-05-22 22:37:54 +03:00
|
|
|
from passfinder.users.models import User
|
|
|
|
from collections.abc import Iterable
|
2023-05-23 17:51:01 +03:00
|
|
|
from django.db.models import Q
|
|
|
|
from geopy.distance import geodesic as GD
|
|
|
|
from datetime import timedelta, time, datetime
|
2023-05-21 13:37:21 +03:00
|
|
|
|
|
|
|
|
2023-05-23 17:51:01 +03:00
|
|
|
def get_nearest_(instance_model, model_type, mapping, rev_mapping, nearest_n, ml_model):
|
2023-05-21 13:37:21 +03:00
|
|
|
how_many = len(Event.objects.filter(type=model_type))
|
|
|
|
|
2023-05-23 17:51:01 +03:00
|
|
|
index = rev_mapping[instance_model.oid]
|
2023-05-21 13:37:21 +03:00
|
|
|
nearest = ml_model.get_nns_by_item(index, len(mapping))
|
|
|
|
|
|
|
|
res = []
|
|
|
|
for i in range(how_many):
|
|
|
|
try:
|
|
|
|
res.append(Event.objects.get(oid=mapping[nearest[i]]))
|
2023-05-23 23:36:45 +03:00
|
|
|
except Event.DoesNotExist:
|
|
|
|
...
|
|
|
|
if len(res) == nearest_n:
|
|
|
|
break
|
2023-05-21 13:37:21 +03:00
|
|
|
return res
|
|
|
|
|
|
|
|
|
|
|
|
def nearest_attraction(attraction, nearest_n):
|
2023-05-23 23:36:45 +03:00
|
|
|
return get_nearest_(
|
|
|
|
attraction,
|
|
|
|
"attraction",
|
|
|
|
attraction_mapping,
|
|
|
|
rev_attraction_mapping,
|
|
|
|
nearest_n,
|
|
|
|
attracion_model,
|
|
|
|
)
|
2023-05-21 13:37:21 +03:00
|
|
|
|
|
|
|
|
|
|
|
def nearest_movie(movie, nearest_n):
|
2023-05-23 23:36:45 +03:00
|
|
|
return get_nearest_(
|
|
|
|
movie, "movie", cinema_mapping, rev_cinema_mapping, nearest_n, cinema_model
|
|
|
|
)
|
2023-05-21 13:37:21 +03:00
|
|
|
|
|
|
|
|
|
|
|
def nearest_plays(play, nearest_n):
|
2023-05-23 23:36:45 +03:00
|
|
|
return get_nearest_(
|
|
|
|
play, "plays", plays_mapping, rev_plays_mapping, nearest_n, plays_model
|
|
|
|
)
|
2023-05-21 13:37:21 +03:00
|
|
|
|
|
|
|
|
|
|
|
def nearest_excursion(excursion, nearest_n):
|
2023-05-23 23:36:45 +03:00
|
|
|
return get_nearest_(
|
|
|
|
excursion,
|
|
|
|
"excursion",
|
|
|
|
excursion_mapping,
|
|
|
|
rev_excursion_mapping,
|
|
|
|
nearest_n,
|
|
|
|
excursion_model,
|
|
|
|
)
|
2023-05-21 13:37:21 +03:00
|
|
|
|
|
|
|
|
|
|
|
def nearest_concert(concert, nearest_n):
|
2023-05-23 23:36:45 +03:00
|
|
|
return get_nearest_(
|
|
|
|
concert,
|
|
|
|
"concert",
|
|
|
|
concert_mapping,
|
|
|
|
rev_concert_mapping,
|
|
|
|
nearest_n,
|
|
|
|
concert_model,
|
|
|
|
)
|
2023-05-21 17:13:16 +03:00
|
|
|
|
|
|
|
|
|
|
|
def get_nearest_event(event, nearest_n):
|
2023-05-23 23:36:45 +03:00
|
|
|
if event.type == "plays":
|
2023-05-21 17:13:16 +03:00
|
|
|
return nearest_plays(event, nearest_n)
|
2023-05-23 23:36:45 +03:00
|
|
|
if event.type == "concert":
|
2023-05-21 17:13:16 +03:00
|
|
|
return nearest_concert(event, nearest_n)
|
2023-05-23 23:36:45 +03:00
|
|
|
if event.type == "movie":
|
2023-05-21 17:13:16 +03:00
|
|
|
return nearest_movie(event, nearest_n)
|
|
|
|
|
|
|
|
|
|
|
|
def update_preferences_state(user, event, direction):
|
|
|
|
pref = UserPreferences.objects.get(user=user)
|
2023-05-23 23:36:45 +03:00
|
|
|
|
|
|
|
if direction == "left":
|
|
|
|
if event.type == "plays":
|
2023-05-21 17:13:16 +03:00
|
|
|
pref.unpreffered_plays.add(event)
|
2023-05-23 23:36:45 +03:00
|
|
|
if event.type == "movie":
|
2023-05-21 17:13:16 +03:00
|
|
|
pref.unpreffered_movies.add(event)
|
2023-05-23 23:36:45 +03:00
|
|
|
if event.type == "concert":
|
2023-05-21 17:13:16 +03:00
|
|
|
pref.unpreferred_concerts.add(event)
|
|
|
|
else:
|
2023-05-23 23:36:45 +03:00
|
|
|
if event.type == "plays":
|
2023-05-21 17:13:16 +03:00
|
|
|
pref.preffered_plays.add(event)
|
2023-05-23 23:36:45 +03:00
|
|
|
if event.type == "movie":
|
2023-05-21 17:13:16 +03:00
|
|
|
pref.preffered_movies.add(event)
|
2023-05-23 23:36:45 +03:00
|
|
|
if event.type == "concert":
|
2023-05-21 17:13:16 +03:00
|
|
|
pref.preferred_concerts.add(event)
|
|
|
|
pref.save()
|
|
|
|
|
|
|
|
|
|
|
|
def get_next_tinder(user, prev_event, prev_direction):
|
|
|
|
pref = UserPreferences.objects.get(user=user)
|
|
|
|
print(prev_event.type, len(pref.preferred_concerts.all()))
|
2023-05-23 23:36:45 +03:00
|
|
|
if prev_direction == "left":
|
|
|
|
if prev_event.type == "plays" and len(pref.unpreffered_plays.all()) <= 2:
|
2023-05-21 17:13:16 +03:00
|
|
|
candidates = nearest_plays(prev_event, 100)
|
|
|
|
# print(candidates, type(candidates), len(Event.objects.filter(type='plays')))
|
|
|
|
return candidates[-1]
|
2023-05-23 23:36:45 +03:00
|
|
|
if prev_event.type == "movie" and len(pref.unpreffered_movies.all()) <= 2:
|
2023-05-21 17:13:16 +03:00
|
|
|
candidates = nearest_movie(prev_event, 100)
|
|
|
|
return candidates[-1]
|
2023-05-23 23:36:45 +03:00
|
|
|
if prev_event.type == "concert" and len(pref.unpreferred_concerts.all()) <= 2:
|
2023-05-21 17:13:16 +03:00
|
|
|
candidates = nearest_concert(prev_event, 100)
|
|
|
|
return candidates[-1]
|
2023-05-23 23:36:45 +03:00
|
|
|
|
|
|
|
if prev_direction == "right":
|
|
|
|
if prev_event.type == "plays" and len(pref.preffered_plays.all()) < 2:
|
2023-05-21 17:13:16 +03:00
|
|
|
candidates = nearest_plays(prev_event, 2)
|
|
|
|
return candidates[1]
|
2023-05-23 23:36:45 +03:00
|
|
|
if prev_event.type == "movie" and len(pref.preffered_movies.all()) < 2:
|
2023-05-21 17:13:16 +03:00
|
|
|
candidates = nearest_movie(prev_event, 2)
|
|
|
|
return candidates[1]
|
2023-05-23 23:36:45 +03:00
|
|
|
if prev_event.type == "concert" and len(pref.preferred_concerts.all()) < 2:
|
2023-05-21 17:13:16 +03:00
|
|
|
candidates = nearest_concert(prev_event, 2)
|
|
|
|
return candidates[1]
|
|
|
|
|
2023-05-23 23:36:45 +03:00
|
|
|
if prev_event.type == "plays":
|
|
|
|
if not len(pref.preffered_movies.all()) and not len(
|
|
|
|
pref.unpreffered_movies.all()
|
|
|
|
):
|
|
|
|
return choice(Event.objects.filter(type="movie"))
|
|
|
|
if not len(pref.preferred_concerts.all()) and not len(
|
|
|
|
pref.unpreferred_concerts.all()
|
|
|
|
):
|
|
|
|
return choice(Event.objects.filter(type="concert"))
|
|
|
|
|
|
|
|
if prev_event.type == "movie":
|
|
|
|
if not len(pref.preffered_plays.all()) and not len(
|
|
|
|
pref.unpreffered_plays.all()
|
|
|
|
):
|
|
|
|
return choice(Event.objects.filter(type="plays"))
|
|
|
|
if not len(pref.preferred_concerts.all()) and not len(
|
|
|
|
pref.unpreferred_concerts.all()
|
|
|
|
):
|
|
|
|
return choice(Event.objects.filter(type="concert"))
|
|
|
|
|
|
|
|
if prev_event.type == "concert":
|
|
|
|
if not len(pref.preffered_plays.all()) and not len(
|
|
|
|
pref.unpreffered_plays.all()
|
|
|
|
):
|
|
|
|
return choice(Event.objects.filter(type="plays"))
|
|
|
|
if not len(pref.preffered_movies.all()) and not len(
|
|
|
|
pref.unpreffered_movies.all()
|
|
|
|
):
|
|
|
|
return choice(Event.objects.filter(type="movie"))
|
2023-05-21 17:13:16 +03:00
|
|
|
|
2023-05-23 23:36:45 +03:00
|
|
|
return None
|
2023-05-21 17:13:16 +03:00
|
|
|
|
|
|
|
|
|
|
|
def rank_candidates(candidates_list, negative_candidates_list):
|
|
|
|
flatten_c_list = []
|
|
|
|
ranks = {}
|
|
|
|
|
|
|
|
flatten_negatives = []
|
|
|
|
|
|
|
|
for negative in negative_candidates_list:
|
|
|
|
flatten_negatives.extend(negative)
|
2023-05-23 23:36:45 +03:00
|
|
|
|
2023-05-21 17:13:16 +03:00
|
|
|
for lst in candidates_list:
|
|
|
|
flatten_c_list.extend(lst)
|
|
|
|
for cand in lst:
|
2023-05-23 23:36:45 +03:00
|
|
|
ranks.update({cand: {"rank": 0, "lst": lst}})
|
|
|
|
|
2023-05-21 17:13:16 +03:00
|
|
|
cnt = Counter(flatten_c_list)
|
|
|
|
|
|
|
|
for candidate, how_many in cnt.most_common(len(flatten_c_list)):
|
2023-05-23 23:36:45 +03:00
|
|
|
ranks[candidate]["rank"] = how_many * (
|
|
|
|
len(ranks[candidate]["lst"]) - ranks[candidate]["lst"].index(candidate)
|
|
|
|
)
|
|
|
|
|
2023-05-21 17:13:16 +03:00
|
|
|
res = []
|
|
|
|
for cand in ranks.keys():
|
2023-05-23 23:36:45 +03:00
|
|
|
res.append((ranks[cand]["rank"], cand))
|
|
|
|
return list(
|
|
|
|
filter(
|
|
|
|
lambda x: x[1] not in flatten_negatives, sorted(res, key=lambda x: -x[0])
|
|
|
|
)
|
|
|
|
)
|
2023-05-21 17:13:16 +03:00
|
|
|
|
|
|
|
|
|
|
|
def get_personal_recommendation(prefer, unprefer):
|
|
|
|
candidates = []
|
|
|
|
negative_candidates = []
|
|
|
|
|
|
|
|
for rec in prefer:
|
|
|
|
candidates.append(list(map(lambda x: x.oid, get_nearest_event(rec, 10)[1:])))
|
2023-05-23 23:36:45 +03:00
|
|
|
|
2023-05-21 17:13:16 +03:00
|
|
|
for neg in unprefer:
|
2023-05-23 23:36:45 +03:00
|
|
|
negative_candidates.append(
|
|
|
|
list(map(lambda x: x.oid, get_nearest_event(neg, 10)[1:]))
|
|
|
|
)
|
|
|
|
|
2023-05-21 17:13:16 +03:00
|
|
|
ranked = rank_candidates(candidates, negative_candidates)
|
|
|
|
|
|
|
|
return list(map(lambda x: (x[0], Event.objects.get(oid=x[1])), ranked[0:5]))
|
|
|
|
|
|
|
|
|
|
|
|
def get_personal_plays_recommendation(user):
|
|
|
|
pref = UserPreferences.objects.get(user=user)
|
|
|
|
|
|
|
|
prefer = pref.preffered_plays.all()
|
|
|
|
unprefer = pref.unpreffered_plays.all()
|
|
|
|
return get_personal_recommendation(prefer, unprefer)
|
|
|
|
|
|
|
|
|
|
|
|
def get_personal_concerts_recommendation(user):
|
|
|
|
pref = UserPreferences.objects.get(user=user)
|
|
|
|
|
|
|
|
prefer = pref.preferred_concerts.all()
|
|
|
|
unprefer = pref.unpreferred_concerts.all()
|
|
|
|
return get_personal_recommendation(prefer, unprefer)
|
|
|
|
|
|
|
|
|
|
|
|
def get_personal_movies_recommendation(user):
|
|
|
|
pref = UserPreferences.objects.get(user=user)
|
|
|
|
|
|
|
|
prefer = pref.preffered_movies.all()
|
|
|
|
unprefer = pref.unpreffered_movies.all()
|
|
|
|
return get_personal_recommendation(prefer, unprefer)
|
2023-05-22 22:37:54 +03:00
|
|
|
|
|
|
|
|
|
|
|
def dist_func(event1: Event, event2: Event):
|
2023-05-23 18:27:14 +03:00
|
|
|
# cords1 = [event1.lat, event1.lon]
|
|
|
|
# cords2 = [event2.lat, event2.lon]
|
|
|
|
# try:
|
|
|
|
# dist = GD(cords1, cords2).km
|
|
|
|
# return dist
|
|
|
|
# except:
|
|
|
|
# return 1000000
|
|
|
|
return (event1.lon - event2.lon) ** 2 + (event1.lat - event2.lat) ** 2
|
2023-05-22 22:37:54 +03:00
|
|
|
|
|
|
|
|
|
|
|
def generate_nearest():
|
|
|
|
NearestEvent.objects.all().delete()
|
|
|
|
all_events = list(Event.objects.all())
|
|
|
|
for i, event in enumerate(Event.objects.all()):
|
2023-05-23 23:36:45 +03:00
|
|
|
event_all_events = list(
|
|
|
|
sorted(all_events.copy(), key=lambda x: dist_func(event, x))
|
|
|
|
)
|
2023-05-22 22:37:54 +03:00
|
|
|
nearest = NearestEvent.objects.create(event=event)
|
|
|
|
nearest.nearest.set(event_all_events[0:100])
|
|
|
|
nearest.save()
|
|
|
|
if i % 100 == 0:
|
|
|
|
print(i)
|
|
|
|
|
|
|
|
|
2023-05-23 17:51:01 +03:00
|
|
|
def generate_hotel_nearest():
|
|
|
|
NearestHotel.objects.all().delete()
|
|
|
|
all_events = list(Event.objects.all())
|
|
|
|
hotels = list(Hotel.objects.all())
|
|
|
|
for i, hotel in enumerate(hotels):
|
2023-05-23 23:36:45 +03:00
|
|
|
event_all_events = list(
|
|
|
|
sorted(all_events.copy(), key=lambda x: dist_func(hotel, x))
|
|
|
|
)
|
2023-05-23 17:51:01 +03:00
|
|
|
nearest = NearestHotel.objects.create(hotel=hotel)
|
|
|
|
nearest.nearest_events.set(event_all_events[0:100])
|
|
|
|
if i % 100 == 0:
|
|
|
|
print(i)
|
|
|
|
|
2023-05-23 23:36:45 +03:00
|
|
|
|
2023-05-23 18:27:14 +03:00
|
|
|
def match_points():
|
|
|
|
regions = list(City.objects.all())
|
|
|
|
for i, point in enumerate(Event.objects.all()):
|
|
|
|
s_regions = list(sorted(regions.copy(), key=lambda x: dist_func(point, x)))
|
|
|
|
point.city = s_regions[0]
|
|
|
|
point.save()
|
|
|
|
if i % 10 == 0:
|
|
|
|
print(i)
|
|
|
|
|
2023-05-23 17:51:01 +03:00
|
|
|
|
2023-05-23 23:36:45 +03:00
|
|
|
def calculate_mean_metric(
|
|
|
|
favorite_events: Iterable[Event],
|
|
|
|
target_event: Event,
|
|
|
|
model: AnnoyIndex,
|
|
|
|
rev_mapping,
|
|
|
|
):
|
2023-05-22 22:37:54 +03:00
|
|
|
if not len(favorite_events):
|
|
|
|
return 100000
|
2023-05-23 23:36:45 +03:00
|
|
|
|
2023-05-22 22:37:54 +03:00
|
|
|
dists = []
|
2023-05-23 17:51:01 +03:00
|
|
|
target_event_idx = rev_mapping[target_event.oid]
|
2023-05-22 22:37:54 +03:00
|
|
|
for fav in favorite_events:
|
2023-05-23 17:51:01 +03:00
|
|
|
dists.append(model.get_distance(rev_mapping[fav.oid], target_event_idx))
|
2023-05-22 22:37:54 +03:00
|
|
|
return sum(dists) / len(dists)
|
|
|
|
|
|
|
|
|
|
|
|
def calculate_favorite_metric(event: Event, user: User):
|
|
|
|
pref = UserPreferences.objects.get(user=user)
|
2023-05-23 23:36:45 +03:00
|
|
|
if event.type == "plays":
|
2023-05-22 22:37:54 +03:00
|
|
|
preferred = pref.preffered_plays.all()
|
2023-05-23 23:36:45 +03:00
|
|
|
return calculate_mean_metric(preferred, event, plays_model, rev_plays_mapping)
|
|
|
|
if event.type == "concert":
|
2023-05-22 22:37:54 +03:00
|
|
|
preferred = pref.preferred_concerts.all()
|
|
|
|
return calculate_mean_metric(
|
2023-05-23 23:36:45 +03:00
|
|
|
preferred, event, concert_model, rev_concert_mapping
|
2023-05-22 22:37:54 +03:00
|
|
|
)
|
2023-05-23 23:36:45 +03:00
|
|
|
if event.type == "movie":
|
2023-05-22 22:37:54 +03:00
|
|
|
preferred = pref.preffered_movies.all()
|
2023-05-23 23:36:45 +03:00
|
|
|
return calculate_mean_metric(preferred, event, cinema_model, rev_cinema_mapping)
|
2023-05-22 22:37:54 +03:00
|
|
|
return 1000000
|
|
|
|
|
|
|
|
|
2023-05-23 23:36:45 +03:00
|
|
|
def get_nearest_favorite(
|
|
|
|
events: Iterable[Event], user: User, exclude_events: Iterable[Event] = []
|
|
|
|
):
|
2023-05-23 17:51:01 +03:00
|
|
|
first_event = None
|
|
|
|
for candidate in events:
|
2023-05-23 23:36:45 +03:00
|
|
|
if candidate not in exclude_events:
|
2023-05-23 17:51:01 +03:00
|
|
|
first_event = candidate
|
|
|
|
break
|
|
|
|
|
|
|
|
result = first_event
|
|
|
|
result_min = calculate_favorite_metric(result, user)
|
2023-05-22 22:37:54 +03:00
|
|
|
for event in events:
|
2023-05-23 23:36:45 +03:00
|
|
|
if event in exclude_events:
|
|
|
|
continue
|
2023-05-22 22:37:54 +03:00
|
|
|
local_min_metric = calculate_favorite_metric(event, user)
|
|
|
|
if local_min_metric < result_min:
|
|
|
|
result_min = local_min_metric
|
|
|
|
result = event
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
2023-05-23 17:51:01 +03:00
|
|
|
def filter_hotel(region: Region, user: User, stars: Iterable[int]):
|
|
|
|
hotels = Hotel.objects.filter(region=region)
|
|
|
|
return choice(hotels)
|
|
|
|
|
|
|
|
|
|
|
|
def time_func(km_distance: float):
|
|
|
|
return timedelta(minutes=(km_distance) / (4.0 / 60))
|
|
|
|
|
|
|
|
|
|
|
|
def generate_route(point1: BasePoint, point2: BasePoint):
|
|
|
|
distance = dist_func(point1, point2)
|
|
|
|
time = time_func(distance)
|
|
|
|
return {
|
|
|
|
"type": "transition",
|
|
|
|
"from": point1,
|
|
|
|
"to": point2,
|
|
|
|
"distance": distance,
|
2023-05-23 23:36:45 +03:00
|
|
|
"time": time,
|
2023-05-23 17:51:01 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
def generate_point(point: BasePoint):
|
|
|
|
return {
|
|
|
|
"type": "point",
|
|
|
|
"point": point,
|
|
|
|
"point_type": "",
|
2023-05-23 23:36:45 +03:00
|
|
|
"time": timedelta(minutes=90 + choice(range(-80, 90, 10))),
|
2023-05-23 17:51:01 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2023-05-22 22:37:54 +03:00
|
|
|
def generate_path(region: Region, user: User):
|
2023-05-23 23:36:45 +03:00
|
|
|
# region_events = Event.objects.filter(region=region)
|
2023-05-23 17:51:01 +03:00
|
|
|
|
|
|
|
hotel = filter_hotel(region, user, [])
|
2023-05-22 22:37:54 +03:00
|
|
|
|
2023-05-23 17:51:01 +03:00
|
|
|
candidates = NearestHotel.objects.get(hotel=hotel).nearest_events.all()
|
|
|
|
|
|
|
|
start_point = get_nearest_favorite(candidates, user, [])
|
2023-05-22 22:37:54 +03:00
|
|
|
|
|
|
|
candidates = NearestEvent.objects.get(event=start_point).nearest.all()
|
|
|
|
|
|
|
|
points = [start_point]
|
|
|
|
|
2023-05-23 17:51:01 +03:00
|
|
|
path = [generate_point(points[-1])]
|
|
|
|
|
|
|
|
start_time = datetime.combine(datetime.now(), time(hour=10))
|
|
|
|
|
|
|
|
while start_time.hour < 22:
|
2023-05-22 22:37:54 +03:00
|
|
|
candidates = NearestEvent.objects.get(event=points[-1]).nearest.all()
|
|
|
|
points.append(get_nearest_favorite(candidates, user, points))
|
2023-05-23 23:36:45 +03:00
|
|
|
|
2023-05-23 17:51:01 +03:00
|
|
|
transition_route = generate_route(points[-1], points[-2])
|
2023-05-23 23:36:45 +03:00
|
|
|
start_time += transition_route["time"]
|
2023-05-23 17:51:01 +03:00
|
|
|
|
|
|
|
point_route = generate_point(points[-1])
|
2023-05-23 23:36:45 +03:00
|
|
|
start_time += point_route["time"]
|
2023-05-23 17:51:01 +03:00
|
|
|
path.extend([transition_route, point_route])
|
2023-05-23 23:36:45 +03:00
|
|
|
|
2023-05-23 17:51:01 +03:00
|
|
|
return hotel, points, path
|