mod: raw sql --> ORM
This commit is contained in:
@@ -528,6 +528,7 @@ fSetRating = k1 + k2 + k3 (итого от 0.0 до 5.0 звёзд)
|
|||||||
[OK!] ПЕРЕСЧЁТ РЕЙТИНГОВ ЗАВЕРШЁН УСПЕШНО!
|
[OK!] ПЕРЕСЧЁТ РЕЙТИНГОВ ЗАВЕРШЁН УСПЕШНО!
|
||||||
• Обновлено профилей: 94
|
• Обновлено профилей: 94
|
||||||
• Обновлено стеклопакетов: 97
|
• Обновлено стеклопакетов: 97
|
||||||
|
• Обновлено наборов: 27
|
||||||
```
|
```
|
||||||
|
|
||||||
## Оркестрация и reload веб-сервера
|
## Оркестрация и reload веб-сервера
|
||||||
|
|||||||
@@ -6,7 +6,12 @@ Django management command: make_rating
|
|||||||
Вычисление базируется на ренкинге методом Манна-Уитни (Mann-Whitney U test шаг).
|
Вычисление базируется на ренкинге методом Манна-Уитни (Mann-Whitney U test шаг).
|
||||||
|
|
||||||
В базовом ренкинге участвуют только профили и стеклопакеты, присутствующие
|
В базовом ренкинге участвуют только профили и стеклопакеты, присутствующие
|
||||||
в коммерческих предложениях размещённых в ОКНАРДИИ.
|
в коммерческих предложениях, размещённых в ОКНАРДИИ.
|
||||||
|
|
||||||
|
ОБНОВЛЕНИЕ: Все raw SQL запросы заменены на Django ORM (v2.0):
|
||||||
|
- Профили: использует PVCprofiles.objects.annotate(Count(...))
|
||||||
|
- Стеклопакеты: используется Glazing.objects.annotate(Count(...))
|
||||||
|
- Наборы: используется SetKit.objects.select_related().annotate(Count/Max/F(...))
|
||||||
|
|
||||||
АЛГОРИТМ РАНЖИРОВАНИЯ (Mann-Whitney U Step Rank):
|
АЛГОРИТМ РАНЖИРОВАНИЯ (Mann-Whitney U Step Rank):
|
||||||
====================================================
|
====================================================
|
||||||
@@ -101,6 +106,7 @@ Django management command: make_rating
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
|
from django.db.models import Count, Max, F
|
||||||
import json
|
import json
|
||||||
import time
|
import time
|
||||||
|
|
||||||
@@ -199,7 +205,7 @@ def get_rank(set_dictionary, key, key_weight: float = 1, rank_name="", revers=Fa
|
|||||||
|
|
||||||
def prepare_pvc_dictionary(profile_use_list):
|
def prepare_pvc_dictionary(profile_use_list):
|
||||||
"""
|
"""
|
||||||
Конвертирует RawQuerySet в список словарей и исправляет данные для ранжирования.
|
Конвертирует QuerySet объектов профилей (с аннотациями) в список словарей и исправляет данные для ранжирования.
|
||||||
|
|
||||||
Коррекции:
|
Коррекции:
|
||||||
1. iProfileCameras: конвертирует строку "3/3" в число суммированием цифр
|
1. iProfileCameras: конвертирует строку "3/3" в число суммированием цифр
|
||||||
@@ -207,13 +213,19 @@ def prepare_pvc_dictionary(profile_use_list):
|
|||||||
2. iProfileHeight: для значений < 10 см → ставим 1000 (невалидные данные)
|
2. iProfileHeight: для значений < 10 см → ставим 1000 (невалидные данные)
|
||||||
3. NumOffer: добавляем поле если его нет (количество коммерческих предложений)
|
3. NumOffer: добавляем поле если его нет (количество коммерческих предложений)
|
||||||
|
|
||||||
:param profile_use_list: RawQuerySet с объектами профилей
|
:param profile_use_list: список объектов профилей (QuerySet или list)
|
||||||
:return: список словарей с исправленными данными
|
:return: список словарей с исправленными данными
|
||||||
"""
|
"""
|
||||||
pvc_dictionary = []
|
pvc_dictionary = []
|
||||||
|
|
||||||
for profile in profile_use_list:
|
for profile in profile_use_list:
|
||||||
|
# Конвертируем объект модели в dict, включая аннотированные поля
|
||||||
profile_dict = profile.__dict__.copy()
|
profile_dict = profile.__dict__.copy()
|
||||||
|
# Добавляем annotate-поля которые не в __dict__
|
||||||
|
if hasattr(profile, 'NumOffer'):
|
||||||
|
profile_dict['NumOffer'] = profile.NumOffer
|
||||||
|
else:
|
||||||
|
profile_dict['NumOffer'] = 0
|
||||||
|
|
||||||
# Коррекция: количество камер из строки в число
|
# Коррекция: количество камер из строки в число
|
||||||
if 'iProfileCameras' in profile_dict:
|
if 'iProfileCameras' in profile_dict:
|
||||||
@@ -237,19 +249,24 @@ def prepare_pvc_dictionary(profile_use_list):
|
|||||||
|
|
||||||
def prepare_glaz_dictionary(glazing_use_list):
|
def prepare_glaz_dictionary(glazing_use_list):
|
||||||
"""
|
"""
|
||||||
Конвертирует RawQuerySet в список словарей и исправляет данные для ранжирования.
|
Конвертирует QuerySet объектов стеклопакетов (с аннотациями) в список словарей и исправляет данные для ранжирования.
|
||||||
|
|
||||||
Коррекции:
|
Коррекции:
|
||||||
1. fGlazingLightTransmission: если < 1% → ставим 10000 (нет данных о светопропускании)
|
1. fGlazingLightTransmission: если < 1% → ставим 10000 (нет данных о светопропускании)
|
||||||
2. fGlazingPassingSun: если < 1% → ставим 10000 (нет данных о солнцепропускании)
|
2. fGlazingPassingSun: если < 1% → ставим 10000 (нет данных о солнцепропускании)
|
||||||
|
|
||||||
:param glazing_use_list: RawQuerySet с объектами стеклопакетов
|
:param glazing_use_list: список объектов стеклопакетов (QuerySet или list)
|
||||||
:return: список словарей с исправленными данными
|
:return: список словарей с исправленными данными
|
||||||
"""
|
"""
|
||||||
glaz_dictionary = []
|
glaz_dictionary = []
|
||||||
|
|
||||||
for glazing in glazing_use_list:
|
for glazing in glazing_use_list:
|
||||||
glazing_dict = glazing.__dict__.copy()
|
glazing_dict = glazing.__dict__.copy()
|
||||||
|
# Добавляем annotate-поля которые не в __dict__
|
||||||
|
if hasattr(glazing, 'NumOffer'):
|
||||||
|
glazing_dict['NumOffer'] = glazing.NumOffer
|
||||||
|
else:
|
||||||
|
glazing_dict['NumOffer'] = 0
|
||||||
|
|
||||||
# Коррекция: светопропускание < 1% означает нет данных
|
# Коррекция: светопропускание < 1% означает нет данных
|
||||||
if glazing_dict.get("fGlazingLightTransmission", 0) < 1:
|
if glazing_dict.get("fGlazingLightTransmission", 0) < 1:
|
||||||
@@ -371,15 +388,16 @@ def ranking_pvc(pvc_dictionary):
|
|||||||
|
|
||||||
def prepare_setkit_dictionary(setkit_use_list):
|
def prepare_setkit_dictionary(setkit_use_list):
|
||||||
"""
|
"""
|
||||||
Конвертирует RawQuerySet в список словарей и исправляет данные для ранжирования.
|
Конвертирует QuerySet объектов наборов (с аннотациями) в список словарей и исправляет данные для ранжирования.
|
||||||
|
|
||||||
Коррекции:
|
Коррекции:
|
||||||
1. Строковые поля (sSetSill, sSetPanes, sSetSlope, sSetClimateControl) преобразуются в 0/1
|
1. Строковые поля (sSetSill, sSetPanes, sSetSlope, sSetClimateControl) преобразуются в 0/1
|
||||||
2. sOfficeDiscountMetaFormula парсится для максимальной скидки и количества вариантов
|
2. sOfficeDiscountMetaFormula парсится для максимальной скидки и количества вариантов
|
||||||
3. dModify (datetime) преобразуется в timestamp (число) для ранжирования
|
3. dModify (datetime/timestamp) преобразуется в timestamp (число) для ранжирования
|
||||||
4. Логические поля должны быть числами для алгоритма ранжирования
|
4. Логические поля должны быть числами для алгоритма ранжирования
|
||||||
|
5. Аннотированные поля (NumOffer, fProfileRating, fGlazingRating) извлекаются из атрибутов модели
|
||||||
|
|
||||||
:param setkit_use_list: RawQuerySet с объектами наборов
|
:param setkit_use_list: список объектов наборов (QuerySet или list)
|
||||||
:return: список словарей с исправленными данными
|
:return: список словарей с исправленными данными
|
||||||
"""
|
"""
|
||||||
import datetime
|
import datetime
|
||||||
@@ -389,6 +407,19 @@ def prepare_setkit_dictionary(setkit_use_list):
|
|||||||
for setkit in setkit_use_list:
|
for setkit in setkit_use_list:
|
||||||
setkit_dict = setkit.__dict__.copy()
|
setkit_dict = setkit.__dict__.copy()
|
||||||
|
|
||||||
|
# Добавляем annotate-поля которые не в __dict__
|
||||||
|
if hasattr(setkit, 'NumOffer'):
|
||||||
|
setkit_dict['NumOffer'] = setkit.NumOffer or 0
|
||||||
|
else:
|
||||||
|
setkit_dict['NumOffer'] = 0
|
||||||
|
|
||||||
|
if hasattr(setkit, 'fProfileRating'):
|
||||||
|
setkit_dict['fProfileRating'] = setkit.fProfileRating or 0.0
|
||||||
|
if hasattr(setkit, 'fGlazingRating'):
|
||||||
|
setkit_dict['fGlazingRating'] = setkit.fGlazingRating or 0.0
|
||||||
|
if hasattr(setkit, 'sOfficeDiscountMetaFormula'):
|
||||||
|
setkit_dict['sOfficeDiscountMetaFormula'] = setkit.sOfficeDiscountMetaFormula or ""
|
||||||
|
|
||||||
# Коррекция: преобразуем дату в timestamp для ранжирования
|
# Коррекция: преобразуем дату в timestamp для ранжирования
|
||||||
# Свежие данные (большие timestamp) должны иметь более высокий ранг
|
# Свежие данные (большие timestamp) должны иметь более высокий ранг
|
||||||
if 'dModify' in setkit_dict and setkit_dict['dModify']:
|
if 'dModify' in setkit_dict and setkit_dict['dModify']:
|
||||||
@@ -811,17 +842,14 @@ class Command(BaseCommand):
|
|||||||
profile_all_num = PVCprofiles.objects.all().update(fProfileRating=0.0)
|
profile_all_num = PVCprofiles.objects.all().update(fProfileRating=0.0)
|
||||||
self.stdout.write(f' ✓ Обнулены рейтинги у {profile_all_num} профилей')
|
self.stdout.write(f' ✓ Обнулены рейтинги у {profile_all_num} профилей')
|
||||||
|
|
||||||
# Извлекаем профили которые используются в коммерческих предложениях
|
# Извлекаем ВСЕ профили с количеством коммерческих предложений
|
||||||
q = PVCprofiles.objects.raw(
|
# (включая те что без предложений - для них NumOffer = 0)
|
||||||
"SELECT oknardia_pvcprofiles.*, COUNT(oknardia_priceoffer.id) AS NumOffer "
|
# ORM эквивалент: RIGHT OUTER JOIN в SQL
|
||||||
"FROM oknardia_setkit "
|
profile_use_list = list(
|
||||||
"INNER JOIN oknardia_priceoffer "
|
PVCprofiles.objects.annotate(
|
||||||
" ON oknardia_setkit.id = oknardia_priceoffer.kOffer2SetKit_id "
|
NumOffer=Count('setkit__priceoffer', distinct=True)
|
||||||
"RIGHT OUTER JOIN oknardia_pvcprofiles "
|
)
|
||||||
" ON oknardia_setkit.kSet2PVCprofiles_id = oknardia_pvcprofiles.id "
|
|
||||||
"GROUP BY oknardia_pvcprofiles.id"
|
|
||||||
)
|
)
|
||||||
profile_use_list = list(q)
|
|
||||||
self.stdout.write(f' ✓ Найдено {len(profile_use_list)} профилей для ранжирования')
|
self.stdout.write(f' ✓ Найдено {len(profile_use_list)} профилей для ранжирования')
|
||||||
|
|
||||||
# Подготавливаем словарь профилей с коррекциями
|
# Подготавливаем словарь профилей с коррекциями
|
||||||
@@ -900,17 +928,13 @@ class Command(BaseCommand):
|
|||||||
glazing_all_num = Glazing.objects.all().update(fGlazingRating=0.0)
|
glazing_all_num = Glazing.objects.all().update(fGlazingRating=0.0)
|
||||||
self.stdout.write(f' ✓ Обнулены рейтинги у {glazing_all_num} стеклопакетов')
|
self.stdout.write(f' ✓ Обнулены рейтинги у {glazing_all_num} стеклопакетов')
|
||||||
|
|
||||||
# Извлекаем стеклопакеты которые используются в коммерческих предложениях
|
# Извлекаем ВСЕ стеклопакеты с количеством коммерческих предложений
|
||||||
q = Glazing.objects.raw(
|
# (включая те что без предложений - для них NumOffer = 0)
|
||||||
"SELECT oknardia_glazing.*, COUNT(oknardia_priceoffer.id) AS NumOffer "
|
glazing_use_list = list(
|
||||||
"FROM oknardia_setkit "
|
Glazing.objects.annotate(
|
||||||
"INNER JOIN oknardia_priceoffer "
|
NumOffer=Count('setkit__priceoffer', distinct=True)
|
||||||
" ON oknardia_setkit.id = oknardia_priceoffer.kOffer2SetKit_id "
|
)
|
||||||
"RIGHT OUTER JOIN oknardia_glazing "
|
|
||||||
" ON oknardia_setkit.kSet2Glazing_id = oknardia_glazing.id "
|
|
||||||
"GROUP BY oknardia_glazing.id"
|
|
||||||
)
|
)
|
||||||
glazing_use_list = list(q)
|
|
||||||
self.stdout.write(f' ✓ Найдено {len(glazing_use_list)} стеклопакетов для ранжирования')
|
self.stdout.write(f' ✓ Найдено {len(glazing_use_list)} стеклопакетов для ранжирования')
|
||||||
|
|
||||||
# Подготавливаем словарь стеклопакетов с коррекциями
|
# Подготавливаем словарь стеклопакетов с коррекциями
|
||||||
@@ -990,32 +1014,25 @@ class Command(BaseCommand):
|
|||||||
self.stdout.write(f' ✓ Обнулены рейтинги у {setkit_all_num} наборов')
|
self.stdout.write(f' ✓ Обнулены рейтинги у {setkit_all_num} наборов')
|
||||||
|
|
||||||
# Извлекаем наборы которые используются в коммерческих предложениях
|
# Извлекаем наборы которые используются в коммерческих предложениях
|
||||||
q = SetKit.objects.raw(
|
# ORM эквивалент: SELECT setkit с JOIN к PriceOffer, Glazing, PVCprofiles, OurUser, MerchantOffice
|
||||||
"SELECT oknardia_setkit.*, COUNT(oknardia_priceoffer.id) AS NumOffer, "
|
# и агрегацией COUNT(PriceOffer), MAX(dOfferModify)
|
||||||
"MAX(oknardia_priceoffer.dOfferModify) AS dModify, "
|
setkit_use_list = list(
|
||||||
"oknardia_pvcprofiles.fProfileRating, "
|
SetKit.objects.filter(
|
||||||
"oknardia_glazing.fGlazingRating, "
|
sSetActive=True
|
||||||
"oknardia_merchantoffice.sOfficeDiscountMetaFormula "
|
).select_related(
|
||||||
"FROM oknardia_setkit "
|
'kSet2PVCprofiles', # PVCprofiles для получения fProfileRating
|
||||||
"INNER JOIN oknardia_priceoffer "
|
'kSet2Glazing', # Glazing для получения fGlazingRating
|
||||||
" ON oknardia_priceoffer.kOffer2SetKit_id = oknardia_setkit.id "
|
'kSet2User__kMerchantOffice' # OurUser -> MerchantOffice для sOfficeDiscountMetaFormula
|
||||||
"INNER JOIN oknardia_glazing "
|
).annotate(
|
||||||
" ON oknardia_setkit.kSet2Glazing_id = oknardia_glazing.id "
|
NumOffer=Count('priceoffer', distinct=True), # COUNT(PriceOffer)
|
||||||
"INNER JOIN oknardia_pvcprofiles "
|
dModify=Max('priceoffer__dOfferModify'), # MAX(dOfferModify)
|
||||||
" ON oknardia_pvcprofiles.id = oknardia_setkit.kSet2PVCprofiles_id "
|
fProfileRating=F('kSet2PVCprofiles__fProfileRating'),
|
||||||
"INNER JOIN oknardia_ouruser "
|
fGlazingRating=F('kSet2Glazing__fGlazingRating'),
|
||||||
" ON oknardia_setkit.kSet2User_id = oknardia_ouruser.id "
|
sOfficeDiscountMetaFormula=F('kSet2User__kMerchantOffice__sOfficeDiscountMetaFormula')
|
||||||
"INNER JOIN oknardia_merchantoffice "
|
).filter(
|
||||||
" ON oknardia_ouruser.kMerchantOffice_id = oknardia_merchantoffice.id "
|
NumOffer__gt=0
|
||||||
"WHERE oknardia_setkit.sSetActive = TRUE "
|
).order_by('-dModify')
|
||||||
"GROUP BY oknardia_pvcprofiles.fProfileRating, "
|
|
||||||
" oknardia_glazing.fGlazingRating, "
|
|
||||||
" oknardia_merchantoffice.sOfficeDiscountMetaFormula, "
|
|
||||||
" oknardia_setkit.sSetActive, oknardia_setkit.id "
|
|
||||||
"ORDER BY MAX(oknardia_priceoffer.dOfferModify)"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
setkit_use_list = list(q)
|
|
||||||
self.stdout.write(f' ✓ Найдено {len(setkit_use_list)} наборов для ранжирования')
|
self.stdout.write(f' ✓ Найдено {len(setkit_use_list)} наборов для ранжирования')
|
||||||
|
|
||||||
# Подготавливаем словарь наборов с коррекциями
|
# Подготавливаем словарь наборов с коррекциями
|
||||||
|
|||||||
Reference in New Issue
Block a user