diff --git a/.gitignore b/.gitignore index 4850597..f5acaed 100644 --- a/.gitignore +++ b/.gitignore @@ -87,6 +87,7 @@ celerybeat-schedule *.sage.py # Environments +*.env .env .venv env/ diff --git a/environ.env b/environ.env deleted file mode 100644 index 223f969..0000000 --- a/environ.env +++ /dev/null @@ -1,10 +0,0 @@ -CONTAINERS_NAME=rsrv -# The name of the image to use for the containers -DJANGO_PORT=8000 -# The django container settings and enviroment -POSTGRES_DB=rsrv_db -POSTGRES_HOST=localhost -POSTGRES_PORT=5435 -POSTGRES_USER=rsrv_user -POSTGRES_PASSWORD=rsrv_pass -DEBUG=True \ No newline at end of file diff --git a/postamates/settings.py b/postamates/settings.py index 83a3fe4..229a088 100644 --- a/postamates/settings.py +++ b/postamates/settings.py @@ -166,4 +166,4 @@ REST_REGISTRATION = { 'REGISTER_EMAIL_VERIFICATION_URL': f'{FRONTEND_URL}verify-email/', 'VERIFICATION_FROM_EMAIL': 'noreply@spatiality.website', -} \ No newline at end of file +} diff --git a/requirements.txt b/requirements.txt index 6d87448..125dae3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,21 +1,61 @@ -catboost +asgiref==3.6.0 +attrs==22.2.0 +catboost==1.1.1 +certifi==2022.12.7 +charset-normalizer==3.0.1 +click==8.1.3 +click-plugins==1.1.1 +cligj==0.7.2 +coreapi==2.3.3 +coreschema==0.0.4 +cycler==0.11.0 Django==3.2 +django-cors-headers==3.12.0 +django-json-widget==1.1.1 +django-rest-registration==0.7.3 djangorestframework==3.11.1 -psycopg2-binary==2.9.3 -pandas==1.4.2 -xlrd==1.2.0 -tqdm==4.64.0 +drf-yasg==1.21.5 +et-xmlfile==1.1.0 +Fiona==1.9.1 +fonttools==4.38.0 +future==0.18.3 +geopandas==0.12.2 +graphviz==0.20.1 +greenlet==2.0.2 gunicorn==19.9.0 -django-cors-headers==3.12.0 -pyshp==2.3.0 +idna==3.4 +inflection==0.5.1 +itypes==1.2.0 +Jinja2==3.1.2 +kiwisolver==1.4.4 +MarkupSafe==2.1.2 matplotlib==3.5.2 +munch==2.5.0 +numpy==1.24.2 openpyxl==3.0.10 -shapely -pygeos -xlsxwriter -django-json-widget -django-rest-registration -geopandas -sqlalchemy -numpy -drf-yasg \ No newline at end of file +packaging==23.0 +pandas==1.4.2 +Pillow==9.4.0 +plotly==5.13.0 +psycopg2-binary==2.9.3 +pygeos==0.14 +pyparsing==3.0.9 +pyproj==3.4.1 +pyshp==2.3.0 +python-dateutil==2.8.2 +pytz==2022.7.1 +requests==2.28.2 +ruamel.yaml==0.17.21 +ruamel.yaml.clib==0.2.7 +scipy==1.10.0 +shapely==2.0.1 +six==1.16.0 +SQLAlchemy==2.0.3 +sqlparse==0.4.3 +tenacity==8.2.1 +tqdm==4.64.0 +typing_extensions==4.5.0 +uritemplate==4.1.1 +urllib3==1.26.14 +xlrd==1.2.0 +XlsxWriter==3.0.8 diff --git a/service/admin.py b/service/admin.py index b312562..219c62d 100644 --- a/service/admin.py +++ b/service/admin.py @@ -1,3 +1,4 @@ from django.contrib import admin from django.db import models - +from service.models import PlacementPoint +admin.site.register(PlacementPoint) diff --git a/service/migrations/0001_initial.py b/service/migrations/0001_initial.py new file mode 100644 index 0000000..7736c16 --- /dev/null +++ b/service/migrations/0001_initial.py @@ -0,0 +1,33 @@ +# Generated by Django 3.2 on 2023-02-18 15:37 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='PlacementPoint', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('location_id', models.IntegerField(blank=True, null=True, verbose_name='Id локации')), + ('lon', models.FloatField(blank=True, null=True, verbose_name='Долгота')), + ('lat', models.FloatField(blank=True, null=True, verbose_name='Широта')), + ('status', models.TextField(blank=True, null=True, verbose_name='Статус')), + ('category', models.TextField(blank=True, null=True, verbose_name='Категория')), + ('prediction', models.IntegerField(blank=True, null=True, verbose_name='Прогноз')), + ('age', models.IntegerField(blank=True, null=True, verbose_name='Возраст')), + ('plan', models.IntegerField(blank=True, null=True, verbose_name='Плановый показатель')), + ('fact', models.IntegerField(blank=True, null=True, verbose_name='Фактический показатель')), + ('delta', models.IntegerField(blank=True, null=True, verbose_name='Разница')), + ('sample_trn', models.IntegerField(blank=True, null=True)), + ('okrug', models.TextField(blank=True, null=True, verbose_name='Округ')), + ('rayon', models.TextField(blank=True, null=True, verbose_name='Район')), + ], + ), + ] diff --git a/service/migrations/__init__.py b/service/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/service/models.py b/service/models.py new file mode 100644 index 0000000..ad18b10 --- /dev/null +++ b/service/models.py @@ -0,0 +1,17 @@ +from django.db import models + + +class PlacementPoint(models.Model): + location_id = models.IntegerField(null=True, blank=True, verbose_name='Id локации') + lon = models.FloatField(verbose_name="Долгота", null=True, blank=True) + lat = models.FloatField(verbose_name="Широта", null=True, blank=True) + status = models.TextField(null=True, blank=True, verbose_name='Статус') + category = models.TextField(null=True, blank=True, verbose_name='Категория') + prediction = models.IntegerField(null=True, blank=True, verbose_name='Прогноз') + age = models.IntegerField(null=True, blank=True, verbose_name='Возраст') + plan = models.IntegerField(null=True, blank=True, verbose_name='Плановый показатель') + fact = models.IntegerField(null=True, blank=True, verbose_name='Фактический показатель') + delta = models.IntegerField(null=True, blank=True, verbose_name='Разница') + sample_trn = models.IntegerField(null=True, blank=True) + okrug = models.TextField(null=True, blank=True, verbose_name='Округ') + rayon = models.TextField(null=True, blank=True, verbose_name='Район') diff --git a/service/pagination.py b/service/pagination.py new file mode 100644 index 0000000..ca0213e --- /dev/null +++ b/service/pagination.py @@ -0,0 +1,6 @@ +from rest_framework.pagination import PageNumberPagination + + +class MyPagination(PageNumberPagination): + page_size = 100 + page_size_query_param = 'page_size' \ No newline at end of file diff --git a/service/serializers.py b/service/serializers.py new file mode 100644 index 0000000..2647538 --- /dev/null +++ b/service/serializers.py @@ -0,0 +1,8 @@ +from rest_framework import serializers +from service import models + + +class PlacementPointSerializer(serializers.ModelSerializer): + class Meta: + model = models.PlacementPoint + fields = '__all__' \ No newline at end of file diff --git a/service/urls.py b/service/urls.py index 5757bab..036ce69 100644 --- a/service/urls.py +++ b/service/urls.py @@ -1,10 +1,12 @@ -from django.urls import path, re_path +from django.urls import path, re_path, include from service import views -from rest_framework import permissions +from rest_framework import permissions, routers from drf_yasg.views import get_schema_view from drf_yasg import openapi +router = routers.DefaultRouter() +router.register('', views.PlacementPointViewSet) schema_view = get_schema_view( openapi.Info( @@ -21,6 +23,7 @@ schema_view = get_schema_view( ) urlpatterns = [ + path('placement_points', include(router.urls), name='placement_points'), path('ao_and_rayons/', views.ao_and_rayons.as_view(), name='ao_and_rayons'), path('raschet/', views.raschet.as_view(), name='ao_and_rayons'), re_path(r'^swagger(?P\.json|\.yaml)$', schema_view.without_ui(cache_timeout=0), name='schema-json'), diff --git a/service/utils.py b/service/utils.py index f7cd975..25c24a3 100644 --- a/service/utils.py +++ b/service/utils.py @@ -1,6 +1,8 @@ import pandas as pd from django.conf import settings import sqlalchemy +from service import models +from tqdm import tqdm def raschet_real(df, koefs): koef_summ = sum(koefs.values()) @@ -50,3 +52,14 @@ def raschet(tables, filters, koefs, method): nets_df = nets[(nets['model'] >= rate_from) & (nets['model'] <= rate_to)] return points_df, nets_df + + +# ============== + +def load_data(filepath: str): + models.PlacementPoint.objects.all().delete() + df = pd.read_csv(filepath).iloc[:,1:].to_dict('records') + for data in tqdm(df, desc='Loading data...'): + dt = models.PlacementPoint(**data) + dt.save() + diff --git a/service/views.py b/service/views.py index a16ef37..47c2767 100644 --- a/service/views.py +++ b/service/views.py @@ -7,12 +7,29 @@ import pandas as pd from io import BytesIO from django.http import HttpResponse from rest_framework.generics import GenericAPIView +from rest_framework.viewsets import ReadOnlyModelViewSet +from service import serializers, models, pagination +import json +from io import BytesIO + +import pandas as pd +from django.core.cache import cache +from django.http import HttpResponse +from rest_framework import permissions +from rest_framework.generics import GenericAPIView +from rest_framework.response import Response +from rest_framework.viewsets import ReadOnlyModelViewSet + +from service import serializers, models, pagination +from service.utils import raschet as raschet_alg + def rename_result_dataset(dataframe, rename_dict, reverse=False): if reverse: return dataframe.rename(columns={v: k for k, v in rename_dict.items()}) return dataframe.rename(columns=rename_dict) + class ao_and_rayons(GenericAPIView): permission_classes = [permissions.AllowAny] @@ -54,9 +71,53 @@ class raschet(GenericAPIView): "dk": "дом культуры и клуб", "sport": "спортивный объект", } - RAYONS_RENAME_DICT = {1: 'район Филёвский Парк', 2: 'район Зюзино', 3: 'район Внуково', 4: 'Алтуфьевский район', 5: 'Дмитровский район', 6: 'Можайский район', 7: 'район Ново-Переделкино', 8: 'район Щукино', 9: 'Молжаниновский район', 10: 'район Митино', 11: 'район Куркино', 12: 'район Аэропорт', 13: 'район Строгино', 14: 'район Крылатское', 15: 'район Тёплый Стан', 16: 'район Солнцево', 57: 'Бутырский район', 17: 'район Южное Тушино', 18: 'район Северное Тушино', 19: 'район Покровское-Стрешнево', 20: 'район Хорошёво-Мнёвники', 21: 'район Очаково-Матвеевское', 22: 'район Тропарёво-Никулино', 23: 'район Левобережный', 24: 'район Фили-Давыдково', 25: 'район Ховрино', 26: 'Головинский район', 27: 'район Раменки', 28: 'Войковский район', 29: 'район Западное Дегунино', 30: 'район Сокол', 31: 'район Проспект Вернадского', 32: 'район Южное Бутово', 33: 'Обручевский район', 34: 'район Ясенево', 35: 'район Дорогомилово', 36: 'район Коньково', 37: 'Хорошёвский район', 38: 'район Северный', 39: 'район Коптево', 40: 'Ломоносовский район', 58: 'район Марфино', 59: 'Тверской район', 41: 'Пресненский район', 74: 'район Нагатино-Садовники', 42: 'Бескудниковский район', 43: 'Гагаринский район', 44: 'район Черёмушки', 45: 'Тимирязевский район', 46: 'район Беговой', 47: 'район Хамовники', 48: 'район Северное Бутово', 49: 'район Лианозово', 50: 'район Восточное Дегунино', 51: 'Савёловский район', 52: 'Академический район', 53: 'район Чертаново Центральное', 54: 'район Отрадное', 55: 'район Арбат', 56: 'район Чертаново Южное', 60: 'район Чертаново Северное', 61: 'район Якиманка', 62: 'район Котловка', 63: 'Останкинский район', 64: 'Донской район', 65: 'район Бибирево', 66: 'район Марьина Роща', 67: 'Нагорный район', 68: 'Даниловский район', 73: 'район Северное Медведково', 69: 'Мещанский район', 70: 'район Бирюлёво Западное', 71: 'район Южное Медведково', 72: 'район Замоскворечье', 75: 'район Царицыно', 76: 'район Москворечье-Сабурово', 77: 'район Свиблово', 78: 'район Нагатинский Затон', 82: 'Таганский район', 79: 'Басманный район', 80: 'Красносельский район', 81: 'район Ростокино', 83: 'Алексеевский район', 84: 'район Восточный', 87: 'Бабушкинский район', 85: 'район Бирюлёво Восточное', 86: 'Южнопортовый район', 88: 'район Сокольники', 89: 'Ярославский район', 90: 'район Богородское', 91: 'район Метрогородок', 92: 'район Лефортово', 93: 'район Орехово-Борисово Северное', 94: 'район Печатники', 95: 'Лосиноостровский район', 98: 'район Новогиреево', 96: 'Нижегородский район', 99: 'район Марьино', 97: 'район Орехово-Борисово Южное', 100: 'район Преображенское', 105: 'район Братеево', 101: 'район Соколиная Гора', 102: 'район Перово', 103: 'район Люблино', 104: 'район Текстильщики', 106: 'район Зябликово', 107: 'Рязанский район', 108: 'район Измайлово', 109: 'район Северное Измайлово', 113: 'район Капотня', 110: 'район Кузьминки', 111: 'район Гольяново', 112: 'район Вешняки', 114: 'район Выхино-Жулебино', 115: 'район Восточное Измайлово', 118: 'район Новокосино', 116: 'район Ивановское', 117: 'район Косино-Ухтомский', 119: 'район Некрасовка', 120: 'район Кунцево'} - AO_RENAME_DICT = {1: 'Западный административный округ', 2: 'Южный административный округ', 3: 'Северный административный округ', 4: 'Юго-Западный административный округ', 5: 'Северо-Западный административный округ', 6: 'Центральный административный округ', 7: 'Северо-Восточный административный округ', 8: 'Юго-Восточный административный округ', 9: 'Восточный административный округ'} - EXCLUDED_COLS = ['id', 'people', 'people2025', 'stops_ot', 'routes_ot', 'in_metro', 'out_metro', 'tc', 'empls', 'walkers', 'schools', 'parking', 'pvz', 'gov_place', 'bike_park', 'products', 'nonprod', 'service', 'vuz'] + RAYONS_RENAME_DICT = {1: 'район Филёвский Парк', 2: 'район Зюзино', 3: 'район Внуково', 4: 'Алтуфьевский район', + 5: 'Дмитровский район', 6: 'Можайский район', 7: 'район Ново-Переделкино', + 8: 'район Щукино', 9: 'Молжаниновский район', 10: 'район Митино', 11: 'район Куркино', + 12: 'район Аэропорт', 13: 'район Строгино', 14: 'район Крылатское', + 15: 'район Тёплый Стан', 16: 'район Солнцево', 57: 'Бутырский район', + 17: 'район Южное Тушино', 18: 'район Северное Тушино', 19: 'район Покровское-Стрешнево', + 20: 'район Хорошёво-Мнёвники', 21: 'район Очаково-Матвеевское', + 22: 'район Тропарёво-Никулино', 23: 'район Левобережный', 24: 'район Фили-Давыдково', + 25: 'район Ховрино', 26: 'Головинский район', 27: 'район Раменки', 28: 'Войковский район', + 29: 'район Западное Дегунино', 30: 'район Сокол', 31: 'район Проспект Вернадского', + 32: 'район Южное Бутово', 33: 'Обручевский район', 34: 'район Ясенево', + 35: 'район Дорогомилово', 36: 'район Коньково', 37: 'Хорошёвский район', + 38: 'район Северный', 39: 'район Коптево', 40: 'Ломоносовский район', 58: 'район Марфино', + 59: 'Тверской район', 41: 'Пресненский район', 74: 'район Нагатино-Садовники', + 42: 'Бескудниковский район', 43: 'Гагаринский район', 44: 'район Черёмушки', + 45: 'Тимирязевский район', 46: 'район Беговой', 47: 'район Хамовники', + 48: 'район Северное Бутово', 49: 'район Лианозово', 50: 'район Восточное Дегунино', + 51: 'Савёловский район', 52: 'Академический район', 53: 'район Чертаново Центральное', + 54: 'район Отрадное', 55: 'район Арбат', 56: 'район Чертаново Южное', + 60: 'район Чертаново Северное', 61: 'район Якиманка', 62: 'район Котловка', + 63: 'Останкинский район', 64: 'Донской район', 65: 'район Бибирево', + 66: 'район Марьина Роща', 67: 'Нагорный район', 68: 'Даниловский район', + 73: 'район Северное Медведково', 69: 'Мещанский район', 70: 'район Бирюлёво Западное', + 71: 'район Южное Медведково', 72: 'район Замоскворечье', 75: 'район Царицыно', + 76: 'район Москворечье-Сабурово', 77: 'район Свиблово', 78: 'район Нагатинский Затон', + 82: 'Таганский район', 79: 'Басманный район', 80: 'Красносельский район', + 81: 'район Ростокино', 83: 'Алексеевский район', 84: 'район Восточный', + 87: 'Бабушкинский район', 85: 'район Бирюлёво Восточное', 86: 'Южнопортовый район', + 88: 'район Сокольники', 89: 'Ярославский район', 90: 'район Богородское', + 91: 'район Метрогородок', 92: 'район Лефортово', 93: 'район Орехово-Борисово Северное', + 94: 'район Печатники', 95: 'Лосиноостровский район', 98: 'район Новогиреево', + 96: 'Нижегородский район', 99: 'район Марьино', 97: 'район Орехово-Борисово Южное', + 100: 'район Преображенское', 105: 'район Братеево', 101: 'район Соколиная Гора', + 102: 'район Перово', 103: 'район Люблино', 104: 'район Текстильщики', + 106: 'район Зябликово', 107: 'Рязанский район', 108: 'район Измайлово', + 109: 'район Северное Измайлово', 113: 'район Капотня', 110: 'район Кузьминки', + 111: 'район Гольяново', 112: 'район Вешняки', 114: 'район Выхино-Жулебино', + 115: 'район Восточное Измайлово', 118: 'район Новокосино', 116: 'район Ивановское', + 117: 'район Косино-Ухтомский', 119: 'район Некрасовка', 120: 'район Кунцево'} + AO_RENAME_DICT = {1: 'Западный административный округ', 2: 'Южный административный округ', + 3: 'Северный административный округ', 4: 'Юго-Западный административный округ', + 5: 'Северо-Западный административный округ', 6: 'Центральный административный округ', + 7: 'Северо-Восточный административный округ', 8: 'Юго-Восточный административный округ', + 9: 'Восточный административный округ'} + EXCLUDED_COLS = ['id', 'people', 'people2025', 'stops_ot', 'routes_ot', 'in_metro', 'out_metro', 'tc', 'empls', + 'walkers', 'schools', 'parking', 'pvz', 'gov_place', 'bike_park', 'products', 'nonprod', + 'service', 'vuz'] df_points_, df_nets_ = raschet_alg(**request.data) df_points_['category'] = df_points_['category'].map(TIPES_DICT) df_points_['msk_ao'] = df_points_['msk_ao'].map(AO_RENAME_DICT) @@ -68,18 +129,23 @@ class raschet(GenericAPIView): df_points = rename_result_dataset(df_points_, POINTS_RENAME_DICT) if "Стандартная модель" in df_points.columns: - df_points = df_points[["ID точки (для всех наборов одинаковые)", "Адм. округ", "Адм. район", "Адрес", "Название", "Тип объекта", "geom", "Модель на ML", "Стандартная модель"]] + df_points = df_points[ + ["ID точки (для всех наборов одинаковые)", "Адм. округ", "Адм. район", "Адрес", "Название", + "Тип объекта", "geom", "Модель на ML", "Стандартная модель"]] else: - df_points = df_points[["ID точки (для всех наборов одинаковые)", "Адм. округ", "Адм. район", "Адрес", "Название", "Тип объекта", "geom", "Модель на ML",]] - - + df_points = df_points[ + ["ID точки (для всех наборов одинаковые)", "Адм. округ", "Адм. район", "Адрес", "Название", + "Тип объекта", "geom", "Модель на ML", ]] df_nets = rename_result_dataset(df_nets_, NET_RENAME_DICT) if "Стандартная модель" in df_nets.columns: - df_nets = df_nets[["ID ячейки (для всех трёх сеток - разные, нумерация сквозная)", "Адм. округ", "Адм. район", "geom", "Модель на ML", "Стандартная модель"]] + df_nets = df_nets[ + ["ID ячейки (для всех трёх сеток - разные, нумерация сквозная)", "Адм. округ", "Адм. район", "geom", + "Модель на ML", "Стандартная модель"]] else: - df_nets = df_nets[["ID ячейки (для всех трёх сеток - разные, нумерация сквозная)", "Адм. округ", "Адм. район", "geom", "Модель на ML"]] - + df_nets = df_nets[ + ["ID ячейки (для всех трёх сеток - разные, нумерация сквозная)", "Адм. округ", "Адм. район", "geom", + "Модель на ML"]] with BytesIO() as b: # Use the StringIO object as the filehandle. @@ -87,7 +153,7 @@ class raschet(GenericAPIView): if df_points is not None: df_points.to_excel(writer, sheet_name='Точки', index=False) worksheet = writer.sheets['Точки'] # pull worksheet object - for idx, col in enumerate(df_points): # loop through all columns + for idx, col in enumerate(df_points): # loop through all columns if col == 'geom': continue series = df_points[col] @@ -117,3 +183,15 @@ class raschet(GenericAPIView): ) response['Content-Disposition'] = 'attachment; filename=%s' % filename return response + + +# ====== + + +class PlacementPointViewSet(ReadOnlyModelViewSet): + serializer_class = serializers.PlacementPointSerializer + queryset = models.PlacementPoint.objects + pagination_class = pagination.MyPagination + + def get_queryset(self): + return self.queryset.all()