Files
2022_oknardia/MANAGEMENT_RUNBOOK.md

940 lines
52 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 <command> [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/`.
- генерирует **3 типа файлов** для каждой серии (верхняя статья НЕ кешируется, она рендерится динамически из БД):
- `{seria_id}_id_static_flaps.html` — схемы открывания
- `{seria_id}_id_static_graph.html` — график ввода в эксплуатацию
- `{seria_id}_id_static_map_stats.html` — карта + статистика
### Контекст: двухуровневое кеширование
Система использует двухуровневое кеширование:
- **Статический кеш** — дорогостоящие операции (геокоординаты, графики, схемы). Генерируются один раз и сохраняются на диск.
- **Динамические данные** — верхняя статья про серию (может редактироваться через админку, видно БЕЗ перезагрузки контейнера) и таблица оконных проёмов (показывает актуальные предложения).
Подробнее: см. [`CACHE_PRERENDER_SYSTEM.md`](CACHE_PRERENDER_SYSTEM.md)
### Проверка без записи файлов (dry-run):
```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
```
Вывод:
```
OK seria 210: 3 кеш-файла созданы
OK seria 100: 3 кеш-файла созданы
...
Готово. Обработано: 31. Создано/пересоздано: 31 × 3 файла. Пропущено: 0.
```
Принудительная пересборка всех 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
```
### Когда запускать
- **После первого развертывания** — сгенерировать кеш всех 31 серии один раз.
- **После обновления логики `catalog_seria_info`** — изменились параметры графика или карты.
- **После изменения координат зданий** — geo-данные обновлены.
- **После добавления новых зданий в серию** — карта и список зда<D0B4><D0B0>ий изменились.
- **По расписанию** (опционально, если данные по геокоординатам обновляются):
```bash
0 3 * * 0 cd /Users/e-serg/PRJ/2022-oknardia && poetry run python oknardia/manage.py regenerate_seria_prerender >> /var/log/oknardia-prerender.log 2>&1
```
### Когда НЕ нужна регенерация
- **При добавлении новых предложений/цен** — таблица окон обновляется при каждом запросе автоматически.
- **При редактировании верхней статьи через админку** — она рендерится динамически, кешируется НЕ нужно.
- **При изменении наличия/статуса профилей** — рейтинги пересчитываются запросом через `make_rating`.
## 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 символов (исключая теги `<cut>`) |
| `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`