AI Asist
v2
Д
Д

Плейбук: CTR-тесты главной фотографии Ozon

← Заметки  ·  Раздел: Ozon  ·  Файл: data/notes/ozon/ctr-photo-tests-playbook.md

Плейбук: CTR-тесты главной фотографии Ozon

Команды из чата: «сними данные до нового теста», «обнови тест», «статус CTR-тестов».

Источники истины и граница атомарности

  • Рекламные факты и победители: серверная /home/mi/Bot_TG/mp-system/mp.db.
  • Фото и Markdown: локальный Product-Photo-Studio/Test CTR.
  • Веб читает только серверный mirror /home/mi/Bot_TG/mp-system/data/ctr_tests, заданный через CTR_TESTS_WEB_ROOT (допустим fallback CTR_TESTS_MIRROR_ROOT).
  • DB capture не трогает файлы. Local materialize не вызывает API и не меняет серверную БД. Между фазами нет ложной «распределённой транзакции»: каждая фаза идемпотентна и повторяется с тем же operation_id.

CLI никогда не запускает Alembic. Перед любой DB-командой он печатает resolved SQLite path и сверяет его с обязательным абсолютным --expected-db. Роль также обязательна через CTR_TESTS_DB_ROLE=server|local и совпадающий --db-role.

Рабочий цикл закрытия

1. Server DB capture

На Hetzner из корня проекта:

export CTR_TESTS_DB_ROLE=server
.venv/bin/python scripts/ctr_photo_tests.py close-db \
  --database-url sqlite:////home/mi/Bot_TG/mp-system/mp.db \
  --expected-db /home/mi/Bot_TG/mp-system/mp.db --db-role server \
  --shop-id 4 --sku LT8307LS \
  --operation-id close-lt8307ls-2026-08-21

.venv/bin/python scripts/ctr_photo_tests.py export \
  --database-url sqlite:////home/mi/Bot_TG/mp-system/mp.db \
  --expected-db /home/mi/Bot_TG/mp-system/mp.db --db-role server \
  --file /tmp/ctr-tests-2026-08-21.json

close-db сначала обновляет campaigns, затем запрашивает только валидированные CPC РК товара. Новая generic РК без артикула требует явный повторяемый --campaign-id. Текущий close принимает только RUNNING/INACTIVE; исторический update-db диагностирует STOPPED/ARCHIVED отдельно.

Повтор команды с тем же operation_id возвращает существующую immutable revision и не создаёт дубль. Окно не может быть будущим или длиннее 62 дней.

2. Передать JSON server → local

Передача выполняется отдельным scp/SFTP-шагом. ctr_photo_tests.py sync сети не использует. Сверить SHA-256 перед импортом.

3. Local import + materialize

На Windows из корня Big-System-MP:

$env:CTR_TESTS_DB_ROLE = 'local'
.venv\Scripts\python.exe scripts\ctr_photo_tests.py import `
  --database-url sqlite:///D:/YandexDisk/Bot_TG/Big-System-MP/mp.db `
  --expected-db D:\YandexDisk\Bot_TG\Big-System-MP\mp.db --db-role local `
  --file D:\Temp\ctr-tests-2026-08-21.json

.venv\Scripts\python.exe scripts\ctr_photo_tests.py materialize `
  --database-url sqlite:///D:/YandexDisk/Bot_TG/Big-System-MP/mp.db `
  --expected-db D:\YandexDisk\Bot_TG\Big-System-MP\mp.db --db-role local `
  --shop-id 4 --sku LT8307LS `
  --root "D:\YandexDisk\Bot_TG\Product-Photo-Studio\Test CTR"

Materialize безопасно и повторяемо:

  • выводит имя папки только из test_number, дат и state; имя из JSON не доверяется;
  • переименовывает Last_DD-MM в Test_N_DD-MM_DD-MM, сохраняя фото;
  • атомарно заменяет RESULT.md/README.md через временный файл + os.replace;
  • создаёт следующий Last_DD-MM;
  • на retry не удаляет существующие фото или RESULT;
  • запрещает .., вложенные SKU, symlink/junction/reparse points.

4. Local canonical → server web mirror

Сначала sync строит точную локальную staging-копию с manifest-prune. Корни не могут совпадать, быть предками друг друга или содержать ссылки:

.venv\Scripts\python.exe scripts\ctr_photo_tests.py sync `
  --root "D:\YandexDisk\Bot_TG\Product-Photo-Studio\Test CTR" `
  --mirror-root "D:\Temp\ctr-tests-server-mirror"

Затем staging-копия переносится отдельным проверяемым deploy-шагом в /home/mi/Bot_TG/mp-system/data/ctr_tests с точным mirror/delete semantics и SHA-256 сверкой. После переноса перезапуск web не нужен, если CTR_TESTS_WEB_ROOT уже настроен.

Правила расчёта

  1. Размер снимается только как терминальный сегмент. HZ20 и HZ020 — один alias-aware товар; identity case-insensitive.
  2. При наличии PLACEMENT_TOP_PROMOTION («Поиск») берётся только он, иначе PLACEMENT_SEARCH_AND_CATEGORY («Поиск и рекомендации»). Fallback при <100 кликов запрещён.
  3. <100 кликов: insufficient, папка всё равно архивируется. ≥100 и 0 корзин: валидный проигрыш, цена ∞.
  4. Первый eligible период с корзинами — стартовый победитель. Новый побеждает только при том же плейсменте и campaign set, одновременно большем CTR и меньшей стоимости корзины.
  5. Победители отдельны по плейсментам. Сравнение: последние пять периодов плюс действующий победитель.
  6. День команды относится к старому фото; новый Last начинается D+1 по Москве. Текущий день может быть неполным. Отсутствие фото — предупреждение, отсутствие поддерживаемого плейсмента/API error — abort.
  7. update-db добавляет immutable revision. Current winner детерминированно replay-ится; materialize пересобирает все MD товара и отдельно показывает исторический verdict и текущего победителя.

Migration release boundary

CTR migration c9a000a09 намеренно зависит от локальной warehouse c8a000a08. Развёрнутая серверная ветка c7 → b7e2ad19c4f0 → c4e8f1a2d5b9 → d1f7a3c92e04 сохранена byte-for-byte. Обе ветки сходятся только в merge e2a8c4f31b10. Деплой включает c8+c9, три серверные миграции и merge как одну проверенную release boundary; app/models.py ради CTR не менять.

Финальная проверка

  • DB path/role напечатаны и совпали с ожидаемыми;
  • capture вернул operation ID + content digest;
  • export/import digest совпал, open→closed envelope применён владельцу destination shop;
  • фото сохранилось, RESULT/README и следующий Last существуют после повторного materialize;
  • staging mirror не содержит удалённых локально managed-файлов;
  • веб показывает только raster/MD, MD скачивается attachment, ответы имеют nosniff;
  • server/local/staging SHA-256 сверены отдельным deploy-шагом.
Фильтры