# MANAGEMENT_RUNBOOK.md Единый runbook по management-командам проекта. Документ отвечает на 3 вопроса: - что запускать; - когда запускать; - как безопасно откатываться/повторять запуск. ## Каталог команд 1. `regenerate_seria_roots` — пересчет корневых серий (иерархия и консолидация). 2. `generate_map_js` — генерация JavaScript для карт с геоданными зданий. 3. `generate_sitemaps` — оффлайн генерация sitemap-файлов. 4. `regenerate_seria_prerender` — оффлайн пересборка pre-render шаблонов для `catalog_seria_info`. 5. `populate_seo_fields` — автозаполнение SEO-полей блога из существующих данных. 6. `make_rating` — пересчёт рейтингов профилей и стеклопакетов методом Манна-Уитни. ## Общие правила запуска - Запускать команды из корня репозитория. - Для локального/CI запуска использовать `poetry`. - Не запускать тяжелые операции через HTTP-эндпоинты `/service/*`. - Перезапуск веб-сервера (`gunicorn`/`uWSGI`) делать отдельным шагом оркестрации, а не из кода Django. Базовый шаблон запуска: ```bash cd /Users/e-serg/PRJ/2022-oknardia poetry run python oknardia/manage.py [args] ``` ## 1) Команда `regenerate_seria_roots` Назначение: - пересчитать корневые серии (root) для всей иерархии серий домов. - консолидировать различные написания одной и той же серии в одну "корневую" серию. ### Контекст На разных источниках данные о типовых сериях домов были записаны с разными орфографическими вариантами. Например серия **II-57** могла быть обозначена как: - `2-57` (цифра вместо кириллицы) - `И-57` (кириллица И вместо латинской II) - `П-57` (опечатка) - и т.п. При парсинге данных эти варианты могли оказаться в БД как отдельные серии, хотя на самом деле это одна и та же серия. Функция `regenerate_seria_roots` связывает все эти варианты (алиасы) с одной **корневой** серией. ### Когда это нужно При добавлении новых адресов и серий типового строительства, при ручном редактировании иерархии серий в админке, при загрузке новых данных с разными орфографическими вариантами серий. Кроме того, если для уже существующих в базе серий будут получены данные о типовых размерах оконных проёмов и типов квартирах (сейчас в базе около 4500 серий, из них всего 31 корневых серий и 1957 серий с найденным корнем... ещё 2502 неописанных серии без корня, т.е. больше половины, могут пополнить каталог Окнардии. ### Как это работает 1. **Этап 1**: Находит "корневые" серии (root series) — те, что реально используются в таблице `Apartment_Type` (т.е. у которых есть квартиры). Для каждой такой серии устанавливает `kRoot_id = own_id`. 2. **Этап 2**: Для всех остальных серий: - Движется вверх по дереву иерархии (`kParent_id`) - Ищет корневую серию (ту, которая либо не имеет родителя, либо она в списке корневых) - Устанавливает найденную корневую серию в поле `kRoot_id` - Если не находит корневую серию → `kRoot_id = None` ### Пример структуры после обработки ``` Таблица: Seria_Info id | sSeriaName | kParent_id | kRoot_id | Комментарий ----|------------|------------|-----------|------------------- 123 | II-57 | NULL | 123 | Корневая серия 124 | 2-57 | 123 | 123 | Алиас (через родителя) 125 | И-57 | 123 | 123 | Алиас (через родителя) 126 | П-57 | 123 | 123 | Алиас (через родителя) ``` ### Базовый запуск ```bash cd /Users/e-serg/PRJ/2022-oknardia poetry run python oknardia/manage.py regenerate_seria_roots ``` ### Параметры запуска **`--verbosity 0`** — только ошибки (минимум информации): ```bash poetry run python oknardia/manage.py regenerate_seria_roots --verbosity 0 ``` **`--verbosity 1`** — точки/плюсы (стандартный режим): ```bash poetry run python oknardia/manage.py regenerate_seria_roots # или явно poetry run python oknardia/manage.py regenerate_seria_roots --verbosity 1 ``` **`--verbosity 2`** — подробный вывод (названия серий): ```bash poetry run python oknardia/manage.py regenerate_seria_roots --verbosity 2 ``` **`--verbosity 3`** — очень подробный вывод в виде таблицы: ```bash poetry run python oknardia/manage.py regenerate_seria_roots --verbosity 3 ``` ### Примеры вывода **Verbosity 0 (только ошибки - чистый вывод):** ``` ✅ Пересчет завершен! Время: 2.18с ``` **Verbosity 1 (магический режим - точки и символы):** ``` === ПЕРЕСЧЕТ КОРНЕВЫХ СЕРИЙ === Этап 1: Ищем корневые серии в таблице квартир... ✓ Найдено корневых серий: 241 ............................... Этап 2: Главная магия - обрабатываем все серии в иерархии... -----++..--.++--...--.+.++++.-+++++++-.-.++.-+--++--++++-+++++++-++.+--.+++-+-..++++++-++++++++++--.-++++++-++++ +--+-+++++++++-++++++++++-++++--+++.+++--+++++++++++++++++++---++.+-+-+++++-++++++++++-+++-+----+.-+++-+--++++++ +++----+--+-+++++-+--+++--+++-.+++++-++++++++-+---++-+++++---+++------++----++-+--++----+--++--++++--+++++++++++ +++++++-------++++---+++-[... очень много символов ...] === РЕЗУЛЬТАТЫ === ✓ Корневых серий (обработаны на этапе 1): 31 ✓ Серий с найденным корнем: 1957 ⚠ Серий без корня: 2502 ✅ Пересчет завершен! Время: 2.18с ``` **Легенда магического режима:** - `.` = корневая серия (обработана на этапе 1) - `+` = серия с найденным корнем - `-` = серия без найденного корня - `E` = ошибка при обработке **Verbosity 2 (подробный):** ``` === ПЕРЕСЧЕТ КОРНЕВЫХ СЕРИЙ === Этап 1: Ищем корневые серии в таблице квартир... ✓ Найдено корневых серий: 241 ✓ 0008 П-44 ✓ 0009 П-3 ✓ 0012 II-49 ✓ 0017 КОПЭ ... Этап 2: Главная магия - обрабатываем все серии в иерархии... 0001: Нет корня 0002: Нет корня 0006: корень → 12 0007: корень → 9 0008: корневая ... ``` **Verbosity 3 (очень подробный - таблица):** ``` === ПЕРЕСЧЕТ КОРНЕВЫХ СЕРИЙ === Этап 1: Ищем корневые серии в таблице квартир... ✓ Найдено корневых серий: 241 ✓ 0008 | П-44 | корневая ✓ 0009 | П-3 | корневая ✓ 0012 | II-49 | корневая ... Этап 2: Главная магия - обрабатываем все серии в иерархии... -------------------------------------------------------------------------------------------------------------- ID | Название | Родитель | Путь | Результат -------------------------------------------------------------------------------------------------------------- ... ... 2565 | Г-ЗИ | 9 | 9 | ✓ Корень #9 2566 | I-528КП-809/69 | - | (нет) | ✗ Нет корня 2567 | 464Д-0154 | 3339 | 3339 → 963 → 375 | ✓ Корень #375 2568 | УЛГ-507-4/64 | 2105 | 2105 | ✓ Корень #2105 2569 | ЛГ-507-4 | 2105 | 2105 | ✓ Корень #2105 2570 | 1ЛГ-600-И-1 | - | (нет) | ✗ Нет корня 2571 | 464Д-0154 Новополоцкого ДСК | 3339 | 3339 → 963 → 375 | ✓ Корень #375 2572 | 121-0142,13,87 | - | (нет) | ✗ Нет корня ... ... -------------------------------------------------------------------------------------------------------------- === РЕЗУЛЬТАТЫ === ✓ Корневых серий (обработаны на этапе 1): 31 ✓ Серий с найденным корнем: 1957 ⚠ Серий без корня: 2502 ✅ Пересчет завершен! Время: 2.18с ``` ### Когда запускать - **После первого развертывания** — консолидировать иерархию серий. - **После ручного редактирования иерархии** (добавления родитель-потомков в админку). - **После загрузки новых данных** с разными орфографическими вариантами серий. - **По расписанию** (опционально, например раз в месяц): ```bash 0 2 * * 1 cd /home/user/app-path/2022-oknardia && poetry run python oknardia/manage.py regenerate_seria_roots >> /var/log/oknardia-seria-roots.log 2>&1 ``` ### Откат и безопасность - **Безопасна для повторного запуска** — просто пересчитывает все `kRoot_id`. - **Откат через SQL** — если нужно очистить поле (перед запуском рекомендуется бэкап): ```sql UPDATE oknardia_seria_info SET kRoot_id = NULL; ``` - **Проверка результатов** — после запуска можно проверить: ```bash poetry run python oknardia/manage.py shell -c " from oknardia.models import Seria_Info count_null = Seria_Info.objects.filter(kRoot_id__isnull=True).count() count_with_root = Seria_Info.objects.filter(kRoot_id__isnull=False).count() print(f'Серий без корня: {count_null}') print(f'Серий с корнем: {count_with_root}') " ``` ## 2) Команда `generate_map_js` Назначение: - сгенерировать JavaScript-файл для отрисовки карты всех зданий типовых серий в Яндекс.Картах. - файл содержит геоданные (latitude/longitude), ID адресов, привязку к сериям и информацию для balloon-окон на картах. ### Что происходит 1. **Сбор геоданных** — для всех корневых серий (где `id = kRoot_id`) - Запрашиваются здания из таблицы `Building_Info` с non-zero координатами - Для каждого здания собирается: широта, долгота, ID адреса, адрес в латинице, ID серии 2. **Генерация JavaScript** — на основе шаблона `service/JavaScript4AllSeriaMap.js.html` - Генерируется массив цветов для каждой серии - Объявляются переменные с ID и названиями серий - Инициализируется Yandex.Maps с PlaceMarks для каждого здания 3. **Минификация через Terser** — уменьшение размера JavaScript - Удаляются ненужные пробелы и переносы строк - Сокращаются имена переменных (mangling) - Удаляются console.log и debugger 4. **Запись в файлы**: - `public/static/js/4maps/_ALL_seria_on_map.js` — исходный форматированный файл (715 KB) - `public/static/js/4maps/_ALL_seria_on_map.mini.js` — минифицированный файл (639 KB) ### Оптимизация размера Файл был оптимизирован в три этапа: | Этап | Размер | Сжатие | |------|--------|--------| | Исходный (2016 год) | 2.5 MB | — | | **Уровень 1**: функция-фабрика `m()` | 715 KB | **71%** | | **Уровень 2**: Terser минификация | 639 KB | +10.6% | | **Уровень 3**: Gzip в браузере | 188 KB | +29.4% | | **Итого сжатие** | **188 KB** | **92.5%** | > **Примечание**: Gzip применяется автоматически браузером и веб-сервером при наличии в заголовках `Content-Encoding: gzip` Содержимое: - **Маркеры на карте**: 18,228 зданий - **Серии с цветами**: 31 - **Корневые серии**: 31 ### Базовый запуск ```bash cd /Users/e-serg/PRJ/2022-oknardia poetry run python oknardia/manage.py generate_map_js ``` ### Параметры запуска **`--force`** — пересгенерировать файл (перезаписать если существует): ```bash poetry run python oknardia/manage.py generate_map_js --force ``` **`--verbosity 2`** — подробный вывод со статистикой: ```bash poetry run python oknardia/manage.py generate_map_js --verbosity 2 ``` ### Когда запускать - **После первого развертывания** — создать файл карты один раз. - **После добавления новых зданий** в БД (через парсеры или импорт). - **По расписанию** (опционально, если здания редко добавляются): ```bash 0 3 * * 0 cd /home/user/app-path/2022-oknardia && poetry run python oknardia/manage.py generate_map_js >> /var/log/oknardia-map-js.log 2>&1 ``` ### Пример вывода ``` === ГЕНЕРАЦИЯ JAVASCRIPT ДЛЯ КАРТ === Этап 1: Сбор информации о корневых сериях... ✓ Найдено корневых серий: 31 Этап 2: Генерация единого JS-файла для ВСЕ серий... ✓ Написан исходный файл: _ALL_seria_on_map.js Размер: 734.0 KB Этап 3: Минификация JavaScript (rjsmin)... [*] Минификация успешна! Исходный файл: 734.015 KB Минифицированный: 732.952 KB Сжатие: 0.14% Время: 0.0017с [i] Полная статистика по сериям: - Жилых м²: 125,749,341 - Муниципальных м²: 11,302,860 - Жильцов: 6,342,742 - Квартир: 2,769,800 === РЕЗУЛЬТАТЫ === ✓ Серий обработано: 31 ✓ Зданий на карте: 18228 ✓ JS-файлов создано: 2 (исходный + минифицированный) ✓ Исходный файл: _ALL_seria_on_map.js ✓ Минифицированный: _ALL_seria_on_map.mini.js ✓ Обфускация: Base64 кодирование координат [OK] Генерация завершена! Время: 1.10с ``` ## 3) Команда `generate_sitemaps` Назначение: - пересобрать `sitemap.xml` и chunk-файлы в `MEDIA_ROOT/_serv_sitemap`. Базовый запуск: ```bash cd /Users/e-serg/PRJ/2022-oknardia poetry run python oknardia/manage.py generate_sitemaps ``` Запуск с параметрами: ```bash cd /Users/e-serg/PRJ/2022-oknardia poetry run python oknardia/manage.py generate_sitemaps \ --compare-min-depth 2 \ --compare-max-depth 4 \ --max-items 40000 \ --max-file-size 5242880 \ --max-files-qty 998 ``` Когда запускать: - после деплоя; - по расписанию (cron/systemd timer); - после крупных изменений данных каталога/блога. ### Важные замечания Чтобы `sitemap.xml` отдавал прокси-nginx напрямую из файловой системы, нужно, чтобы он физически лежал в `MEDIA_ROOT/_serv_sitemap/sitemap.xml`. Допустимо, что файл доступен по двум URL (корневой и media), но в `robots.txt` должен быть указан один канонический вариант `sitemap.xml` #### NGINX snippet (alias для корневого sitemap) ```nginx # Корневой sitemap.xml (для привычного для поисковиков URL) location = /sitemap.xml { alias /<путь-к-каталогку-с-докер-приложением>/media/_serv_sitemap/sitemap.xml; default_type application/xml; add_header Cache-Control "public, max-age=300"; } ``` ## 4) Команда `regenerate_seria_prerender` Назначение: - пересобрать pre-render шаблоны для страниц серий (`catalog_seria_info`) в каталоге `seria_info/prepared/`. Проверка без записи файлов: ```bash cd /Users/e-serg/PRJ/2022-oknardia poetry run python oknardia/manage.py regenerate_seria_prerender --dry-run ``` Пересборка только отсутствующих файлов: ```bash cd /Users/e-serg/PRJ/2022-oknardia poetry run python oknardia/manage.py regenerate_seria_prerender ``` Принудительная пересборка всех root-серий: ```bash cd /Users/e-serg/PRJ/2022-oknardia poetry run python oknardia/manage.py regenerate_seria_prerender --force ``` Выборочная пересборка: ```bash cd /Users/e-serg/PRJ/2022-oknardia poetry run python oknardia/manage.py regenerate_seria_prerender --seria-id 843 --seria-id 2100 --force ``` Когда запускать: - после обновления логики `catalog_seria_info`; - после массового обновления данных серий/окон/квартир; - после очистки `seria_info/prepared/`. ## 5) Команда `populate_seo_fields` Назначение: - автозаполнить SEO-поля (`sSlug`, `sMetaDescription`, `sMetaKeywords`) для всех существующих записей блога. Используется: - при первом развертывании новой версии с автогенерацией SEO-полей; - при восстановлении из бэкапа где SEO-поля пусты; - при изменении логики автогенерации (с флагом `--force`). ### Базовый запуск Заполнить только пустые SEO-поля (стандартный вариант): ```bash cd /Users/e-serg/PRJ/2022-oknardia poetry run python oknardia/manage.py populate_seo_fields ``` ### Параметры запуска **`--dry-run`** — только показать что будет сделано (без сохранения в БД): ```bash poetry run python oknardia/manage.py populate_seo_fields --dry-run ``` **`--force`** — переполнить ВСЕ SEO-поля, даже уже заполненные: ```bash poetry run python oknardia/manage.py populate_seo_fields --force ``` **`--clean`** — очистить все SEO-поля перед заполнением (для переделки): ```bash poetry run python oknardia/manage.py populate_seo_fields --clean ``` **Комбинация флагов** — сухой прогон переполнения всех полей: ```bash poetry run python oknardia/manage.py populate_seo_fields --dry-run --force ``` ### Что заполняется | Поле | Источник | Результат | |------|----------|-----------| | `sSlug` | `sPostHeader` | URL-безопасный слаг (max 200 символов) | | `sMetaDescription` | `sPostContent` | Первые 160 символов (исключая теги ``) | | `sMetaKeywords` | `sPostHeader` | Заголовок + префикс "oknardia, окнардия, блог, публикация" (max 256 символов) | Пример результата: ```python sPostHeader = "Профиль Brusbox Super Aero" ↓ sSlug = "profil-brusbox-super-aero" sMetaDescription = "brusbox-super-aero-pyatikamernaya-profil-sistema..." sMetaKeywords = "oknardia, окнардия, блог, публикация, Профиль Brusbox Super Aero" ``` ### Когда запускать - **После первого развертывания** — заполнить SEO-поля всех 29 существующих постов одной командой. - **Один раз** — команда идемпотентна (при повторном запуске не будет ничего менять, т.к. пустые поля остатся). - **При изменении логики** — использовать `--clean --force` для полной переделки всех SEO-полей. ### Пример полного сценария ```bash cd /Users/e-serg/PRJ/2022-oknardia # Шаг 1: Проверить что будет заполнено poetry run python oknardia/manage.py populate_seo_fields --dry-run # Шаг 2: Если результат устраивает — запустить реально poetry run python oknardia/manage.py populate_seo_fields # Шаг 3: Проверить что заполнилось poetry run python oknardia/manage.py shell -c " from oknardia.models import BlogPosts posts = BlogPosts.objects.all() print(f'Пусто sSlug: {posts.filter(sSlug=\"\").count()}') print(f'Пусто sMetaDescription: {posts.filter(sMetaDescription=\"\").count()}') print(f'Пусто sMetaKeywords: {posts.filter(sMetaKeywords=\"\").count()}') " ``` ### Возвращаемая информация ``` ====================================================================== ИТОГОВЫЙ ОТЧЕТ ====================================================================== ✓ sSlug заполнено: 28 раз ✓ sMetaDescription заполнено: 28 раз ✓ sMetaKeywords заполнено: 28 раз ✓ Записей обновлено в БД: 28 ✗ Ошибок при обработке: 0 ✅ Обновлено 28 записей успешно! ``` ### Откат и безопасность - ✅ **Безопасна для повторного запуска** — пустые поля не изменяются при повторной работе. - ✅ **Откат через SQL** — если нужно очистить, используй: `UPDATE oknardia_blogposts SET sSlug='', sMetaDescription='', sMetaKeywords='';` - ✅ **Всегда используй `--dry-run`** перед первым запуском для проверки. ## 6) Команда `make_rating` Назначение: - пересчитать рейтинги оконных профилей, стеклопакетов и наборов услуг используя адаптированный метод Манна-Уитни (Mann-Whitney U Step Rank). - сохранить результаты в поля `fProfileRating`, `fGlazingRating`, `fSetRating` (0.0 … 5.0 звёзд). - заполнить JSON-состав рейтинга (детальный разбор по каждому параметру) в поля `sProfileDescription`, `sGlazingDescription`, `sSetDescription`. - алгоритм рассчитывает три этапа ранжирования: профили → стеклопакеты → наборы (которые зависят от профилей и стеклопакетов). ### Базовый запуск Пересчитать рейтинги всех профилей и стеклопакетов (стандартный режим): ```bash cd /Users/e-serg/PRJ/2022-oknardia poetry run python oknardia/manage.py make_rating ``` ### Параметры запуска **`--verbosity 0`** — минимум информации (только ошибки): **`--verbosity 1`** — стандартная информация (по умолчанию): **`--verbosity 3`** — очень подробный вывод (для отладки, для каждого профиля/стеклопакета таблица): Пример использования с параметром `--verbosity`: ```bash poetry run python oknardia/manage.py make_rating --verbosity 3 | head -500 ``` ### АЛГОРИТМ: Метод Манна-Уитни (Mann-Whitney U Step Rank) Команда использует адаптированный вариант критерия Манна-Уитни для ранжирования параметров качества оконных предложений и комопнентов (профилей, стеклопакетов, наборов услуг) на основе их технических характеристик и популярности у поставщиков. #### Как это работает: 1. **Сортировка объектов** по одному параметру (например, по теплопередаче): - Профиль A: 0.60 Ro → ранг = 0.0 - Профиль B: 0.60 Ro → ранг = 0.0 (то же значение, ранг не меняется) - Профиль C: 0.80 Ro → ранг = 1.0 (новое значение, добавляем вес параметра) - Профиль D: 0.95 Ro → ранг = 2.0 (ещё новое значение) 2. **Направление ранжирования** определяется флагом `revers`: - `revers=False` — **БОЛЬШЕ = ЛУЧШЕ** (например, теплопередача, звукоизоляция) - `revers=True` — **МЕНЬШЕ = ЛУЧШЕ** (например, высота в проёме для прочности) 3. **Нормализация рангов** к диапазону 0.0 … 1.0: - Профиль A: 0.0 / 2.0 = 0.0 - Профиль B: 0.0 / 2.0 = 0.0 - Профиль C: 1.0 / 2.0 = 0.5 - Профиль D: 2.0 / 2.0 = 1.0 4. **Суммирование рангов** по всем параметрам: - TmpRating = Σ(ранг_параметра × вес_параметра) 5. **Преобразование в звёзды** (0.0 … 5.0): - ТmpRating нормализуется к 0..1 - Умножается на 5.0 для получения финального рейтинга #### Пример итогового рейтинга профиля: ``` Профиль "Brusbox Super Aero" Теплопередача: 0.60 Ro (ранг 0.9, вес 1.0) Звукоизоляция: 33 дБ (ранг 0.8, вес 1.0) Высота в проёме: 112 мм (ранг 0.6, вес 0.3) Количество камер: 6 шт (ранг 0.7, вес 0.1) Итого: (0.9×1.0 + 0.8×1.0 + 0.6×0.3 + 0.7×0.1) / 2.3 ≈ 3.8 звёзд ⭐⭐⭐⭐ ``` ### ПРОФИЛИ: какие параметры учитываются | № | Параметр | Поле БД | ЛУЧШЕ | Вес | Описание | |---|----------------------|----------------------------|--------------------|-----|-----------------------------------------------------| | 1 | Звукоизоляция | `fProfileSoundproofing` | БОЛЬШЕ дБ | 1.0 | Сопротивление шуму (дБ) | | 2 | Теплопередача | `fProfileHeatTransf` | БОЛЬШЕ Ro | 1.0 | Сопротивление теплопередаче (м²×°C/Вт) | | 3 | Высота в проёме | `iProfileHeight` | МЕНЬШЕ мм | 0.3 | Видимая высота в световом проёме (экономия) | | 4 | Высота фальца | `iProfileRabbet` | БОЛЬШЕ мм | 0.2 | Глубина фальца для герметизации | | 5 | Толщина стеклопакета | `iProfileGlazingThickness` | БОЛЬШЕ мм | 0.2 | Максимальная толщина стеклопакета | | 6 | Толщина профиля | `iProfileThickness` | БОЛЬШЕ мм | 0.2 | Монтажная (боковая) ширина профиля | | 7 | Контуры уплотнения | `fProfileSeals` | БОЛЬШЕ контуров | 1.2 | Количество контуров уплотнения | | 8 | Количество камер | `iProfileCameras` | БОЛЬШЕ шт | 0.1 | Число камер в профиле (из рамки + створки) | | 9 | Популярность | `NumOffer` | БОЛЬШЕ предложений | 0.1 | Используется ли профиль в коммерческих предложениях | **Примеры интерпретации:** - Профиль с рейтингом **5.0 ⭐⭐⭐⭐⭐**: отличная теплопередача + звукоизоляция + много камер + многоконтурные уплотнения. - Профиль с рейтингом **2.0 ⭐⭐**: среднее качество, слабые характеристики. - Профиль с рейтингом **0.5 ⭐**: слабые характеристики или производить не предоставил данных и их нет в отрытых источниках. ### СТЕКЛОПАКЕТЫ: какие параметры учитываются | № | Параметр | Поле БД | ЛУЧШЕ | Вес | Описание | |---|-------------------|-----------------------------|--------------|------|----------------------------------------------------------------------------------------| | 1 | Звукоизоляция | `fGlazingSoundproofing` | БОЛЬШЕ дБ | 1.0 | Звукоизоляционный коэффициент (дБ) | | 2 | Теплопередача | `fGlazingHeatTransfer` | БОЛЬШЕ Ro | 1.0 | Сопротивление теплопередаче (м²×°C/Вт) | | 3 | Светопропускание | `fGlazingLightTransmission` | БОЛЬШЕ % | 0.25 | Коэффициент пропускания видимого света (%), отражение света снаружи | | 4 | Солнцепропускание | `fGlazingPassingSun` | **МЕНЬШЕ %** | 0.15 | Коэффициент солнечного излучения (SHGC) — В России меньше = лучше для охлаждения летом | | 5 | Толщина | `iGlazingThickness` | БОЛЬШЕ мм | 0.1 | Общая толщина стеклопакета | | 6 | Количество камер | `iGlazingCamerasN` | БОЛЬШЕ шт | 0.1 | Число воздушных/аргоновых камер | **Особенности стеклопакетов:** - **Светопропускание** = как много естественного света проходит в помещение (больше = лучше) - **Солнцепропускание** = как много солнечного тепла/излучения проходит (в России: меньше = лучше, потому что внутри есть отражающее напыление) - Двухкамерный (с аргоном) почти всегда лучше однокамерного - Трёхкамерные = премиум для холодного климата **Примеры интерпретации:** - **5.0 ⭐⭐⭐⭐⭐**: трёхкамерный с хорошей теплопередачей, звукоизоляцией (обычно с аргоном и напылением). - **3.0 ⭐⭐⭐**: двухкамерный, среднее качество - **1.0 ⭐**: однокамерный старого образца или с плохими характеристиками ### НАБОРЫ: какие параметры учитываются | № | Параметр | Поле БД | ЛУЧШЕ | Вес | Описание | |---|-------------------|--------------------------|------------------|-----|---------------------------------------------------| | 1 | Актуальность | `dModify` | МЕНЬШЕ (свежее) | 0.3 | Дата последнего обновления (timestamp) | | 2 | Доставка | `bSetDelivery` | ДА (1) | 0.8 | Включена ли доставка в стоимость | | 3 | Монтаж/демонтаж | `bSetUninstallInstall` | ДА (1) | 1.0 | Включены ли услуги монтажа и демонтажа | | 4 | Подоконник | `sSetSill` | ДА (1) | 0.5 | Включен ли подоконник | | 5 | Водоотлив | `sSetPanes` | ДА (1) | 0.8 | Включен ли водоотлив/козырёк | | 6 | Откос | `sSetSlope` | ДА (1) | 0.5 | Включены ли откосы | | 7 | Климат-контроль | `sSetClimateControl` | ДА (1) | 0.3 | Включено ли управление микроклиматом | | 8 | Число предложений | `NumOffer` | БОЛЬШЕ | 0.2 | Популярность набора (кол-во активных предложений) | | 9 | Гибкость скидок | `iDiscountVariantsCount` | БОЛЬШЕ вариантов | 0.5 | Кол-во вариантов скидок из формулы офиса | | 10| Размер скидок | `fDiscountMax` | БОЛЬШЕ % | 1.0 | Максимальная скидка из всех вариантов | **ВАЖНО: Итоговый рейтинг набора состоит из трёх компонентов:** - Рейтинг параметров услуг (Актуальность, Доставка, Монтаж, Подоконник и т.д.) - Рейтинг входящего стеклопакета (ранжируется отдельно) - Рейтинг входящего профиля (ранжируется отдельно) Формула итогового рейтинга набора (fSetRating): ``` k1 = нормализованный TmpRating (услуги) * вес услуг k2 = нормализованный рейтинг стеклопакета * RARING_WEIGHT_GLAZING_IN_SET (обычно 1.5) k3 = нормализованный рейтинг профиля * RARING_WEIGHT_PVC_PROFILE_IN_SET (обычно 1.5) fSetRating = k1 + k2 + k3 (итого от 0.0 до 5.0 звёзд) ``` **Примеры интерпретации:** - **5.0 ⭐⭐⭐⭐⭐**: набор с премиум компонентами (хороший профиль и стеклопакет) + полный пакет услуг (доставка, монтаж, подоконник, откос, климат-контроль) + значительные скидки. - **3.5 ⭐⭐⭐⭐**: хороший профиль/стеклопакет + базовые услуги (доставка, монтаж) + скромные скидки. - **2.0 ⭐⭐**: эконом компоненты или слабые услуги (нет доставки, нет откосов). - **1.0 ⭐**: минимальный пакет или устаревшие предложения (давно не обновлялись). ### Когда запускать - **После первого развертывания** — заполнить рейтинги всех профилей, стеклопакетов и наборов. - **После изменения каталога** (добавление нового профиля/стеклопакета/набора). - **После уточнения характеристик** (например, поставщик предоставил новые данные). ```bash poetry run python oknardia/manage.py make_rating ``` - **По расписанию** (например, ежемесячно, чтобы пересчитать популярность): ```bash 30 2 * * 1 cd /home/user/app-path/2022-oknardia && poetry run python oknardia/manage.py make_rating >> /var/log/oknardia-rating.log 2>&1 ``` - **После обновления весов** в `settings.py` (константы `RANK_PVCP_*`, `RANK_GLAZ_*`). ### Откат и безопасность - **Безопасна для повторного запуска** — пересчитывает все рейтинги заново. - **Всегда обновляет только рейтинги** — другие данные в таблицах не меняются. - **Откат через SQL** — если нужно установить нулевые значения (перед запуском рекомендуется бэкап базы): ```sql -- Очистить рейтинги профилей UPDATE oknardia_pvcprofiles SET fProfileRating = 0.0, sProfileDescription = '{}'; -- Очистить рейтинги стеклопакетов UPDATE oknardia_glazing SET fGlazingRating = 0.0, sGlazingDescription = '{}'; -- Очистить рейтинги наборов UPDATE oknardia_setkit SET fSetRating = 0.0, sSetDescription = '{}'; ``` ### Примеры из реальных данных Пример вывода `--verbosity 1`: ``` === НАЧАЛИ ПЕРЕСЧЁТ РЕЙТИНГОВ === ======================================== [ЭТАП 1]: Пересчёт рейтингов ПРОФИЛЕЙ... ======================================== ✓ Обнулены рейтинги у 94 профилей ✓ Найдено 94 профилей для ранжирования ✓ Сохранено 94 профилей с финальными рейтингами ============================================= [ЭТАП 2]: Пересчёт рейтингов СТЕКЛОПАКЕТОВ... ============================================= ✓ Обнулены рейтинги у 97 стеклопакетов ✓ Найдено 97 стеклопакетов для ранжирования ✓ Сохранено 97 стеклопакетов с финальными рейтингами ================================================ [ЭТАП 3]: Пересчёт рейтингов НАБОРОВ (SetKit)... ================================================ ✓ Обнулены рейтинги у 27 наборов ✓ Найдено 27 наборов для ранжирования ✓ Сохранено 27 наборов с финальными рейтингами [OK!] ПЕРЕСЧЁТ РЕЙТИНГОВ ЗАВЕРШЁН УСПЕШНО! • Обновлено профилей: 94 • Обновлено стеклопакетов: 97 • Обновлено наборов: 27 ``` Пример вывода `--verbosity 3` (наиболее подробный): ``` === НАЧАЛИ ПЕРЕСЧЁТ РЕЙТИНГОВ === ======================================== [ЭТАП 1]: Пересчёт рейтингов ПРОФИЛЕЙ... ======================================== ✓ Обнулены рейтинги у 94 профилей ✓ Найдено 94 профилей для ранжирования ... ... ==================================================================================================== ПРОФИЛЬ: politech W80 (ID: 78) ==================================================================================================== Характеристика Значение Ранг (0..1) Вклад ---------------------------------------------------------------------------------------------------- Высота в проёме 120 мм 0.368 * Популярность 0 предл. 0.000 Теплопередача 0.91 Ro 0.657 *** Толщина профиля 80 мм 0.588 ** Толщина стеклопакета 42 мм 0.409 ** Уплотнители 3 контуров 1.000 ***** Фальц 14 мм 0.150 Число камер 12 шт 0.714 *** Шумоизоляция 44.00 дБ 0.909 **** ---------------------------------------------------------------------------------------------------- ИТОГО: Рейтинг = 4.94/5.0 **** ... ... ✓ Сохранено 94 профилей с финальными рейтингами ============================================= [ЭТАП 2]: Пересчёт рейтингов СТЕКЛОПАКЕТОВ... ============================================= ✓ Обнулены рейтинги у 97 стеклопакетов ✓ Найдено 97 стеклопакетов для ранжирования ... ... ==================================================================================================== СТЕКЛОПАКЕТ: Однокамерный 5-4, 25 мм (И+аргон) (ID: 60) | Марка:СПО 5М1-Ar16-И4 ==================================================================================================== Характеристика Значение Ранг (0..1) Вклад ---------------------------------------------------------------------------------------------------- Камеры — 0.000 Светопропускание 74.00% 0.824 **** Солнцепропускание 58.00% 0.450 ** Теплопередача 0.91 Ro 0.936 **** Толщина 25 мм 0.400 ** Шумоизоляция — 0.429 ** ---------------------------------------------------------------------------------------------------- ИТОГО: Рейтинг = 4.87/5.0 **** ... ... ✓ Сохранено 97 стеклопакетов с финальными рейтингами ================================================ [ЭТАП 3]: Пересчёт рейтингов НАБОРОВ (SetKit)... ================================================ ✓ Обнулены рейтинги у 27 наборов ✓ Найдено 27 наборов для ранжирования ... ... ======================================================================================================================== НАБОР: Элит (ID: 3) ======================================================================================================================== Параметр Значение Ранг (0..1) Вклад ------------------------------------------------------------------------------------------------------------------------ Актуальность свежий 0.375 * Водоотлив ✓ Да 1.000 ***** Гибкость скидок 0 вариантов 0.500 ** Доставка ✓ Да 1.000 ***** Климат-контроль ✓ Да 1.000 ***** Монтаж ✓ Да 1.000 ***** Откос ✓ Да 1.000 ***** Подоконник ✓ Да 1.000 ***** Размер скидок 0.0% 0.500 ** Число предложений 46 шт 0.250 * ------------------------------------------------------------------------------------------------------------------------ ИТОГО: Рейтинг = 4.16/5.0 **** ... ... [OK!] ПЕРЕСЧЁТ РЕЙТИНГОВ ЗАВЕРШЁН УСПЕШНО! • Обновлено профилей: 94 • Обновлено стеклопакетов: 97 • Обновлено наборов: 27 ``` ## Оркестрация и reload веб-сервера Важно: - reload веб-сервера не встроен в management-команды; - это отдельная операция окружения. Пример для systemd + gunicorn: ```bash sudo systemctl reload gunicorn ``` Рекомендуемый batch-сценарий: ```bash cd /Users/e-serg/PRJ/2022-oknardia poetry run python oknardia/manage.py regenerate_seria_prerender --force poetry run python oknardia/manage.py generate_sitemaps sudo systemctl reload gunicorn ``` ## Cron/systemd timer (пример) Пример cron (раз в сутки в 03:20): ```bash 20 3 * * * cd /Users/e-serg/PRJ/2022-oknardia && poetry run python oknardia/manage.py regenerate_seria_prerender --force && poetry run python oknardia/manage.py generate_sitemaps >> /var/log/oknardia-maintenance.log 2>&1 ``` Если нужен reload после batch, добавляй отдельной строкой/шагом оркестратора. ## Диагностика Быстрая проверка конфигурации: ```bash cd /Users/e-serg/PRJ/2022-oknardia poetry run python oknardia/manage.py check ``` Типовые причины проблем: - нет прав записи в директории `templates/seria_info/prepared` или `MEDIA_ROOT/_serv_sitemap`; - устаревшее виртуальное окружение / неустановленные зависимости; - запуск не из того каталога. ## План миграции `/service/*` -> management commands Текущее направление: - все тяжелые и административные операции переносить из HTTP в management-команды; - `/service/*` оставлять только как thin UI/мониторинг или убрать полностью. --- См. также: - `SETUP.md` - `README.md`