diff --git a/passfinder/recomendations/migrations/0003_nearestevent.py b/passfinder/recomendations/migrations/0003_nearestevent.py new file mode 100644 index 0000000..92e3ea0 --- /dev/null +++ b/passfinder/recomendations/migrations/0003_nearestevent.py @@ -0,0 +1,46 @@ +# Generated by Django 4.2.1 on 2023-05-22 17:09 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ("events", "0016_remove_basepoint_location_remove_city_location_and_more"), + ( + "recomendations", + "0002_rename_unpreffered_lays_userpreferences_unpreffered_plays", + ), + ] + + operations = [ + migrations.CreateModel( + name="NearestEvent", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "event", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="nearest_model_rel", + to="events.event", + ), + ), + ( + "nearest", + models.ManyToManyField( + related_name="nearest_model_rev_rel", to="events.event" + ), + ), + ], + ), + ] diff --git a/passfinder/recomendations/models.py b/passfinder/recomendations/models.py index 831ced3..6e219b0 100644 --- a/passfinder/recomendations/models.py +++ b/passfinder/recomendations/models.py @@ -13,4 +13,9 @@ class UserPreferences(models.Model): unpreffered_movies = models.ManyToManyField(Event, related_name='unpreffered_user_movie') preferred_concerts = models.ManyToManyField(Event, related_name='preffered_users_concert') - unpreferred_concerts = models.ManyToManyField(Event, related_name='unpreffered_users_concert') \ No newline at end of file + unpreferred_concerts = models.ManyToManyField(Event, related_name='unpreffered_users_concert') + + +class NearestEvent(models.Model): + event = models.ForeignKey(Event, on_delete=models.CASCADE, related_name='nearest_model_rel') + nearest = models.ManyToManyField(Event, related_name='nearest_model_rev_rel') \ No newline at end of file diff --git a/passfinder/recomendations/service/mapping/mapping.py b/passfinder/recomendations/service/mapping/mapping.py index d1361e2..d07e393 100644 --- a/passfinder/recomendations/service/mapping/mapping.py +++ b/passfinder/recomendations/service/mapping/mapping.py @@ -6,21 +6,29 @@ excursion_mapping = None concert_mapping = None + +def build_dict(list_mapping): + mapping = {} + for idx, elem in enumerate(list_mapping): + mapping.update({elem: idx}) + return mapping + + with open('passfinder/recomendations/service/mapping/attractions.pickle', 'rb') as file: - attraction_mapping = pickle.load(file) + attraction_mapping = build_dict(pickle.load(file)) with open('passfinder/recomendations/service/mapping/kino.pickle', 'rb') as file: - cinema_mapping = pickle.load(file) + cinema_mapping = build_dict(pickle.load(file)) with open('passfinder/recomendations/service/mapping/spektakli.pickle', 'rb') as file: - plays_mapping = pickle.load(file) + plays_mapping = build_dict(pickle.load(file)) with open('passfinder/recomendations/service/mapping/excursii.pickle', 'rb') as file: - excursion_mapping = pickle.load(file) + excursion_mapping = build_dict(pickle.load(file)) with open('passfinder/recomendations/service/mapping/concerts.pickle', 'rb') as file: - concert_mapping = pickle.load(file) \ No newline at end of file + concert_mapping = build_dict(pickle.load(file)) \ No newline at end of file diff --git a/passfinder/recomendations/service/service.py b/passfinder/recomendations/service/service.py index 3880251..30004c7 100644 --- a/passfinder/recomendations/service/service.py +++ b/passfinder/recomendations/service/service.py @@ -1,16 +1,18 @@ from annoy import AnnoyIndex from .mapping.mapping import * from .models.models import * -from passfinder.events.models import Event -from passfinder.recomendations.models import UserPreferences +from passfinder.events.models import Event, Region +from passfinder.recomendations.models import UserPreferences, NearestEvent from random import choice from collections import Counter +from passfinder.users.models import User +from collections.abc import Iterable def get_nearest_(instance_model, model_type, mapping, nearest_n, ml_model): how_many = len(Event.objects.filter(type=model_type)) - index = mapping.index(instance_model.oid) + index = mapping[instance_model.oid] nearest = ml_model.get_nns_by_item(index, len(mapping)) res = [] @@ -182,3 +184,89 @@ def get_personal_movies_recommendation(user): prefer = pref.preffered_movies.all() unprefer = pref.unpreffered_movies.all() return get_personal_recommendation(prefer, unprefer) + + + +def dist_func(event1: Event, event2: Event): + return (event1.lat - event2.lat) ** 2 + (event2.lon - event2.lon) ** 2 + + +def generate_nearest(): + NearestEvent.objects.all().delete() + all_events = list(Event.objects.all()) + for i, event in enumerate(Event.objects.all()): + event_all_events = list(sorted(all_events.copy(), key=lambda x: dist_func(event, x))) + nearest = NearestEvent.objects.create(event=event) + nearest.nearest.set(event_all_events[0:100]) + nearest.save() + if i % 100 == 0: + print(i) + + +def calculate_mean_metric(favorite_events: Iterable[Event], target_event: Event, model: AnnoyIndex, rev_list: Iterable[str]): + if not len(favorite_events): + return 100000 + + dists = [] + target_event_idx = rev_list[target_event.oid] + for fav in favorite_events: + dists.append(model.get_distance(rev_list[fav.oid], target_event_idx)) + return sum(dists) / len(dists) + + +def calculate_favorite_metric(event: Event, user: User): + pref = UserPreferences.objects.get(user=user) + if event.type == 'plays': + preferred = pref.preffered_plays.all() + return calculate_mean_metric( + preferred, + event, + plays_model, + plays_mapping + ) + if event.type == 'concert': + preferred = pref.preferred_concerts.all() + return calculate_mean_metric( + preferred, + event, + concert_model, + concert_mapping + ) + if event.type == 'movie': + preferred = pref.preffered_movies.all() + return calculate_mean_metric( + preferred, + event, + cinema_model, + cinema_mapping + ) + return 1000000 + + +def get_nearest_favorite(events: Iterable[Event], user: User, exclude_events: Iterable[Event]=[]): + result = events[0] + result_min = calculate_favorite_metric(events[0], user) + for event in events: + if event in exclude_events: continue + local_min_metric = calculate_favorite_metric(event, user) + if local_min_metric < result_min: + result_min = local_min_metric + result = event + + return result + + +def generate_path(region: Region, user: User): + region_events = Event.objects.filter(region=region) + + start_point = get_nearest_favorite(region_events, user, []) + + candidates = NearestEvent.objects.get(event=start_point).nearest.all() + + points = [start_point] + + while len(points) < 5: + candidates = NearestEvent.objects.get(event=points[-1]).nearest.all() + points.append(get_nearest_favorite(candidates, user, points)) + + return points