added attractions info, route save, user favorites

This commit is contained in:
Alexander Karpov 2023-05-23 23:36:45 +03:00
parent affe5b1e2c
commit 857e7b870a
13 changed files with 489 additions and 113 deletions

View File

@ -1,7 +1,11 @@
from django.urls import path, include from django.urls import path, include
from rest_framework.routers import DefaultRouter from rest_framework.routers import DefaultRouter
from passfinder.recomendations.api.views import TinderView, PersonalRecommendation from passfinder.recomendations.api.views import TinderView, PersonalRecommendation
from passfinder.users.api.views import UserViewSet, CreateUserPreferenceApiView from passfinder.users.api.views import (
UserViewSet,
CreateUserPreferenceApiView,
ListUserFavoritePointsApiView,
)
router = DefaultRouter() router = DefaultRouter()
@ -14,5 +18,6 @@
path("", include("passfinder.events.api.urls")), path("", include("passfinder.events.api.urls")),
path("auth/", include("passfinder.users.api.urls")), path("auth/", include("passfinder.users.api.urls")),
path("user/preference", CreateUserPreferenceApiView.as_view()), path("user/preference", CreateUserPreferenceApiView.as_view()),
path("user/favorite", ListUserFavoritePointsApiView.as_view()),
] ]
urlpatterns += router.urls urlpatterns += router.urls

View File

@ -291,7 +291,7 @@
CELERY_REDIS_DB = env("CELERY_REDIS_DB", default=0) CELERY_REDIS_DB = env("CELERY_REDIS_DB", default=0)
CELERY_REDIS_SSL = env.bool("CELERY_REDIS_SSL", default=False) CELERY_REDIS_SSL = env.bool("CELERY_REDIS_SSL", default=False)
CELERY_BROKER_URL = env("CELERY_BROKER_URL", default='') CELERY_BROKER_URL = env("CELERY_BROKER_URL", default="")
CELERY_TASK_SERIALIZER = "json" CELERY_TASK_SERIALIZER = "json"
CELERY_ACCEPT_CONTENT = ["application/json"] CELERY_ACCEPT_CONTENT = ["application/json"]
CELERY_ENABLE_UTC = True CELERY_ENABLE_UTC = True
@ -301,7 +301,6 @@
"task": "django_clickhouse.tasks.clickhouse_auto_sync", "task": "django_clickhouse.tasks.clickhouse_auto_sync",
"schedule": timedelta(seconds=5), "schedule": timedelta(seconds=5),
"options": {"expires": 1}, "options": {"expires": 1},
}, },
} }
# DRF # DRF
@ -346,7 +345,7 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
CLICKHOUSE_DATABASES = { CLICKHOUSE_DATABASES = {
"default": { "default": {
"db_url": env("CLICKHOUSE_URL", default="http://akarpov.ru:1337"), "db_url": env("CLICKHOUSE_URL", default="http://localhost:8123"),
"db_name": env("CLICKHOUSE_DB", default="default"), "db_name": env("CLICKHOUSE_DB", default="default"),
"username": env("CLICKHOUSE_USER", default="default"), "username": env("CLICKHOUSE_USER", default="default"),
"password": env("CLICKHOUSE_PASSWORD", default="default"), "password": env("CLICKHOUSE_PASSWORD", default="default"),

File diff suppressed because one or more lines are too long

View File

@ -1,11 +1,12 @@
import json import json
from pprint import pprint from pprint import pprint
from passfinder.events.models import Region from passfinder.events.models import Region, Event
with open("data/pos-attr.json") as f: with open("data/pos-attr.json") as f:
data = json.load(f) data = json.load(f)
reggg = { reggg = {
"г. Санкт-Петербург": "Санкт-Петербург", "г. Санкт-Петербург": "Санкт-Петербург",
"г. Москва": "Москва", "г. Москва": "Москва",
@ -16,7 +17,7 @@
"Республика Северная Осетия - Алания": "Республика Северная Осетия Алания", "Республика Северная Осетия - Алания": "Республика Северная Осетия Алания",
"Ханты-Мансийский автономный округ - Югра": "Ханты-Мансийский автономный округ — Югра", "Ханты-Мансийский автономный округ - Югра": "Ханты-Мансийский автономный округ — Югра",
} }
rett = []
ret = [] ret = []
for infff in data: for infff in data:
info = infff["general"] info = infff["general"]
@ -38,6 +39,21 @@
} }
if "typologies" in info: if "typologies" in info:
res["extra_kwargs"]["typologies"] = [x["value"] for x in info["typologies"]] res["extra_kwargs"]["typologies"] = [x["value"] for x in info["typologies"]]
if "securityInfo" in info or "borderInfo" in info:
for ev in Event.objects.filter(
title=info["name"],
address=res["address"],
lat=res["lat"],
lon=res["lon"],
):
ddd = [
info["securityInfo"] if "securityInfo" in info else "",
info["borderInfo"] if "borderInfo" in info else "",
]
ddd = [x for x in ddd if x]
ev.description = " ".join(ddd)
ev.save()
ret.append(res) ret.append(res)

View File

@ -10,11 +10,6 @@ class Meta:
exclude = "hotel" exclude = "hotel"
class CitySerializer(serializers.ModelSerializer):
class Meta:
model = City
class HotelSerializer(serializers.ModelSerializer): class HotelSerializer(serializers.ModelSerializer):
phones = HotelPhoneSerializer(many=True) phones = HotelPhoneSerializer(many=True)
source = serializers.CharField(source="parser_source") source = serializers.CharField(source="parser_source")
@ -41,11 +36,12 @@ class Meta:
class PointSerializer(serializers.ModelSerializer): class PointSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = BasePoint model = BasePoint
fields = ["title", "description", "location", "icon"] fields = ["oid", "title", "description", "location", "icon"]
class RouteSerializer(serializers.Serializer): class RouteSerializer(serializers.Serializer):
name = serializers.CharField() name = serializers.CharField()
date = serializers.DateField(allow_null=True)
description = serializers.CharField() description = serializers.CharField()
points = serializers.ListSerializer(child=PointSerializer()) points = serializers.ListSerializer(child=PointSerializer())
@ -54,11 +50,59 @@ class RouteInputSerializer(serializers.Serializer):
date_from = serializers.DateField(required=False, allow_null=True) date_from = serializers.DateField(required=False, allow_null=True)
date_to = serializers.DateField(required=False, allow_null=True) date_to = serializers.DateField(required=False, allow_null=True)
region = serializers.CharField( region = serializers.CharField(
min_length=24, max_length=24, required=False, allow_blank=True min_length=24, max_length=24, required=False, allow_blank=True, allow_null=True
) )
class CitySerializer(serializers.ModelSerializer):
class Meta:
model = City
fields = ["oid", "title"]
class RegionSerializer(serializers.ModelSerializer): class RegionSerializer(serializers.ModelSerializer):
cities = CitySerializer(many=True)
class Meta: class Meta:
model = Region model = Region
fields = ["oid", "title", "description_short"] fields = ["oid", "title", "description_short", "cities"]
class InputRoutePointSerializer(serializers.Serializer):
type = serializers.ChoiceField(choices=["point", "transition"])
time = serializers.IntegerField(min_value=0, required=True)
# point
point = serializers.CharField(
min_length=24, max_length=24, required=False, allow_blank=True, allow_null=True
)
# transition
point_from = serializers.CharField(
min_length=24, max_length=24, required=False, allow_blank=True, allow_null=True
)
point_to = serializers.CharField(
min_length=24, max_length=24, required=False, allow_blank=True, allow_null=True
)
distance = serializers.FloatField(min_value=0, required=False, allow_null=True)
def validate(self, data):
if data["type"] == "point":
if "point" not in data or not data["point"]:
raise serializers.ValidationError("Point id is required")
get_object_or_404(BasePoint, oid=data["point"])
else:
if "point_to" not in data or not data["point_to"]:
raise serializers.ValidationError("Point to id is required")
get_object_or_404(BasePoint, oid=data["point_to"])
if "point_from" not in data or not data["point_from"]:
raise serializers.ValidationError("Point from id is required")
get_object_or_404(BasePoint, oid=data["point_from"])
if "distance" not in data or not data["distance"]:
raise serializers.ValidationError("Distance is required")
return data
class InputRouteSerializer(serializers.Serializer):
points = serializers.ListSerializer(child=InputRoutePointSerializer())

View File

@ -1,10 +1,17 @@
from django.urls import path from django.urls import path
from passfinder.events.api.views import BuildRouteApiView, ListRegionApiView from passfinder.events.api.views import (
BuildRouteApiView,
ListRegionApiView,
ListCityApiView,
SaveRouteSerializer,
)
app_name = "events" app_name = "events"
urlpatterns = [ urlpatterns = [
path("route/build", BuildRouteApiView.as_view(), name="build_route"), path("route/build", BuildRouteApiView.as_view(), name="build_route"),
path("regions", ListRegionApiView.as_view(), name="regions"), path("route/save", SaveRouteSerializer.as_view(), name="save_route"),
path("data/regions", ListRegionApiView.as_view(), name="regions"),
path("data/cities", ListCityApiView.as_view(), name="cities"),
] ]

View File

@ -7,8 +7,17 @@
RouteSerializer, RouteSerializer,
RegionSerializer, RegionSerializer,
RouteInputSerializer, RouteInputSerializer,
CitySerializer,
InputRouteSerializer,
)
from passfinder.events.models import (
BasePoint,
Region,
City,
UserRoute,
UserRoutePoint,
UserRouteTransaction,
) )
from passfinder.events.models import BasePoint, Region
class BuildRouteApiView(GenericAPIView): class BuildRouteApiView(GenericAPIView):
@ -21,6 +30,7 @@ def get(self, request):
routes.append( routes.append(
{ {
"name": "bebra", "name": "bebra",
"date": None,
"description": "bebra bebra bebra", "description": "bebra bebra bebra",
"points": PointSerializer(many=True).to_representation( "points": PointSerializer(many=True).to_representation(
BasePoint.objects.order_by("?")[:10] BasePoint.objects.order_by("?")[:10]
@ -35,7 +45,8 @@ def get(self, request):
def post(self, request): def post(self, request):
serializer = RouteInputSerializer(data=request.data) serializer = RouteInputSerializer(data=request.data)
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
region = serializer.data["region"] data = serializer.data
region = data["region"]
routes = [] routes = []
if region: if region:
region = get_object_or_404(Region, oid=region) region = get_object_or_404(Region, oid=region)
@ -43,6 +54,7 @@ def post(self, request):
routes.append( routes.append(
{ {
"name": "bebra", "name": "bebra",
"date": data["date_from"],
"description": "bebra bebra bebra", "description": "bebra bebra bebra",
"points": PointSerializer(many=True).to_representation( "points": PointSerializer(many=True).to_representation(
region.points.all().order_by("?")[:10] region.points.all().order_by("?")[:10]
@ -54,6 +66,7 @@ def post(self, request):
routes.append( routes.append(
{ {
"name": "bebra", "name": "bebra",
"date": data["date_from"],
"description": "bebra bebra bebra", "description": "bebra bebra bebra",
"points": PointSerializer(many=True).to_representation( "points": PointSerializer(many=True).to_representation(
BasePoint.objects.order_by("?")[:10] BasePoint.objects.order_by("?")[:10]
@ -66,3 +79,31 @@ def post(self, request):
class ListRegionApiView(ListAPIView): class ListRegionApiView(ListAPIView):
serializer_class = RegionSerializer serializer_class = RegionSerializer
queryset = Region.objects.all() queryset = Region.objects.all()
class ListCityApiView(ListAPIView):
serializer_class = CitySerializer
queryset = City.objects.all().order_by("title")
class SaveRouteSerializer(GenericAPIView):
serializer_class = InputRouteSerializer
def post(self, request, *args, **kwargs):
serializer = InputRouteSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
data = serializer.data
route = UserRoute.objects.create(user=self.request.user)
for point in data["points"]:
if point["type"] == "point":
UserRoutePoint.objects.create(
route=route, point=BasePoint.objects.get(oid=point["point"])
)
else:
UserRouteTransaction.objects.create(
route=route,
point_from=BasePoint.objects.get(oid=point["point_from"]),
point_to=BasePoint.objects.get(oid=point["point_to"]),
)
return Response(data=data)

View File

@ -0,0 +1,142 @@
# Generated by Django 4.2.1 on 2023-05-23 20:34
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
("contenttypes", "0002_remove_content_type_name"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
("events", "0017_event_address_event_extra_kwargs_and_more"),
]
operations = [
migrations.CreateModel(
name="UserRoute",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("created", models.DateTimeField(auto_now_add=True)),
(
"user",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="routes",
to=settings.AUTH_USER_MODEL,
),
),
],
),
migrations.CreateModel(
name="BaseUserRoutePoint",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"polymorphic_ctype",
models.ForeignKey(
editable=False,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="polymorphic_%(app_label)s.%(class)s_set+",
to="contenttypes.contenttype",
),
),
(
"route",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="points",
to="events.userroute",
),
),
],
options={
"abstract": False,
"base_manager_name": "objects",
},
),
migrations.CreateModel(
name="UserRouteTransaction",
fields=[
(
"baseuserroutepoint_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="events.baseuserroutepoint",
),
),
("distance", models.FloatField()),
(
"point_from",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="user_route_point_from",
to="events.basepoint",
),
),
(
"point_to",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="user_route_point_to",
to="events.basepoint",
),
),
],
options={
"abstract": False,
"base_manager_name": "objects",
},
bases=("events.baseuserroutepoint",),
),
migrations.CreateModel(
name="UserRoutePoint",
fields=[
(
"baseuserroutepoint_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="events.baseuserroutepoint",
),
),
(
"point",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="events.basepoint",
),
),
],
options={
"abstract": False,
"base_manager_name": "objects",
},
bases=("events.baseuserroutepoint",),
),
]

View File

@ -175,3 +175,35 @@ class Restaurant(BasePoint):
avg_time_visit = models.IntegerField(null=True) avg_time_visit = models.IntegerField(null=True)
working_time = models.JSONField(null=True) working_time = models.JSONField(null=True)
phones = ArrayField(models.CharField(max_length=18), null=True) phones = ArrayField(models.CharField(max_length=18), null=True)
class UserRoute(models.Model):
user = models.ForeignKey(
"users.User", related_name="routes", on_delete=models.CASCADE
)
created = models.DateTimeField(auto_now_add=True)
def __str__(self):
return f"{self.user}'s route"
class BaseUserRoutePoint(PolymorphicModel):
route = models.ForeignKey(
"UserRoute", related_name="points", on_delete=models.CASCADE
)
class UserRoutePoint(BaseUserRoutePoint):
type = "point"
point = models.ForeignKey("BasePoint", on_delete=models.CASCADE)
class UserRouteTransaction(BaseUserRoutePoint):
type = "transition"
point_from = models.ForeignKey(
"BasePoint", related_name="user_route_point_from", on_delete=models.CASCADE
)
point_to = models.ForeignKey(
"BasePoint", related_name="user_route_point_to", on_delete=models.CASCADE
)
distance = models.FloatField()

View File

@ -22,109 +22,146 @@ def get_nearest_(instance_model, model_type, mapping, rev_mapping, nearest_n, ml
for i in range(how_many): for i in range(how_many):
try: try:
res.append(Event.objects.get(oid=mapping[nearest[i]])) res.append(Event.objects.get(oid=mapping[nearest[i]]))
except Event.DoesNotExist: ... except Event.DoesNotExist:
if len(res) == nearest_n: break ...
if len(res) == nearest_n:
break
return res return res
def nearest_attraction(attraction, nearest_n): def nearest_attraction(attraction, nearest_n):
return get_nearest_(attraction, 'attraction', attraction_mapping, rev_attraction_mapping, nearest_n, attracion_model) return get_nearest_(
attraction,
"attraction",
attraction_mapping,
rev_attraction_mapping,
nearest_n,
attracion_model,
)
def nearest_movie(movie, nearest_n): def nearest_movie(movie, nearest_n):
return get_nearest_(movie, 'movie', cinema_mapping, rev_cinema_mapping, nearest_n, cinema_model) return get_nearest_(
movie, "movie", cinema_mapping, rev_cinema_mapping, nearest_n, cinema_model
)
def nearest_plays(play, nearest_n): def nearest_plays(play, nearest_n):
return get_nearest_(play, 'plays', plays_mapping, rev_plays_mapping, nearest_n, plays_model) return get_nearest_(
play, "plays", plays_mapping, rev_plays_mapping, nearest_n, plays_model
)
def nearest_excursion(excursion, nearest_n): def nearest_excursion(excursion, nearest_n):
return get_nearest_(excursion, 'excursion', excursion_mapping, rev_excursion_mapping, nearest_n, excursion_model) return get_nearest_(
excursion,
"excursion",
excursion_mapping,
rev_excursion_mapping,
nearest_n,
excursion_model,
)
def nearest_concert(concert, nearest_n): def nearest_concert(concert, nearest_n):
return get_nearest_(concert, 'concert', concert_mapping, rev_concert_mapping, nearest_n, concert_model) return get_nearest_(
concert,
"concert",
concert_mapping,
rev_concert_mapping,
nearest_n,
concert_model,
)
def get_nearest_event(event, nearest_n): def get_nearest_event(event, nearest_n):
if event.type == 'plays': if event.type == "plays":
return nearest_plays(event, nearest_n) return nearest_plays(event, nearest_n)
if event.type == 'concert': if event.type == "concert":
return nearest_concert(event, nearest_n) return nearest_concert(event, nearest_n)
if event.type == 'movie': if event.type == "movie":
return nearest_movie(event, nearest_n) return nearest_movie(event, nearest_n)
def update_preferences_state(user, event, direction): def update_preferences_state(user, event, direction):
pref = UserPreferences.objects.get(user=user) pref = UserPreferences.objects.get(user=user)
if direction == 'left': if direction == "left":
if event.type == 'plays': if event.type == "plays":
pref.unpreffered_plays.add(event) pref.unpreffered_plays.add(event)
if event.type == 'movie': if event.type == "movie":
pref.unpreffered_movies.add(event) pref.unpreffered_movies.add(event)
if event.type == 'concert': if event.type == "concert":
pref.unpreferred_concerts.add(event) pref.unpreferred_concerts.add(event)
else: else:
if event.type == 'plays': if event.type == "plays":
pref.preffered_plays.add(event) pref.preffered_plays.add(event)
if event.type == 'movie': if event.type == "movie":
pref.preffered_movies.add(event) pref.preffered_movies.add(event)
if event.type == 'concert': if event.type == "concert":
pref.preferred_concerts.add(event) pref.preferred_concerts.add(event)
pref.save() pref.save()
def get_next_tinder(user, prev_event, prev_direction): def get_next_tinder(user, prev_event, prev_direction):
pref = UserPreferences.objects.get(user=user) pref = UserPreferences.objects.get(user=user)
print(prev_event.type, len(pref.preferred_concerts.all())) print(prev_event.type, len(pref.preferred_concerts.all()))
if prev_direction == 'left': if prev_direction == "left":
if prev_event.type == 'plays' and len(pref.unpreffered_plays.all()) <= 2: if prev_event.type == "plays" and len(pref.unpreffered_plays.all()) <= 2:
candidates = nearest_plays(prev_event, 100) candidates = nearest_plays(prev_event, 100)
# print(candidates, type(candidates), len(Event.objects.filter(type='plays'))) # print(candidates, type(candidates), len(Event.objects.filter(type='plays')))
return candidates[-1] return candidates[-1]
if prev_event.type == 'movie' and len(pref.unpreffered_movies.all()) <= 2: if prev_event.type == "movie" and len(pref.unpreffered_movies.all()) <= 2:
candidates = nearest_movie(prev_event, 100) candidates = nearest_movie(prev_event, 100)
return candidates[-1] return candidates[-1]
if prev_event.type == 'concert' and len(pref.unpreferred_concerts.all()) <= 2: if prev_event.type == "concert" and len(pref.unpreferred_concerts.all()) <= 2:
candidates = nearest_concert(prev_event, 100) candidates = nearest_concert(prev_event, 100)
return candidates[-1] return candidates[-1]
if prev_direction == 'right': if prev_direction == "right":
if prev_event.type == 'plays' and len(pref.preffered_plays.all()) < 2: if prev_event.type == "plays" and len(pref.preffered_plays.all()) < 2:
candidates = nearest_plays(prev_event, 2) candidates = nearest_plays(prev_event, 2)
return candidates[1] return candidates[1]
if prev_event.type == 'movie' and len(pref.preffered_movies.all()) < 2: if prev_event.type == "movie" and len(pref.preffered_movies.all()) < 2:
candidates = nearest_movie(prev_event, 2) candidates = nearest_movie(prev_event, 2)
return candidates[1] return candidates[1]
if prev_event.type == 'concert' and len(pref.preferred_concerts.all()) < 2: if prev_event.type == "concert" and len(pref.preferred_concerts.all()) < 2:
candidates = nearest_concert(prev_event, 2) candidates = nearest_concert(prev_event, 2)
return candidates[1] return candidates[1]
if prev_event.type == 'plays': if prev_event.type == "plays":
if not len(pref.preffered_movies.all()) and not len(pref.unpreffered_movies.all()): if not len(pref.preffered_movies.all()) and not len(
return choice(Event.objects.filter(type='movie')) pref.unpreffered_movies.all()
if not len(pref.preferred_concerts.all()) and not len(pref.unpreferred_concerts.all()): ):
return choice(Event.objects.filter(type='concert')) return choice(Event.objects.filter(type="movie"))
if not len(pref.preferred_concerts.all()) and not len(
if prev_event.type == 'movie': pref.unpreferred_concerts.all()
if not len(pref.preffered_plays.all()) and not len(pref.unpreffered_plays.all()): ):
return choice(Event.objects.filter(type='plays')) return choice(Event.objects.filter(type="concert"))
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(
if prev_event.type == 'concert': pref.unpreffered_plays.all()
if not len(pref.preffered_plays.all()) and not len(pref.unpreffered_plays.all()): ):
return choice(Event.objects.filter(type='plays')) return choice(Event.objects.filter(type="plays"))
if not len(pref.preffered_movies.all()) and not len(pref.unpreffered_movies.all()): if not len(pref.preferred_concerts.all()) and not len(
return choice(Event.objects.filter(type='movie')) 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"))
return None return None
def rank_candidates(candidates_list, negative_candidates_list): def rank_candidates(candidates_list, negative_candidates_list):
flatten_c_list = [] flatten_c_list = []
ranks = {} ranks = {}
@ -133,21 +170,27 @@ def rank_candidates(candidates_list, negative_candidates_list):
for negative in negative_candidates_list: for negative in negative_candidates_list:
flatten_negatives.extend(negative) flatten_negatives.extend(negative)
for lst in candidates_list: for lst in candidates_list:
flatten_c_list.extend(lst) flatten_c_list.extend(lst)
for cand in lst: for cand in lst:
ranks.update({cand: {'rank': 0, 'lst': lst}}) ranks.update({cand: {"rank": 0, "lst": lst}})
cnt = Counter(flatten_c_list) cnt = Counter(flatten_c_list)
for candidate, how_many in cnt.most_common(len(flatten_c_list)): for candidate, how_many in cnt.most_common(len(flatten_c_list)):
ranks[candidate]['rank'] = how_many * (len(ranks[candidate]['lst']) - ranks[candidate]['lst'].index(candidate)) ranks[candidate]["rank"] = how_many * (
len(ranks[candidate]["lst"]) - ranks[candidate]["lst"].index(candidate)
)
res = [] res = []
for cand in ranks.keys(): for cand in ranks.keys():
res.append((ranks[cand]['rank'], cand)) res.append((ranks[cand]["rank"], cand))
return list(filter(lambda x: x[1] not in flatten_negatives, sorted(res, key=lambda x: -x[0]))) return list(
filter(
lambda x: x[1] not in flatten_negatives, sorted(res, key=lambda x: -x[0])
)
)
def get_personal_recommendation(prefer, unprefer): def get_personal_recommendation(prefer, unprefer):
@ -156,10 +199,12 @@ def get_personal_recommendation(prefer, unprefer):
for rec in prefer: for rec in prefer:
candidates.append(list(map(lambda x: x.oid, get_nearest_event(rec, 10)[1:]))) candidates.append(list(map(lambda x: x.oid, get_nearest_event(rec, 10)[1:])))
for neg in unprefer: for neg in unprefer:
negative_candidates.append(list(map(lambda x: x.oid, get_nearest_event(neg, 10)[1:]))) negative_candidates.append(
list(map(lambda x: x.oid, get_nearest_event(neg, 10)[1:]))
)
ranked = rank_candidates(candidates, negative_candidates) ranked = rank_candidates(candidates, negative_candidates)
return list(map(lambda x: (x[0], Event.objects.get(oid=x[1])), ranked[0:5])) return list(map(lambda x: (x[0], Event.objects.get(oid=x[1])), ranked[0:5]))
@ -189,7 +234,6 @@ def get_personal_movies_recommendation(user):
return get_personal_recommendation(prefer, unprefer) return get_personal_recommendation(prefer, unprefer)
def dist_func(event1: Event, event2: Event): def dist_func(event1: Event, event2: Event):
# cords1 = [event1.lat, event1.lon] # cords1 = [event1.lat, event1.lon]
# cords2 = [event2.lat, event2.lon] # cords2 = [event2.lat, event2.lon]
@ -205,7 +249,9 @@ def generate_nearest():
NearestEvent.objects.all().delete() NearestEvent.objects.all().delete()
all_events = list(Event.objects.all()) all_events = list(Event.objects.all())
for i, event in enumerate(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))) event_all_events = list(
sorted(all_events.copy(), key=lambda x: dist_func(event, x))
)
nearest = NearestEvent.objects.create(event=event) nearest = NearestEvent.objects.create(event=event)
nearest.nearest.set(event_all_events[0:100]) nearest.nearest.set(event_all_events[0:100])
nearest.save() nearest.save()
@ -218,12 +264,15 @@ def generate_hotel_nearest():
all_events = list(Event.objects.all()) all_events = list(Event.objects.all())
hotels = list(Hotel.objects.all()) hotels = list(Hotel.objects.all())
for i, hotel in enumerate(hotels): for i, hotel in enumerate(hotels):
event_all_events = list(sorted(all_events.copy(), key=lambda x: dist_func(hotel, x))) event_all_events = list(
sorted(all_events.copy(), key=lambda x: dist_func(hotel, x))
)
nearest = NearestHotel.objects.create(hotel=hotel) nearest = NearestHotel.objects.create(hotel=hotel)
nearest.nearest_events.set(event_all_events[0:100]) nearest.nearest_events.set(event_all_events[0:100])
if i % 100 == 0: if i % 100 == 0:
print(i) print(i)
def match_points(): def match_points():
regions = list(City.objects.all()) regions = list(City.objects.all())
for i, point in enumerate(Event.objects.all()): for i, point in enumerate(Event.objects.all()):
@ -234,11 +283,15 @@ def match_points():
print(i) print(i)
def calculate_mean_metric(
def calculate_mean_metric(favorite_events: Iterable[Event], target_event: Event, model: AnnoyIndex, rev_mapping): favorite_events: Iterable[Event],
target_event: Event,
model: AnnoyIndex,
rev_mapping,
):
if not len(favorite_events): if not len(favorite_events):
return 100000 return 100000
dists = [] dists = []
target_event_idx = rev_mapping[target_event.oid] target_event_idx = rev_mapping[target_event.oid]
for fav in favorite_events: for fav in favorite_events:
@ -248,45 +301,34 @@ def calculate_mean_metric(favorite_events: Iterable[Event], target_event: Event,
def calculate_favorite_metric(event: Event, user: User): def calculate_favorite_metric(event: Event, user: User):
pref = UserPreferences.objects.get(user=user) pref = UserPreferences.objects.get(user=user)
if event.type == 'plays': if event.type == "plays":
preferred = pref.preffered_plays.all() preferred = pref.preffered_plays.all()
return calculate_mean_metric( return calculate_mean_metric(preferred, event, plays_model, rev_plays_mapping)
preferred, if event.type == "concert":
event,
plays_model,
rev_plays_mapping
)
if event.type == 'concert':
preferred = pref.preferred_concerts.all() preferred = pref.preferred_concerts.all()
return calculate_mean_metric( return calculate_mean_metric(
preferred, preferred, event, concert_model, rev_concert_mapping
event,
concert_model,
rev_concert_mapping
) )
if event.type == 'movie': if event.type == "movie":
preferred = pref.preffered_movies.all() preferred = pref.preffered_movies.all()
return calculate_mean_metric( return calculate_mean_metric(preferred, event, cinema_model, rev_cinema_mapping)
preferred,
event,
cinema_model,
rev_cinema_mapping
)
return 1000000 return 1000000
def get_nearest_favorite(events: Iterable[Event], user: User, exclude_events: Iterable[Event]=[]): def get_nearest_favorite(
events: Iterable[Event], user: User, exclude_events: Iterable[Event] = []
):
first_event = None first_event = None
for candidate in events: for candidate in events:
if candidate not in exclude_events: if candidate not in exclude_events:
first_event = candidate first_event = candidate
break break
result = first_event result = first_event
result_min = calculate_favorite_metric(result, user) result_min = calculate_favorite_metric(result, user)
for event in events: for event in events:
if event in exclude_events: continue if event in exclude_events:
continue
local_min_metric = calculate_favorite_metric(event, user) local_min_metric = calculate_favorite_metric(event, user)
if local_min_metric < result_min: if local_min_metric < result_min:
result_min = local_min_metric result_min = local_min_metric
@ -312,7 +354,7 @@ def generate_route(point1: BasePoint, point2: BasePoint):
"from": point1, "from": point1,
"to": point2, "to": point2,
"distance": distance, "distance": distance,
"time": time "time": time,
} }
@ -321,12 +363,12 @@ def generate_point(point: BasePoint):
"type": "point", "type": "point",
"point": point, "point": point,
"point_type": "", "point_type": "",
"time": timedelta(minutes=90+choice(range(-80, 90, 10))) "time": timedelta(minutes=90 + choice(range(-80, 90, 10))),
} }
def generate_path(region: Region, user: User): def generate_path(region: Region, user: User):
#region_events = Event.objects.filter(region=region) # region_events = Event.objects.filter(region=region)
hotel = filter_hotel(region, user, []) hotel = filter_hotel(region, user, [])
@ -345,12 +387,12 @@ def generate_path(region: Region, user: User):
while start_time.hour < 22: while start_time.hour < 22:
candidates = NearestEvent.objects.get(event=points[-1]).nearest.all() candidates = NearestEvent.objects.get(event=points[-1]).nearest.all()
points.append(get_nearest_favorite(candidates, user, points)) points.append(get_nearest_favorite(candidates, user, points))
transition_route = generate_route(points[-1], points[-2]) transition_route = generate_route(points[-1], points[-2])
start_time += transition_route['time'] start_time += transition_route["time"]
point_route = generate_point(points[-1]) point_route = generate_point(points[-1])
start_time += point_route['time'] start_time += point_route["time"]
path.extend([transition_route, point_route]) path.extend([transition_route, point_route])
return hotel, points, path return hotel, points, path

View File

@ -11,6 +11,9 @@
UserRegisterSerializer, UserRegisterSerializer,
UserPreferenceSerializer, UserPreferenceSerializer,
) )
from ..models import UserPreference
from ...events.api.serializers import PointSerializer
from ...events.models import BasePoint
User = get_user_model() User = get_user_model()
@ -45,3 +48,12 @@ def post(self, request, *args, **kwargs):
class CreateUserPreferenceApiView(generics.CreateAPIView): class CreateUserPreferenceApiView(generics.CreateAPIView):
serializer_class = UserPreferenceSerializer serializer_class = UserPreferenceSerializer
class ListUserFavoritePointsApiView(generics.ListAPIView):
serializer_class = PointSerializer
def get_queryset(self):
return BasePoint.objects.filter(
user_preferences__user=self.request.user, user_preferences__type="favorite"
)

38
poetry.lock generated
View File

@ -1274,6 +1274,42 @@ prometheus-client = ">=0.8.0"
pytz = "*" pytz = "*"
tornado = ">=5.0.0,<7.0.0" tornado = ">=5.0.0,<7.0.0"
[[package]]
name = "geographiclib"
version = "2.0"
description = "The geodesic routines from GeographicLib"
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "geographiclib-2.0-py3-none-any.whl", hash = "sha256:6b7225248e45ff7edcee32becc4e0a1504c606ac5ee163a5656d482e0cd38734"},
{file = "geographiclib-2.0.tar.gz", hash = "sha256:f7f41c85dc3e1c2d3d935ec86660dc3b2c848c83e17f9a9e51ba9d5146a15859"},
]
[[package]]
name = "geopy"
version = "2.3.0"
description = "Python Geocoding Toolbox"
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "geopy-2.3.0-py3-none-any.whl", hash = "sha256:4a29a16d41d8e56ba8e07310802a1cbdf098eeb6069cc3d6d3068fc770629ffc"},
{file = "geopy-2.3.0.tar.gz", hash = "sha256:228cd53b6eef699b2289d1172e462a90d5057779a10388a7366291812601187f"},
]
[package.dependencies]
geographiclib = ">=1.52,<3"
[package.extras]
aiohttp = ["aiohttp"]
dev = ["coverage", "flake8 (>=5.0,<5.1)", "isort (>=5.10.0,<5.11.0)", "pytest (>=3.10)", "pytest-asyncio (>=0.17)", "readme-renderer", "sphinx (<=4.3.2)", "sphinx-issues", "sphinx-rtd-theme (>=0.5.0)"]
dev-docs = ["readme-renderer", "sphinx (<=4.3.2)", "sphinx-issues", "sphinx-rtd-theme (>=0.5.0)"]
dev-lint = ["flake8 (>=5.0,<5.1)", "isort (>=5.10.0,<5.11.0)"]
dev-test = ["coverage", "pytest (>=3.10)", "pytest-asyncio (>=0.17)", "sphinx (<=4.3.2)"]
requests = ["requests (>=2.16.2)", "urllib3 (>=1.24.2)"]
timezone = ["pytz"]
[[package]] [[package]]
name = "humanize" name = "humanize"
version = "4.6.0" version = "4.6.0"
@ -3079,4 +3115,4 @@ testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = "^3.8" python-versions = "^3.8"
content-hash = "c715e551faec63d192743332fc0fd568147d4f164ff58a2ad843a7c10a5f4799" content-hash = "2f8d96eb5b506fcf8f61c7ba8bfd68a0b740e9b06b5f32d9a22afb84fd480f2f"

View File

@ -53,6 +53,7 @@ django-filter = "^23.2"
djangorestframework-simplejwt = "^5.2.2" djangorestframework-simplejwt = "^5.2.2"
beautifulsoup4 = "^4.12.2" beautifulsoup4 = "^4.12.2"
django-clickhouse = "^1.2.1" django-clickhouse = "^1.2.1"
geopy = "^2.3.0"
[build-system] [build-system]