Skip to content

Commit

Permalink
Merge pull request #26 from hackathone-prosept-team2/feat/add_del_prices
Browse files Browse the repository at this point in the history
Feat/add del prices
  • Loading branch information
ratarov authored Dec 5, 2023
2 parents 7b15e5e + ee8d457 commit bfbb7ee
Show file tree
Hide file tree
Showing 11 changed files with 106 additions and 38 deletions.
18 changes: 15 additions & 3 deletions apps/api/v1/dealers/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@
from django.db.models import Q

from apps.dealers.models import DealerKey
from config.constants import KeyStatus, MATCH_NUMBER


class DealerKeyFilter(filters.FilterSet):
article = filters.CharFilter(method="get_article")
status = filters.CharFilter(method="get_status")
# similarity = filters.NumberFilter(method="get_similarity")

class Meta:
model = DealerKey
Expand All @@ -24,6 +26,16 @@ def get_status(self, queryset, name, value):
"""Поиск по статусу."""
if not value:
return queryset
if value == "-":
return queryset.filter(status=101)
return queryset.exclude(status=101)
if value == KeyStatus.FOUND:
return queryset.filter(product_id__isnull=False)
if value == KeyStatus.DECLINED:
return queryset.filter(declined=MATCH_NUMBER)
return queryset.filter(
product_id__isnull=True, declined__lt=MATCH_NUMBER
)

# def get_similarity(self, queryset, name, value):
# """Поиск по показателю схожести."""
# if value:
# return queryset.filter(similarity__gte=value)
# return queryset
7 changes: 5 additions & 2 deletions apps/api/v1/dealers/schema.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
from drf_spectacular.utils import extend_schema, OpenApiParameter

from . import serializer as ser
from config.constants import KeyStatus

from . import serializer as ser

dealer_schema = {
"list": extend_schema(description="Получение списка дилеров."),
"retrieve": extend_schema(description="Просмотр экземпляра дилера."),
}


filter_options = [KeyStatus.CHECK, KeyStatus.DECLINED, KeyStatus.FOUND]

key_schema = {
"list": extend_schema(
description="Получение списка уникальных ключей дилеров.",
Expand All @@ -23,7 +26,7 @@
),
OpenApiParameter(
name="status",
description="Фильтр по статусу; варианты: '-', '0', '1+'",
description=f"Фильтр по статусу: {filter_options}",
),
OpenApiParameter(
name="page",
Expand Down
24 changes: 15 additions & 9 deletions apps/api/v1/dealers/serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from apps.dealers.models import Dealer, DealerKey, Match
from apps.products.crud import product_exists
from config.constants import MATCH_NUMBER, KeyStatus

from ..products.serializer import ProductShortSerializer

Expand Down Expand Up @@ -29,7 +30,7 @@ class DealerReportSerializer(BaseDealerSerializer):
keys_without_product = serializers.SerializerMethodField()
confirmed_matches = serializers.IntegerField()
to_be_checked = serializers.IntegerField()
no_matches = serializers.IntegerField()
no_matches = serializers.SerializerMethodField()

class Meta(BaseDealerSerializer.Meta):
fields = BaseDealerSerializer.Meta.fields + (
Expand All @@ -45,6 +46,9 @@ class Meta(BaseDealerSerializer.Meta):
def get_keys_without_product(self, obj):
return obj.total_keys - obj.keys_with_product

def get_no_matches(self, obj):
return obj.total_keys - obj.keys_with_product - obj.to_be_checked


class KeySerializer(serializers.ModelSerializer):
"""Сериализатор для полей Ключей/артикулов Дилера."""
Expand All @@ -53,8 +57,8 @@ class KeySerializer(serializers.ModelSerializer):
product = ProductShortSerializer()
name = serializers.CharField()
last_price = serializers.DecimalField(max_digits=7, decimal_places=2)
similarity = serializers.SerializerMethodField()
# status = serializers.IntegerField()
# similarity = serializers.IntegerField()
status = serializers.SerializerMethodField()

class Meta:
model = DealerKey
Expand All @@ -63,16 +67,18 @@ class Meta:
"key",
"name",
"last_price",
# "status",
"similarity",
"status",
# "similarity",
"dealer",
"product",
)

def get_similarity(self, obj):
if obj.status == 101:
return "-"
return obj.status
def get_status(self, obj):
if obj.product_id is not None:
return KeyStatus.FOUND
if obj.declined == MATCH_NUMBER:
return KeyStatus.DECLINED
return KeyStatus.CHECK


class MatchSerializer(serializers.ModelSerializer):
Expand Down
4 changes: 3 additions & 1 deletion apps/api/v1/prices/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from rest_framework import views, status
from rest_framework.response import Response

from apps.prices.crud import list_key_prices
from apps.prices.crud import list_key_prices, there_are_prices_in_db
from apps.prices.services import delete_prices_and_relations, create_prices

from ..pagination import NestedPagePagination
Expand All @@ -24,6 +24,8 @@ class PricesView(views.APIView):
"""Загрузка и удаление цен дилеров и связанных ключей дилеров."""

def post(self, request):
if there_are_prices_in_db():
return Response(status=status.HTTP_400_BAD_REQUEST)
create_prices()
return Response(status=status.HTTP_201_CREATED)

Expand Down
36 changes: 18 additions & 18 deletions apps/dealers/crud.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@
QuerySet,
OuterRef,
Subquery,
Case,
When,
Value,
Count,
Q,
Prefetch,
Expand Down Expand Up @@ -38,10 +35,16 @@ def list_dealers_report_data() -> QuerySet[Dealer]:
& Q(dealer_keys__prices__isnull=False)
),
),
# TODO добавить код, когда будут модели рекомендаций
confirmed_matches=Value(1),
to_be_checked=Value(1),
no_matches=Value(1),
confirmed_matches=Count(
"dealer_keys",
distinct=True,
filter=Q(dealer_keys__matches__status=Match.MatchStatus.YES),
),
to_be_checked=Count(
"dealer_keys",
distinct=True,
filter=Q(dealer_keys__matches__status=Match.MatchStatus.NEW),
),
)


Expand All @@ -59,15 +62,14 @@ def list_keys() -> QuerySet[DealerKey]:
"price"
)[:1]
),
status=Case(
When(product__isnull=False, then=Value(101)),
# TODO код для расчета статуса по рекомендациям
default=Subquery(
Match.objects.filter(key_id=OuterRef("pk")).values(
"similarity"
)[:1]
),
declined=Count(
"matches", filter=Q(matches__status=Match.MatchStatus.NO)
),
# similarity=Subquery(
# Match.objects.filter(key_id=OuterRef("pk")).values(
# "similarity"
# )[:1]
# ),
)
# фильтр позволяет выгружать только ключи, которые есть в списке цен
.filter(name__isnull=False)
Expand All @@ -76,9 +78,7 @@ def list_keys() -> QuerySet[DealerKey]:

def list_matches(key_pk: int, add_products: bool = True) -> QuerySet[Match]:
"""Получение списка возможных соответствий Ключ - Продукт."""
subquery = Match.objects.filter(key_id=key_pk)
if add_products:
subquery = subquery.select_related("product")
subquery = Match.objects.filter(key_id=key_pk).select_related("product")
query = DealerKey.objects.prefetch_related(
Prefetch(
"matches",
Expand Down
6 changes: 4 additions & 2 deletions apps/dealers/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,16 @@

@atomic
def decline_matches(key_pk: int) -> QuerySet[Match]:
matches = list_matches(key_pk=key_pk, add_products=False)
"""Сменить всем предложениям статус на "Не подходит"."""
matches = list_matches(key_pk=key_pk)
change_status_to_declined(matches=matches)
return matches


@atomic
def choose_match(key_pk: int, product_id: int) -> QuerySet[Match]:
matches = list_matches(key_pk=key_pk, add_products=False)
"""Выбрать продукт из списка предложений."""
matches = list_matches(key_pk=key_pk)
choose_one_decline_others(matches=matches, product_id=product_id)
set_product_for_dealer_key(dealer_key_id=key_pk, product_id=product_id)
return matches
5 changes: 5 additions & 0 deletions apps/prices/crud.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ def list_prices() -> QuerySet[DealerPrice]:
)


def there_are_prices_in_db() -> bool:
"""Проверяет наличие в БД загруженных цен."""
return DealerPrice.objects.exists()


def delete_all_prices() -> None:
"""Удаление всех цен."""
DealerPrice.objects.all().delete()
Expand Down
24 changes: 24 additions & 0 deletions apps/prices/migrations/0002_load_prices.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Generated by Django 4.2.7 on 2023-11-29 13:20

from django.db import migrations, models

from apps.services.fixtures_parser import get_prices_datasets


def create_prices(apps, schema_editor):
DealerPrice: models.Model = apps.get_model("prices", "DealerPrice")
prices_datasets = get_prices_datasets()
if prices_datasets:
prices = [DealerPrice(**fields) for fields in prices_datasets]
DealerPrice.objects.bulk_create(prices, ignore_conflicts=True)


class Migration(migrations.Migration):
dependencies = [
("prices", "0001_initial"),
("dealers", "0003_dealerkey_unique_pair_dealer_and_key"),
]

operations = [
migrations.RunPython(create_prices),
]
2 changes: 1 addition & 1 deletion apps/prices/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,14 +70,14 @@ def create_prices() -> None:
dealer_id=dataset["dealer_id"],
id=id_counter,
)
fields = form_price_obj_fields(id=dealer_key.id, dataset=dataset)

if created:
id_counter += 1
keys_to_match[dealer_key.id] = dataset["product_name"]
elif dealer_key.id in keys_to_match:
keys_to_match[dealer_key.id] = dataset["product_name"]

fields = form_price_obj_fields(id=dealer_key.id, dataset=dataset)
prices_datasets.append(fields)
recommend_products(key_datasets=keys_to_match)
prices_bulk_create(fields_sets=prices_datasets)
Expand Down
4 changes: 3 additions & 1 deletion apps/services/match_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics import pairwise_distances

from config.constants import MATCH_NUMBER


class RecommendationService:
def __init__(
Expand Down Expand Up @@ -94,7 +96,7 @@ def _matching_names(
return df

def get_recommendations(
self, dealer_name: str, recommendations_number: int = 10
self, dealer_name: str, recommendations_number: int = MATCH_NUMBER
) -> list[list[float, float]]:
"""Получение рекомендаций."""
# получаем ключи по названию
Expand Down
14 changes: 13 additions & 1 deletion config/constants.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,16 @@
from dataclasses import dataclass

# Настройки общей пагинации
COMMON_PAGE = 50
# Настройки вложенной в объект пагинации (история цен)
NESTED_PAGE = 3
NESTED_PAGE = 10
# Кол-во выводимых предложений аналогов
MATCH_NUMBER = 10


# Статусы ключей дилеров
@dataclass
class KeyStatus:
CHECK = "На проверку"
DECLINED = "Не подходит"
FOUND = "Подтверждено"

0 comments on commit bfbb7ee

Please sign in to comment.