From 1df35a5e4ca395bb90dd1ab8a4284934e718fb56 Mon Sep 17 00:00:00 2001 From: ilia Date: Sat, 27 May 2023 22:32:52 +0300 Subject: [PATCH] remove bugs from path generation --- passfinder/city.txt | 270 ++++++++++++++ passfinder/events/api/consts.py | 241 ++----------- passfinder/events/api/serializers.py | 43 +++ passfinder/events/api/views.py | 50 ++- passfinder/recomendations/api/serializers.py | 22 +- passfinder/recomendations/api/views.py | 45 +-- ...eferences_preferred_categories_and_more.py | 31 ++ passfinder/recomendations/models.py | 4 + passfinder/recomendations/service/service.py | 341 ++++++++++++++---- 9 files changed, 742 insertions(+), 305 deletions(-) create mode 100644 passfinder/city.txt create mode 100644 passfinder/recomendations/migrations/0008_userpreferences_preferred_categories_and_more.py diff --git a/passfinder/city.txt b/passfinder/city.txt new file mode 100644 index 0000000..f888892 --- /dev/null +++ b/passfinder/city.txt @@ -0,0 +1,270 @@ +Москва + + +Санкт-Петербург + + +Новосибирск + + +Екатеринбург + + +Нижний Новгород + + +Самара + + +Омск + + +Казань + + +Челябинск + + +Ростов-на-Дону + + +Уфа + + +Волгоград + + +Пермь + + +Красноярск + +Воронеж + +Саратов + +Краснодар + +Тольятти + +Ижевск + +Ульяновск + +Барнаул + +Владивосток + +Ярославль + +Иркутск + +Тюмень + +Махачкала + +Хабаровск + +Оренбург + +Новокузнецк + +Кемерово + +Рязань + +Томск + +Астрахань + +Пенза + +Набережные Челны + +Липецк + +Тула + +Киров + +Чебоксары + +Калининград + +Брянск + +Курск + +Иваново + +Магнитогорск + +Улан-Удэ + +Тверь + +Ставрополь + +Нижний Тагил + +Белгород + +Архангельск + +Владимир + +Сочи + +Курган + +Смоленск + +Калуга + +Чита + +Ор +л + +Волжский + +Череповец + +Владикавказ + +Мурманск + +Сургут + +Вологда + +Саранск + +Тамбов + +Стерлитамак + +Грозный + +Якутск + +Кострома + +Комсомольск-на-Амуре + +Петрозаводск + +Таганрог + +Нижневартовск + +Йошкар-Ола + +Братск + +Новороссийск + +Дзержинск + +Шахты + +Нальчик + +Орск + +Сыктывкар + +Нижнекамск + +Ангарск + +Старый Оскол + +Великий Новгород + +Балашиха + +Благовещенск + +Прокопьевск + +Бийск + +Химки + +Псков + +Энгельс + +Рыбинск + +Балаково + +Северодвинск + +Армавир + +Подольск + +Корол +в + +Южно-Сахалинск + +Петропавловск-Камчатский + +Сызрань + +Норильск + +Златоуст + +Каменск-Уральский + +Мытищи + +Люберцы + +Волгодонск + +Новочеркасск + +Абакан + +Находка + +Уссурийск + +Березники + +Салават + +Электросталь + +Миасс + +Первоуральск + +Рубцовск + +Альметьевск + +Ковров + +Коломна + +Майкоп + +Пятигорск + +Одинцово + +Колпино + +Копейск + +Хасавюрт +Новомосковск +Кисловодск +Серпухов +Новочебоксарск diff --git a/passfinder/events/api/consts.py b/passfinder/events/api/consts.py index eaa33b2..a09253a 100644 --- a/passfinder/events/api/consts.py +++ b/passfinder/events/api/consts.py @@ -1,226 +1,47 @@ -city_in_hotels = ['Абзаково', - 'Абрамовка', - 'Абрау-Дюрсо', - 'Адлер', - 'Азов', - 'Аксай', - 'Альметьевск', - 'Анапа', - 'Андрианово', - 'Арамиль', - 'Арзамас', - 'Арнеево', - 'Архипо-Осиповка', - 'Бабкино', - 'Базы отдыха ВТО', +city_in_hotels = {'Астрахань', 'Балашиха', - 'Батайск', - 'Беличье', - 'Белореченск', - 'Бердск', - 'Бердяш', - 'Березовка', - 'Бжид', - 'Битца', - 'Благовещенская', - 'Болтино', - 'Большой Сочи', - 'Бор, Нижегородская область', - 'Борисово', - 'Борносово', - 'Будённовск', - 'Вардане', - 'Васильево, Ленинградская область', - 'Васкелово', - 'Вербилки', - 'Веселовка, Краснодарский край', - 'Видное', - 'Витязево', - 'Владимировка', - 'Внуково', - 'Воскресенск', - 'Вотря', - 'Всеволожск', - 'Всходы', - 'Выборг', - 'Вырубово', - 'Гатчина', - 'Гвардейское', - 'Геленджик', - 'Голиково', - 'Головинка Краснодарский край', - 'Голубицкая', - 'Горки', - 'Городец', - 'Горячий ключ', - 'Григорчиково', - 'Гуамка', - 'Д/О Авангард', - 'Дагомыс', - 'Джемете', - 'Джубга', + 'Березники', + 'Благовещенск', + 'Владикавказ', + 'Волгоград', + 'Волгодонск', + 'Волжский', + 'Воронеж', + 'Грозный', 'Дзержинск', - 'Дивеево', - 'Дивногорье, Краснодарский край', - 'Дивноморское', - 'Дмитров', - 'Домодедово', - 'Дракино', - 'Дранишники', - 'Дубечино', - 'Егорьевск', - 'Ейск', 'Екатеринбург', - 'Ершово', - 'Ессентуки', - 'Железноводск', - 'Жуковский', - 'За Родину', - 'Звенигород', - 'Зеленая поляна', - 'Ивантеевка', - 'Ильичево', - 'Иннолово ', - 'Иноземцево', - 'Исаково', - 'Истра', - 'Кабардинка', - 'Казань', - 'Каменск-Шахтинский', - 'Каневская', - 'Кингисепп', + 'Златоуст', + 'Ижевск', + 'Каменск-Уральский', + 'Киров', 'Кисловодск', - 'Клин', - 'Коломна', - 'Коробицыно', - 'Королев', - 'Косулино', - 'Котельники', - 'Красная Горка', - 'Красная Поляна', - 'Красногорск', - 'Краснодар', - 'Красный Колос', - 'Кудряшовский', - 'Курово', - 'Кусимовского Рудника', - 'Кучугуры', - 'Лабинск', - 'Лазаревское', - 'Лермонтово', - 'Лесной городок', - 'Лодейное Поле', - 'Лоо', - 'Лосево', + 'Колпино', + 'Комсомольск-на-Амуре', + 'Копейск', + 'Кострома', + 'Красноярск', + 'Курган', 'Люберцы', 'Магнитогорск', - 'Малые Решники', - 'Марьино, Ленинградская область', - 'Маяковского', - 'Мезмай', - 'Мещерино', - 'Миасс', - 'Минеральные Воды', - 'Мистолово', - 'Мишуткино', - 'Можайск', + 'Махачкала', 'Москва', - 'Мостовской', 'Мытищи', - 'Набережные Челны', - 'Наро-Фоминск', - 'Нарынка', - 'Небуг', - 'Нестерово', - 'Нижний Новгород', + 'Нальчик', + 'Нижнекамск', 'Нижний Тагил', - 'Новая', - 'Новоабзаково', - 'Нововолково', - 'Новомихайловский', - 'Новороссийск', - 'Новосибирск', - 'Новочеркасск', - 'Новый путь', - 'Ногинск', - 'Нурлат', - 'Овсяники', + 'Новочебоксарск', 'Одинцово', - 'Озеры', - 'Оксино', - 'Октябрьский, Московская область', - 'Ольгинка', - 'Остров, Московская область', - 'Павловск', - 'Падиково', - 'Пересвет', - 'Платформа 69-й километр, Сосновское сельское поселение', + 'Омск', + 'Оренбург', + 'Орск', + 'Пермь', 'Подольск', - 'Подпорожье', - 'Полтавская', - 'Приморско-Ахтарск', - 'Приозерск', - 'Прохорово', - 'Пушкино', 'Пятигорск', - 'Раменское', - 'Реутов', - 'Рождествено', - 'Роза Хутор', - 'Ростов-на-Дону', - 'Рощино', - 'Руза', + 'Салават', 'Санкт-Петербург', - 'Светлое', - 'Светлый', - 'Свирица', - 'Сергиев Посад', - 'Серпухов', - 'Симагино', - 'Сириус', - 'Скоково', - 'Снегири', - 'Солнечногорск', - 'Солохаул', - 'Сосново', - 'Сосновый Бор, Ленинградская область', - 'Сосновый Бор, Московская область', - 'Софрино', - 'Сочи', - 'Ставрополь', - 'Станица Динская', - 'Станица Должанская', - 'Старая Руза', - 'Степаньково', - 'Суйда', - 'Сукко', - 'Супсех', - 'Таганрог', - 'Тарасово', - 'Тимашевск', - 'Тихвин', - 'Тихорецк', - 'Тобольск', - 'Туапсе', - 'Тучково', + 'Томск', 'Тюмень', - 'Увильды ', - 'Углегорский', - 'Удельная', - 'Усть-Койсуг', - 'Усть-Лабинск', 'Уфа', - 'Ушаки', - 'Фрязино', - 'Хадыженск', + 'Хасавюрт', 'Химки', - 'Хоста', - 'Чебаркуль', - 'Челябинск', - 'Чехов, Сахалинская область', - 'Чудская', - 'Шахты', - 'Широкая балка', - 'Щёлково', - 'Эсто-Садок', - 'Якорная щель'] \ No newline at end of file + 'Энгельс'} \ No newline at end of file diff --git a/passfinder/events/api/serializers.py b/passfinder/events/api/serializers.py index 6b04065..00a1dc7 100644 --- a/passfinder/events/api/serializers.py +++ b/passfinder/events/api/serializers.py @@ -65,6 +65,49 @@ class RouteInputSerializer(serializers.Serializer): min_length=24, max_length=24, required=False, allow_blank=True, allow_null=True ) movement = serializers.ChoiceField(['walk', 'bike', 'scooter', 'auto'], required=False, allow_blank=True) + stars = serializers.ListField( + child=serializers.ChoiceField([1, 2, 3, 4, 5]), + required=False, + allow_empty=True, + allow_null=True + ) + what_to_see = serializers.ListField( + child=serializers.ChoiceField( + [ + 'attractions', + 'museum', + 'movie', + 'concert', + 'artwork', + 'plays', + 'shop', + 'gallery', + 'theme_park', + 'viewpoint', + 'zoo' + ] + ), + required=False, + allow_empty=True, + allow_null=True + ) + where_stay = serializers.ListField( + child=serializers.ChoiceField([ + 'hotel', 'apartment', 'hostel' + ]), + required=False, + allow_empty=True, + allow_null=True + ) + where_eat = serializers.ListField( + child=serializers.ChoiceField(['restaurant', 'bar', 'cafe']), + required=False, + allow_empty=True, + allow_null=True + ) + with_kids = serializers.BooleanField(required=False, allow_null=True) + with_animals = serializers.BooleanField(required=False, allow_null=True) + class CitySerializer(serializers.ModelSerializer): class Meta: diff --git a/passfinder/events/api/views.py b/passfinder/events/api/views.py index 7c66118..0bc09e3 100644 --- a/passfinder/events/api/views.py +++ b/passfinder/events/api/views.py @@ -75,6 +75,40 @@ def post(self, request): movement = data['movement'] except KeyError: movement = 'walk' + + hotel_stars = data['stars'] + if hotel_stars is None: + hotel_stars = [] + + + hotel_type = data['where_stay'] + if hotel_type is None: + hotel_type = ['hotel'] + + where_eat = data['where_eat'] + if where_eat is None: + where_eat = ['restaurant', 'bar', 'cafe'] + + what_to_see = data['what_to_see'] + if what_to_see is None: + what_to_see = [ + 'attractions', + 'museum', + 'movie', + 'concert', + 'artwork', + 'plays', + 'shop', + 'gallery', + 'theme_park', + 'viewpoint', + 'zoo' + ] + + + if 'hotel' not in hotel_type: + hotel_stars = [] + region = None if city_id: @@ -95,7 +129,17 @@ def post(self, request): print(request.user, region, start_date, end_date) - tour = generate_tour(request.user, region, start_date, end_date, movement_mapping[movement]) + tour = generate_tour( + request.user, + region, + start_date, + end_date, + avg_velocity=movement_mapping[movement], + stars=hotel_stars, + hotel_type=hotel_type, + where_eat=where_eat, + what_to_see=what_to_see + ) print(len(tour[1])) return Response(data=tour[0]) @@ -109,9 +153,7 @@ class ListRegionApiView(ListAPIView): class ListCityApiView(ListAPIView): serializer_class = CitySerializer queryset = ( - City.objects.annotate(points_num=Count("points")) - .filter(points_num__gte=100) - .order_by("title") + City.objects.annotate(points_count=Count('points')).filter(title__in=city_in_hotels).filter(points_count__gt=200).order_by('title') ) diff --git a/passfinder/recomendations/api/serializers.py b/passfinder/recomendations/api/serializers.py index 7fc072b..f57b186 100644 --- a/passfinder/recomendations/api/serializers.py +++ b/passfinder/recomendations/api/serializers.py @@ -1,5 +1,5 @@ from rest_framework import serializers -from passfinder.events.api.serializers import EventSerializer, HotelSerializer +from passfinder.events.api.serializers import EventSerializer, HotelSerializer, ObjectRouteSerializer class TinderProceedSerializer(serializers.Serializer): @@ -49,3 +49,23 @@ class DailySelectionSerializer(serializers.Serializer): ) ) event = EventSerializer() + + +class StarSelectionSerializer(serializers.Serializer): + stars = serializers.ListField(child=serializers.IntegerField(), write_only=True) + + +class CategorySelectionSerializer(serializers.Serializer): + categories = serializers.ListField(child=serializers.ChoiceField( + ['attractions', 'museum', 'movie', 'concert', 'artwork', 'plays', 'shop', 'gallery', 'theme_park', 'viewpoint', 'zoo'] + )) + + +class RecomendationNode(serializers.Serializer): + category = serializers.CharField() + events = serializers.ListField(child=ObjectRouteSerializer()) + + + +class SelfRecomendationSerializer(serializers.Serializer): + recomendations = serializers.ListField(child=RecomendationNode(), write_only=True) \ No newline at end of file diff --git a/passfinder/recomendations/api/views.py b/passfinder/recomendations/api/views.py index d2b92ba..ae76071 100644 --- a/passfinder/recomendations/api/views.py +++ b/passfinder/recomendations/api/views.py @@ -49,32 +49,13 @@ class PersonalRecommendation(viewsets.GenericViewSet): model = Event queryset = Event.objects.all() - @action(methods=['GET'], detail=False) - def plays(self, request, *args, **kwargs): - recs = get_personal_plays_recommendation(request.user) - ans = [] - for rec in recs: - ans.append(EventSerializer(rec[1]).data) - return Response(ans, 200) - - - @action(methods=['GET'], detail=False) - def concerts(self, request, *args, **kwargs): - recs = get_personal_concerts_recommendation(request.user) - ans = [] - for rec in recs: - ans.append(EventSerializer(rec[1]).data) - return Response(ans, 200) - - - @action(methods=['GET'], detail=False) - def movies(self, request, *args, **kwargs): - recs = get_personal_movies_recommendation(request.user) - ans = [] - for rec in recs: - ans.append(EventSerializer(rec[1]).data) - return Response(ans, 200) + @action(methods=['GET'], detail=False, serializer_class=SelfRecomendationSerializer) + def recommendations(self, request, *args, **kwargs): + return Response( + data=get_personal_recomendations(request.user), + status=200 + ) @action(methods=['GET'], detail=True) def get_nearest_user_distance(self, request, pk, *args, **kwargs): @@ -166,3 +147,17 @@ def add_to_favorites(self, request, pk, *args, **kwargs): pref.save() return Response(status=200) + + @action(methods=['POST'], detail=False, serializer_class=StarSelectionSerializer) + def set_hotel_stars(self, request, *args, **kwargs): + up, _ = UserPreferences.objects.get_or_create(user=request.user) + up.preferred_stars = request.data['stars'] + up.save() + return Response(status=200) + + @action(methods=['POST'], detail=False, serializer_class=CategorySelectionSerializer) + def set_categories(self, request, *args, **kwargs): + up, _ = UserPreferences.objects.get_or_create(user=request.user) + up.preferred_categories = request.data['categories'] + up.save() + return Response(status=200) diff --git a/passfinder/recomendations/migrations/0008_userpreferences_preferred_categories_and_more.py b/passfinder/recomendations/migrations/0008_userpreferences_preferred_categories_and_more.py new file mode 100644 index 0000000..1de34dc --- /dev/null +++ b/passfinder/recomendations/migrations/0008_userpreferences_preferred_categories_and_more.py @@ -0,0 +1,31 @@ +# Generated by Django 4.2.1 on 2023-05-27 10:21 + +import django.contrib.postgres.fields +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("recomendations", "0007_nearesteventtorestaurant"), + ] + + operations = [ + migrations.AddField( + model_name="userpreferences", + name="preferred_categories", + field=django.contrib.postgres.fields.ArrayField( + base_field=models.CharField(max_length=100), + blank=True, + null=True, + size=None, + ), + ), + migrations.AddField( + model_name="userpreferences", + name="preferred_stars", + field=django.contrib.postgres.fields.ArrayField( + base_field=models.IntegerField(), blank=True, null=True, size=None + ), + ), + ] diff --git a/passfinder/recomendations/models.py b/passfinder/recomendations/models.py index c2a8b73..ffc984f 100644 --- a/passfinder/recomendations/models.py +++ b/passfinder/recomendations/models.py @@ -1,6 +1,7 @@ from django.db import models from passfinder.users.models import User from passfinder.events.models import Event, Hotel, Restaurant +from django.contrib.postgres.fields import ArrayField class UserPreferences(models.Model): @@ -21,6 +22,9 @@ class UserPreferences(models.Model): prefferred_museums = models.ManyToManyField(Event, related_name='preffered_users_museums') unprefferred_museums = models.ManyToManyField(Event, related_name='unpreffered_users_museums') + preferred_categories = ArrayField(base_field=models.CharField(max_length=100), null=True, blank=True) + preferred_stars = ArrayField(base_field=models.IntegerField(), null=True, blank=True) + class NearestEvent(models.Model): diff --git a/passfinder/recomendations/service/service.py b/passfinder/recomendations/service/service.py index 5a96453..cdb6700 100644 --- a/passfinder/recomendations/service/service.py +++ b/passfinder/recomendations/service/service.py @@ -266,8 +266,7 @@ def dist_func(event1: Event, event2: Event): return dist except: return 1000000 - #return (event1.lon - event2.lon) ** 2 + (event1.lat - event2.lat) ** 2 - # return (event1.lon - event2.lon) ** 2 + (event1.lat - event2.lat) ** 2 + return (event1.lon - event2.lon) ** 2 + (event1.lat - event2.lat) ** 2 def generate_nearest(): @@ -349,6 +348,19 @@ def match_points(): print(i) +def match_restaurants(): + regions = list(City.objects.all()) + + for i, point in enumerate(Restaurant.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) + + + def calculate_mean_metric( favorite_events: Iterable[Event], target_event: Event, @@ -393,21 +405,50 @@ def calculate_favorite_metric(event: Event, user: User): def get_exponential_koef(time: timedelta): time = time.seconds if time < 60 * 10: - return 1 + return 2 if time < 60 * 20: - return 10 + return 5 if time < 60 * 30: - return 1000 + return 10 if time < 60 * 40: - return 100000 + return 20 return int(1e10) +def get_category_similarity_coef(event, user): + up, _ = UserPreferences.objects.get_or_create(user=user) + cat = up.preferred_categories + if event.type in cat: + return 0.7 + else: + return 1.2 + + def get_nearest_favorite( - events: Iterable[Event], user: User, base_event: Event, exclude_events: Iterable[Event] = [], velocity=3.0, top_k=1 + events: Iterable[Event], + user: User, + base_event: Event, + exclude_events: Iterable[Event] = [], + velocity=3.0, + top_k=1 ): - sorted_events = list(sorted(events, key=lambda event: calculate_favorite_metric(event, user) * get_exponential_koef(time_func(dist_func(event, base_event), velocity)))) + sorted_events = list( + sorted( + filter(lambda event: event not in exclude_events, events), + key=lambda event: + calculate_favorite_metric(event, user) * + get_exponential_koef( + time_func( + dist_func( + event, base_event + ), + velocity + ) + ) * + get_category_similarity_coef(event, user) + ) + ) if top_k == 1: return sorted_events[0] @@ -424,14 +465,6 @@ def time_func(km_distance: float, velocity: float): return timedelta(minutes=(km_distance) / (velocity / 60)) -def generate_route(point1: BasePoint, point2: BasePoint, velocity: float): - distance = dist_func(point1, point2) - time = time_func(distance, velocity) - - -def time_func(km_distance: float, velocity): - return timedelta(minutes=(km_distance) / (velocity / 60)) - def generate_route(point1: BasePoint, point2: BasePoint, velocity): distance = dist_func(point1, point2) @@ -470,14 +503,40 @@ def generate_multiple_tours(user: User, city: City, start_date: datetime.date, e return pool.map(generate_tour, [(user, start_date, end_date, hotel) for hotel in hotels]) -def generate_tour(user: User, city: City, start_date: datetime.date, end_date: datetime.date, avg_velocity=3.0): +def generate_tour( + user: User, + city: City, + start_date: datetime.date, + end_date: datetime.date, + avg_velocity=3.0, + stars=[], + hotel_type=['hotel', 'hostel', 'apartment'], + where_eat=['restaurant', 'bar', 'cafe'], + what_to_see=['attractions', 'museum', 'movie', 'concert', 'artwork', 'plays', 'shop', 'gallery', 'theme_park', 'viewpoint', 'zoo'] + ): UserPreferences.objects.get_or_create(user=user) - hotel = choice(list(Hotel.objects.filter(city=city))) + + hotels_candidates = Hotel.objects.filter(city=city) + if len(hotels_candidates.filter(stars__in=stars)): + hotels_candidates = hotels_candidates.filter(stars__in=stars) + + try: + hotel = choice(list(hotels_candidates)) + except: + hotel = city current_date = start_date paths, points, disallowed_rest = [], [], [] while current_date < end_date: - local_points, local_paths, local_disallowed_rest = generate_path(user, points, hotel, disallowed_rest, avg_velocity) + local_points, local_paths, local_disallowed_rest = generate_path( + user, + points, + hotel, + disallowed_rest, + avg_velocity, + where_eat=where_eat, + what_to_see=what_to_see + ) points.extend(local_points) paths.append( { @@ -514,22 +573,56 @@ def nearest_distance_points(point: BasePoint, user: User, velocity: float=3.0): -def generate_path(user: User, disallowed_points: Iterable[BasePoint], hotel: Hotel, disallowed_rests: Iterable[Restaurant], avg_velocity: float): - # region_events = Event.objects.filter(region=region) - - #candidates = NearestHotel.objects.get(hotel=hotel).nearest_events.all() - allowed_types = ['museum', 'attraction'] - - start_point = NearestRestaurantToHotel.objects.filter(hotel=hotel).first().restaurants.filter(~Q(oid__in=disallowed_rests)).first() - disallowed_rests.append(start_point.oid) - candidates = list(filter(lambda x: x not in disallowed_points, hotel.nearest_hotel_rel.all().first().nearest_events.filter(type__in=allowed_types))) - #candidates = list(filter(lambda x: x.type in allowed_types, map(lambda x: x.event, start_point.nearestrestauranttoevent_set.all()[0:100]))) - points = [start_point] - path = [ - generate_hotel(hotel), - generate_route(start_point, hotel, avg_velocity), - generate_restaurant(start_point) +def generate_path( + user: User, + disallowed_points: Iterable[BasePoint], + hotel: Hotel, + disallowed_rests: Iterable[Restaurant], + avg_velocity: float, + where_eat=['restaurant', 'bar', 'cafe'], + what_to_see=['attractions', 'museum', 'movie', 'concert', 'artwork', 'plays', 'shop', 'gallery', 'theme_park', 'viewpoint', 'zoo'] + ): + allowed_types = [ + 'museum', + 'attraction', + 'artwork', + 'shop', + 'gallery', + 'theme_park', + 'zoo', + 'other', + 'viewpoint' ] + if len(set(allowed_types) & set(what_to_see)) == 0: + allowed_types = what_to_see + else: + allowed_types = list(set(allowed_types) & set(what_to_see)) + print(allowed_types, hotel) + if isinstance(hotel, City): + start_points_candidate = Restaurant.objects.filter(city=hotel).filter(~Q(oid__in=disallowed_rests)) + else: + start_points_candidate = NearestRestaurantToHotel.objects.filter(hotel=hotel).first().restaurants.filter(~Q(oid__in=disallowed_rests)) + + if len(start_points_candidate.filter(type__in=where_eat)): + start_points_candidate = start_points_candidate.filter(type__in=where_eat) + + start_point = start_points_candidate[0] + disallowed_rests.append(start_point.oid) + + candidates = NearestEventToRestaurant.objects.get(restaurant=start_point).events.all().filter(type__in=allowed_types) + + points = [start_point] + + if isinstance(hotel, Hotel): + path = [ + generate_hotel(hotel), + generate_route(start_point, hotel, avg_velocity), + generate_restaurant(start_point) + ] + else: + path = [ + generate_restaurant(start_point) + ] start_time = datetime.combine(datetime.now(), time(hour=10)) @@ -537,32 +630,53 @@ def generate_path(user: User, disallowed_points: Iterable[BasePoint], hotel: Hot while start_time.hour < 22 and start_time.day == datetime.now().day: if (start_time.hour > 14 and how_many_eat == 1) or (start_time.hour > 20 and how_many_eat == 2): - point = NearestRestaurantToEvent.objects.filter(event=points[-1]).first().restaurants.filter(~Q(oid__in=disallowed_rests))[0] - disallowed_rests.append(point.oid) - points.append(point) - # Переделать - сделать еще один прекалк на рестораны с точками - candidates = NearestEventToRestaurant.objects.get(restaurant=point).events.all().filter(type__in=allowed_types) - if len(candidates) < 10: - candidates = NearestEventToRestaurant.objects.get(restaurant=point).events.all() - - - path.append(generate_restaurant(points[-1])) - start_time += timedelta(seconds=path[-1]['time']) - how_many_eat += 1 - continue + print(points, start_time) + try: + point_candidates = NearestRestaurantToEvent.objects.filter(event=points[-1]).first().restaurants.filter(~Q(oid__in=disallowed_rests)) + if len(point_candidates.filter(type__in=where_eat)): + point_candidates = point_candidates.filter(type__in=where_eat) + point = point_candidates[0] + + disallowed_rests.append(point.oid) + points.append(point) + + candidates = NearestEventToRestaurant.objects.get(restaurant=point).events.all().filter(type__in=allowed_types) + if len(candidates) < 2: + candidates = NearestEventToRestaurant.objects.get(restaurant=point).events.all() + + path.append(generate_restaurant(points[-1])) + start_time += timedelta(seconds=path[-1]['time']) + how_many_eat += 1 + continue + except: + return points, path, disallowed_rests + if start_time.hour > 17: - allowed_types = ['play', 'concert', 'movie'] + allowed_types = [ + 'play', + 'concert', + 'movie', + 'shop', + 'gallery', + 'theme_park', + 'viewpoint' + ] + if len(set(allowed_types) & set(what_to_see)) == 0: + allowed_types = what_to_see + else: + allowed_types = list(set(allowed_types) & set(what_to_see)) + if candidates is None: candidates = NearestEvent.objects.get(event=points[-1]).nearest.filter(type__in=allowed_types) - if len(candidates) < 10: + if len(candidates) < 2: candidates = NearestEvent.objects.get(event=points[-1]).nearest.all() try: points.append(get_nearest_favorite(candidates, user, points[-1], points + disallowed_points)) - except AttributeError: + except: points.append(get_nearest_favorite(candidates, user, points[-1], points)) transition_route = generate_route(points[-1], points[-2], avg_velocity) @@ -576,15 +690,6 @@ def generate_path(user: User, disallowed_points: Iterable[BasePoint], hotel: Hot return points, path, disallowed_rests -def calculate_distance(sample1: Event, samples: Iterable[Event], model: AnnoyIndex, rev_mapping): - metrics = [] - - for sample in samples: - metrics.append(model.get_distance(rev_mapping[sample1.oid], rev_mapping[sample.oid])) - - return sum(metrics) / len(metrics) - - def calculate_distance( sample1: Event, samples: Iterable[Event], model: AnnoyIndex, rev_mapping ): @@ -627,11 +732,6 @@ def get_onboarding_hotels(stars=Iterable[int]): def generate_points_path(user: User, points: Iterable[Event], velocity=3.0): - """ - Дописать - 1) генерить маршруты от многих точек (не только по 2) (salesman problem) - 2) Если в маршруте до 7 точек - добавлять похожие пока не станет 7 точек - """ if len(points) < 7: candidates = NearestEvent.objects.get(event=points[0]).nearest.all() points.extend(list(get_nearest_favorite(candidates, user, points[0], [], velocity, 7-len(points)))) @@ -662,4 +762,115 @@ def generate_points_path(user: User, points: Iterable[Event], velocity=3.0): ]) visited_points.append(pt) - return res \ No newline at end of file + return res + + +def flat_list(lst): + res = [] + for i in lst: + res.extend(i) + return res + + +def range_candidates(candidates, user, favorite_events): + model_mappings = { + 'attraction': [attracion_model, rev_attraction_mapping], + 'museum': [mus_model, rev_mus_mapping], + 'movie': [cinema_model, rev_cinema_mapping], + 'concert': [concert_model, rev_concert_mapping], + 'plays': [plays_model, rev_plays_mapping] + } + + if candidates[0].type in ['attraction', 'museum', 'movie', 'concert', 'plays']: + candidates = sorted( + candidates, + key=lambda cand: calculate_mean_metric( + favorite_events, + cand, + *model_mappings[cand.type] + ) + ) + return candidates[0:10] + return sample(candidates, 10) + + +def get_personal_recomendations(user): + up, _ = UserPreferences.objects.get_or_create(user=user) + candidates_generate_strategy = { + 'plays': [lambda pref: flat_list( + list( + map( + lambda cand: nearest_plays( + cand, 30 + ), + pref.preffered_plays.all() + ) + ), + ), lambda pref: pref.preffered_plays.all()], + 'movie': [lambda pref: flat_list( + list( + map( + lambda cand: nearest_movie( + cand, 30 + ), + pref.preffered_movies.all() + ) + ), + ), lambda pref: pref.preffered_movies.all()], + 'concert': [lambda pref: flat_list( + list( + map( + lambda cand: nearest_concert( + cand, 30 + ), + pref.preferred_concerts.all() + ) + ), + ), lambda pref: pref.preferred_concerts.all()], + 'attractions': [lambda pref: flat_list( + list( + map( + lambda cand: nearest_attraction( + cand, 30 + ), + pref.prefferred_attractions.all() + ) + ), + ), lambda pref: pref.prefferred_attractions.all()], + 'museum': [lambda pref: flat_list( + list( + map( + lambda cand: nearest_mus( + cand, 30 + ), + pref.prefferred_museums.all() + ) + ), + ), lambda pref: pref.prefferred_museums.all()], + 'shop': [lambda pref: sample(list(Event.objects.filter(type='shop')), 10), lambda x: []], + 'gallery': [lambda pref: sample(list(Event.objects.filter(type='gallery')), 10), lambda x: []], + 'theme_park': [lambda pref: sample(list(Event.objects.filter(type='theme_park')), 10), lambda x: []], + 'viewpoint': [lambda pref: sample(list(Event.objects.filter(type='viewpoint')), 10), lambda x: []], + 'zoo': [lambda pref: sample(list(Event.objects.filter(type='zoo')), 10), lambda x: []], + } + + res = [] + for category_candidate in up.preferred_categories: + candidates = candidates_generate_strategy[category_candidate][0](up) + ranged = range_candidates( + candidates, + user, + candidates_generate_strategy[category_candidate][1](up) + ) + res.append( + { + 'category': category_candidate, + 'events': list( + map( + lambda x: ObjectRouteSerializer(x).data, + ranged + ) + ) + } + ) + return res