940 lines
52 KiB
Markdown
940 lines
52 KiB
Markdown
# 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`
|
||
|