AI Asist
v2
Д
Д

Плейбук: реклама Ozon — данные, оценка окупаемости, решения

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

Плейбук: реклама Ozon — данные, оценка окупаемости, решения

Срабатывает, когда пользователь спрашивает про рекламу Ozon: «куда уходит рекламный бюджет», «по каким запросам показываемся / сливаем», «разбей рекламу по типам кампаний», «ДРР по артикулам Ozon», «почему артикул X жрёт рекламу», «поисковые запросы по товару», «окупается ли реклама», «выключать или масштабировать», «почему просели заказы».

Файл делится на три слоя, и путать их не надо:

  • A. Данные (§0–§5) — где лежит, чем тянуть, грабли Performance API, что Ozon не даёт.
  • B. Методика оценки (§6) — на чём считается вердикт «окупается / нет» и какие решения из данных вообще следуют. Единственный раздел, по которому делается вывод.
  • C. Кейсы (§8+) — датированные разборы, только как иллюстрация.

Стек: mp-system (корень), .venv/bin/python, БД mp.db, shop 2 = Мой Ozon, shop 4 = Siluetta Ozon. Performance API — app/ozon/performance.py (PerformanceClient). Расчёт прибыли/ДРР — data/notes/ozon/profit-methodology.md. Зрелый выкуп и прогноз хвоста — data/notes/ozon/orders-ad-cpo-playbook.md (не дублировать сюда).


Быстрый production-отчёт CPL/CPO

Для стандартного read-only запроса сначала использовать report.cmd ads, не ручной SQL:

.\report.cmd ads --shop "Ozon Siluetta"
.\report.cmd ads --shop "Ozon Siluetta" --sku "ELS004 Black" --date yesterday
.\report.cmd ads --shop "Ozon Siluetta" --period "за месяц"

Если период не указан, реклама берётся за последние 7 закрытых дней по Москве. Фильтр --sku понимает регистр, пробелы и общие алиасы себестоимости (ELS004 Black совпадает с карточкой ELS04-black). Старые JSON-поля не удаляются.

Дополнительные формулы:

CPL = рекламный расход / рекламные добавления в корзину
атрибутированные заказы = direct orders + model orders (halo)
сырой CPO = рекламный расход / атрибутированные заказы
ожидаемые выкупы = Σ(атрибутированные заказы offer_id × зрелый выкуп offer_id)
CAC ожидаемого выкупа = рекламный расход / ожидаемые выкупы
доступная маржа = 30-дневный вклад после себеса и УСН, но до рекламы / net-выкупы

Зрелый выкуп берётся по 60-дневной когорте, закончившейся 15 дней назад; сначала размер, при выборке меньше 10 завершённых заказов — артикул. Если и по артикулу меньше 10, вердикт недостаточно данных, без фолбэка на среднее магазина.

Оценка CAC ожидаемого выкупа относительно доступной маржи:

  • до 70% включительно — вменяемый CPO;
  • выше 70% и до 100% включительно — пограничный CPO;
  • выше 100% — дорогой/убыточный CPO;
  • расход есть, но корзин или атрибутированных заказов нет — нет конверсий;
  • нет зрелого выкупа, себеса или 30-дневной экономики — недостаточно данных.

Отчёт не вызывает Performance API. Неполное покрытие периода обозначается complete=false; после успешного ответа не читать БД и плейбуки повторно.

🔴 Границы применимости этого отчёта. Все его метрики после CPL (сырой CPO, CAC ожидаемого выкупа, вердикты «вменяемый / пограничный / дорогой») стоят на атрибутированных заказах Ozon (orders + orders_model). Эта атрибуция не выдерживает проверки на согласованность (§6.1), поэтому отчёт годится как быстрая диагностика ставки и сравнение кампаний между собой, но не как вердикт об окупаемости артикула. Вердикт — только по §6: знаменатель «все корзины / все заказы», метрика CPS против вклада. CPL (расход / рекламные корзины) от атрибуции не зависит и остаётся валидным.


0. Перед стартом: данные свежие?

Реклама и кампании часто устаревшие — сперва доингест (Performance API асинхронный, тянется минутами):

.venv/bin/python scripts/ingest_ozon.py --shop-id 2 campaigns
.venv/bin/python scripts/ingest_ozon.py --shop-id 2 ad-stats --from YYYY-MM-DD --to YYYY-MM-DD

Кладётся в ozon_ad_metrics_daily (день × SKU × кампания: views/clicks/orders/money/placement) и ozon_campaigns (тип/плейсмент кампаний).

Креды Performance. Резолв: shops.perf_cred_path → файл Ozon_Cred.txt (Client ID / Client Secret), резолвер app/web/shop_status.py::_resolve_ozon_perf. После Win→Linux миграции в perf_cred_path остаётся мёртвый Windows-путь → «Performance creds не заполнены». Фикс: UPDATE shops SET perf_cred_path='/home/mi/Bot_TG/mp-system/Ozon_Cred.txt' WHERE id=?.


1. Таксономия: типы кампаний и плейсменты

Тип кампании (ozon_campaigns.adv_object_type / payment_type): | adv_object_type | payment_type | Что | |---|---|---| | SKU | CPC | Оплата за клик (товарная) | | SEARCH_PROMO | CPO | Оплата за заказ (% с заказа, ДРР-cap) | | BANNER | CPM | Баннер | | VIDEO_BANNER | — | Видеобаннер | | REF_VK / REF_BLOGGER | — | Внешний трафик (VK / блогеры) |

Плейсмент (placement, для CPC): | placement | По-русски | Фразы? | |---|---|---| | PLACEMENT_TOP_PROMOTION | Поиск (топ выдачи) | да (отчёт по фразам только тут) | | PLACEMENT_SEARCH_AND_CATEGORY | Поиск и рекомендации | частично (только поиск-часть; рекомендации склеены) | | PLACEMENT_PDP | Карточка товара | нет (только ручные кампании) | | PLACEMENT_OVERTOP | Поиск и главная (спецразмещение) | — | | PLACEMENT_TAKEOVER | Первые 4 плитки (спецразмещение) | — | | None (в ad_metrics) | — | это CPO-операции (за заказ) |

Нет плейсмента «только рекомендации». Рекомендации всегда в связке с поиском (SEARCH_AND_CATEGORY). Без фраз умеют: PDP (карточка, только ручные) и CPO.

Чего Ozon не даёт вообще (не искать, время не тратить)

  • Разбивки «поиск vs рекомендации» внутри кампании. views/clicks у SEARCH_AND_CATEGORY — одно число на оба размещения. В деньгах сплит доступен только в CPO-режиме «все товары» (all_sku_promo, §4).
  • Кликов по товару в аналитике карточки. В ответе аналитики (~60 метрик) поля clicks нет ни в каком виде. Ближайшее по смыслу — pdpViews (просмотр карточки).
  • Честной атрибуции заказа к рекламе. См. §6.1.
  • Минус-слов. Продавец их не ставит; вывод по фразам — только для решения вкл/выкл или ставки.

Сравнение типов РК: «Поиск» против «Поиск и Полки»

Siluetta Ozon (shop 4), все товары, 01.06–28.08.2026:

Тип РК Расход CPC Клик→корзина Цена корзины
SEARCH_AND_CATEGORY (Поиск и Полки) 345 531 1.83 ₽ 3.10% 59 ₽
TOP_PROMOTION (Поиск / Вывод в топ) 60 109 5.19 ₽ 3.81% 136 ₽

Помесячно соотношение не плавает (июнь 52/117, июль 64/143, август 58/145 ₽ за корзину). «Поиск» даёт клик качественнее — конверсия в корзину выше на ~23%, — но клик в 2.8× дороже, и качества не хватает, чтобы это отбить: корзина выходит в 2.3× дороже.

⚠ Не резать TOP_PROMOTION автоматически по цене корзины: у неё бывает другая задача (вывод новинки в топ, удержание позиции). Сначала проверить назначение кампании.


2. Разрез расхода по типам РК (главный запрос)

SELECT placement, payment_type, campaign_id, campaign_title,
       ROUND(SUM(money)) AS spend, SUM(clicks) AS clicks, SUM(orders) AS orders
FROM ozon_ad_metrics_daily
WHERE shop_id = :shop AND date BETWEEN :since AND :to
GROUP BY campaign_id ORDER BY SUM(money) DESC;

Чтение: placement=NULL → CPO (за заказ); TOP_PROMOTION → поиск-CPC; SEARCH_AND_CATEGORY → поиск+рекомендации-CPC. Свод по типу: GROUP BY placement (или payment_type).

Связка с прибылью: агрегировать SUM(money) per color_key(offer_id) из ozon_ad_metrics_daily. Исторический образец _oneshot_ozon_unit_shop2.py на 16.08.2026 сохранился только в незакоммиченной рабочей копии Hetzner и не является зависимостью плейбука. Сверка с finance: SUM(amount) WHERE operation_type_name IN ('Оплата за клик','Продвижение с оплатой за заказ') должна совпасть с Performance в пределах 0.1%.


3. Отчёт по поисковым запросам

Только для CPC-кампаний с placement = PLACEMENT_TOP_PROMOTION. Метод на стадии тестирования.

POST /api/client/statistics/phrases/json, тело:

{ "campaigns": ["<id>", ...], "dateFrom": "YYYY-MM-DD", "dateTo": "YYYY-MM-DD", "groupBy": "NO_GROUP_BY" }

Асинхронно → UUIDclient.wait_statistics(uuid)client.statistics_report_bytes(uuid) (JSON-вариант возвращает dict). Дата не раньше 2025-02-01.

Структура: { "<campaign_id>": { title, report: { rows: [...] } } }, строка = {sku, title, search_phrase, views, clicks, ctr}.

Только показы/клики/CTR — НЕТ денег и заказов по фразе. Расход на фразу можно лишь оценить: клики × средний CPC (CPC = расход_кампании / Σ кликов). Конверсию «фраза→заказ» API не отдаёт. CTR низкий = плохая релевантность показа.

Минус-слова продавец на Ozon сам НЕ ставит. Вывод по фразам — для решения вкл/выкл кампании или ставки, не для ручной минусовки.

Алгоритм исторического _oneshot_ozon_phrases_shop2.py: взять TOP_PROMOTION-кампании с расходом в окне, запросить отчёт, агрегировать фразы по кликам и оценить расход через средний CPC. На 16.08.2026 этот one-shot есть только в незакоммиченной рабочей копии Hetzner; воспроизводимый контракт — endpoint и SQL ниже, а не наличие файла. Кандидаты кампаний:

SELECT DISTINCT campaign_id FROM ozon_ad_metrics_daily
WHERE shop_id=:shop AND date BETWEEN :since AND :to AND placement='PLACEMENT_TOP_PROMOTION' AND money>0;

4. Прочие отчёты Performance (что ещё доступно)

Нужно Endpoint Метод Отдаёт
Дневная стата по кампаниям /api/client/statistics/daily GET расход/клики по дням
Расход по кампаниям /api/client/statistics/expense GET быстрый свод трат
CPO заказы /api/client/statistic/orders/generate/json POST {from,to} заказы CPO (исп. в ingest_cpo_orders)
CPO товары (выбранные) /api/client/statistic/products/generate/json POST {from,to} per-SKU: orders, ДРР, расход CPO + CPC-доля
CPO товары (все, сплит поиск/реком) /api/client/statistics/all_sku_promo/products/generate/json GET timeBounds.from/to продажи/заказы из поиска vs из рекомендаций
Конкурентные ставки /api/client/campaign/{id}/products/bids/competitive ставки конкурентов

Сплит «поиск vs рекомендации» (в деньгах/заказах) есть ТОЛЬКО в режиме «все товары» (all_sku_promo). Если CPO настроен как «выбранные товары» — этот отчёт даёт 404 «кампания не найдена», сплит недоступен (нужно переключить продвижение в кабинете на «все товары»).

⚠ CPO «выбранные товары» расход = CPO (за заказ, ДРР-cap) + CPC (за клик) в одном инструменте (поля MoneySpent/DRR и MoneySpentFromCPC/ordersFromCPC).


5. Механика Performance API

  • Авторизация: OAuth2 client_credentials → Bearer (TTL ~30 мин), уже в PerformanceClient.
  • Тяжёлые отчёты асинхронные: submit → UUIDwait_statistics(uuid, max_wait_sec=600)statistics_report_bytes(uuid). /json-вариант пути → dict сразу; обычный → CSV/ZIP.
  • Метод иногда GET, иногда POST — если 405, поменять метод; если параметры через query (timeBounds.from) — это GET. Есть rate-limit (раздел «Лимиты» в доке).
  • Зеркало доки: Dev-API-Ozon/html/latest/performance/index.md.

NOT_STARTED — норма, не баг

wait_statistics поллит статус UUID. Типичный сценарий: UUID создан → NOT_STARTED 2–5 мин → IN_PROGRESS ~30c → OK. 240 секунд — мало, использовать max_wait_sec=600. Реализовано в pipeline.py: step("ad-stats", ..., max_wait_sec=600).

Лимит 1 одновременная выгрузка на аккаунт → 429 «максимум 1» при попытке создать второй UUID. Надо дождаться завершения первого, потом повторить. PerformanceClient.request() ретраит 429 3 раза с паузой 30с — если очередь не освобождается за 90с, кидает исключение.

Колонки CSV переименованы Ozon (~2025)

POST /api/client/statistics возвращает ZIP/CSV с заголовками, которые изменились. Актуальные имена (июнь 2026, verified):

Старое Актуальное
Заказы Продано товаров
В корзину Добавления в корзину
Продажи, ₽ Продажи в продвижении, ₽
Сумма заказов, ₽ Заказано на сумму, ₽
Заказы модели Продано товаров модели
Продажи с заказов модели, ₽ Продажи в продвижении с заказов модели, ₽

Парсер app/ozon/ingest._parse_ad_stats_csv сначала ищет известные точные варианты через _idx("старое", "новое"), затем использует смысловой fallback по заголовкам:

  • количество заказов: содержит заказ / продано / заказано, но не содержит модель и денежные маркеры;
  • сумма заказов: приоритетно Заказано на сумму, ₽; затем старые поля продаж/сумм. Fallback содержит / руб / выруч / сумм / продажи, но не содержит модель и расход;
  • количество/сумма модели: те же правила, но обязательно содержит модель.

Это нужно, потому что Ozon меняет CSV-заголовки без смены endpoint. Если i_orders is None — в лог уйдёт WARNING "колонка 'Заказы'/'Продано товаров' не найдена". Диагностика: поднять уровень ozon_ingest до DEBUG и смотреть строку CSV campaign=X headers: [...].

Симптом: все записи ozon_ad_metrics_daily.orders = 0 при ненулевом money. Значит колонка renamed и парсер её не нашёл — добавить новое имя в _idx(...).

ingest_campaigns и ARCHIVED-кампании

GET /api/client/campaign возвращает только живые кампании. Архивированные из ответа исчезают, но в ozon_campaigns остаются со старым статусом (RUNNING/STOPPED/INACTIVE). В результате ingest_ad_stats посылает statistics_request с невалидными campaign_id → Ozon принимает, UUID выдаёт, но отчёт НИКОГДА не стартует (NOT_STARTED бесконечно).

Фикс (реализован в ingest_campaigns): после upsert свежих данных кампании, не попавшие в ответ API, помечаются CAMPAIGN_STATE_ARCHIVED, и ingest_ad_stats их пропускает.


6. Методика оценки: на чём делается вывод

Установлено 28.08.2026 на разборе ELS004 Black (Siluetta Ozon, 05.07–22.08). Заменяет прежний раздел «CPA с поправкой на выкуп», который стоял на атрибуции Ozon.

6.1. Атрибуция Ozon не годится как основа вердикта

Что означают поля ozon_ad_metrics_daily (парсер app/ozon/ingest._parse_ad_stats_csv):

Поле Колонка CSV Смысл
orders «Продано товаров» (ex-«Заказы») заказы самого рекламируемого SKU
orders_model «Продано товаров модели» заказы других SKU той же модели (гало)
orders_money «Заказано на сумму, ₽» другая стадия/модель атрибуции
orders_model_money «Продажи в продвижении с заказов модели, ₽» продажи по гало

Официальное определение Ozon: «Продано товаров — количество заказов, которые совершили покупатели, увидев и заказав товары с продвигаемых позиций» (knowledge-base-mp/ozon/ads/extracted-text/08-promotion-analytics.txt). Несмотря на слово «Продано», стадия — заказ, а не выкуп.

🔴 orders_money — не денежная пара к orders. Парсер намеренно ставит «Заказано на сумму» первой. Проверка: ELS004 Black, 14–20.08 — orders_money = 609 390 ₽ ≈ 111 единиц при orders + orders_model = 38; в отдельные недели orders_money превышает всю валовую выручку артикула. Считать ДРР = money / orders_money и CPA = money / orders на одной строке отчёта нельзя — это разные базы.

🔴 Проверка на согласованность, которую атрибуция не проходит. Если принять её за истину, то для ELS004 Black получается: рекламная корзина конвертится в заказ на 19%, органическая — на 50–66%, и цифра скачет 29% → 66% неделя к неделе. Корзина не знает, из какого плейсмента пришёл покупатель — такой разрыв нефизичен. Вывод: раскладывать заказы на «рекламные» и «органические» по данным Ozon нельзя.

⚠ Гало задваивается: если на одну карточку работают кампании на M и на S, заказ размера S по клику в кампании M попадёт в orders_model кампании M, и наоборот. Суммировать гало по кампаниям одной модели — с оговоркой.

6.2. Органика не контрольная группа

Инкрементальность «реклама против органического baseline» для наших карточек не считается: органический трафик не независим от рекламы — включили РК, органика появилась; выключили, через неделю пропала.

Подтверждение на ELS004 Black, 11 недель 05.06–20.08:

  • корреляция недельный расход ↔ все заказы r = 0.94; клики РК ↔ все заказы r = 0.97;
  • разгон июня: расход 1.6 → 14.5 тыс ₽, заказы 26 → 110 — движение одной пары, а не «реклама плюс независимая органика»;
  • обратный эпизод 29.07–06.08: показы в поиске рухнули с ~5 000 до ~1 000 в день на девять дней, а totalViews не просел — дыру закрыла реклама. Именно на этой неделе лучший CPS.

⚠ r = 0.94 — это корреляция, а бюджет двигали не случайно. Отделить вклад рекламы от сезона наблюдением нельзя в принципе; нужен эксперимент (§6.6).

6.3. Знаменатель — всё

Из 6.1 и 6.2 следует единственный корректный знаменатель: все корзины и все заказы товара, без попытки выделить рекламную долю.

CPL (цена корзины) = расход / рекламные корзины (atbs)      # атрибуция не нужна
CPO               = расход / ВСЕ заказы товара
CPS (стоимость продажи) = расход / продажи после возвратов  # ← вердикт считается по нему
ДРР               = расход / выручка продаж после возвратов

Продажи после возвратов = отправления в статусе delivered минус те, по которым есть финансовая операция «Получение возврата, отмены, невыкупа от покупателя». На Ozon FBO нет отдельного статуса returned: отмена и невыкуп при доставке схлопнуты в cancelled, а возврат после выкупа виден только в ozon_finance_operations. По ELS004 Black за 01.07–27.08 это 27 шт из 263 выкупленных = 10% сверх невыкупа.

⚠ Когорта должна дозреть. Пока в неделе много отправлений вне delivered/cancelled, продажи и CPS ещё уедут — такую неделю помечать, а не сравнивать. Правило зрелости — orders-ad-cpo-playbook.md.

6.4. Порог: вклад до рекламы

Единственный абсолютный эталон. Считается по факту ozon_finance_operations, привязанным к отправлениям артикула, за окно ≥1 месяца:

нетто с Ozon = «Доставка покупателю»
             − «Получение возврата, отмены, невыкупа от покупателя»
             − «Доставка и обработка возврата, отмены, невыкупа»
             − «Оплата эквайринга» − упаковка партнёрами и материалы
нетто на единицу = нетто с Ozon / (проданные единицы − возвращённые)
вклад до рекламы = нетто на единицу − себес (CostBook) − УСН 6% от покупательской цены

Отмены и невыкупы уже внутри: их стоимость размазана на те единицы, что доехали.

Пример, ELS004 Black, 01.07–27.08.2026: нетто 713 320 ₽ на 321 нетто-проданную единицу = 2 222 ₽/шт; − себес 850 − УСН 183 = вклад 1 189 ₽.

Вердикт: CPS < вклад до рекламы → реклама окупается. Отношение вклад / CPS — запас.

⚠ Что в этот вклад не входит: общемагазинные операции Ozon, не привязанные к отправлению (сбор первых отзывов, досрочная выплата, кросс-докинг, подписка Premium, страхование — по shop 4 это ≈106 ₽ на проданную единицу), и всё вне Ozon (ФФ, доставка до склада, ФОТ). Для строгой оценки вычитать; на вердикт по ELS004 Black это не влияло (запас 1.8× → 1.7×).

6.5. Две группы метрик: опережающие и основные

Разделять всегда — они ломаются в разных местах и лечатся разным.

Группа Метрики Про что
Опережающие показы РК, клики, CTR, клик→корзина, корзины, цена корзины (CPL) работа аукциона и креатива: сколько и по какой цене реклама приводит людей
Основные все заказы, корзина→заказ, продажи после возвратов, CPS, ДРР доходит ли приведённый интерес до денег

Диагностическое правило. Опережающие ровные, а основные едут вниз — проблема не в рекламе. Между корзиной и заказом стоят: наличие размера в кластере покупателя, срок доставки, цена, конкуренты. Поднимать ставку в такой ситуации — тушить не тот пожар.

Безубыточная цена корзины = вклад до рекламы × конверсия корзина→продажа. Она даёт опережающий порог, который виден раньше, чем испортится CPS.

6.6. Что данными не решается — только эксперимент

Наблюдением нельзя получить: вклад рекламы отдельно от сезона; эластичность по цене; правильное деление бюджета между цветами/размерами.

Схема (одна на все такие вопросы). Плечо — одна пара цвет/размер, контроль — соседний цвет той же модели, окно — две недели, метрики — все корзины, корзина→заказ и CPS обоих плеч. Ставится сдвиг ровно одного параметра: бюджет ±30–50% либо цена кабинета ±10–15%.

🔴 Тест ставится на товаре в фазе роста, а не на уходящем сезоне. Контроль снимает тренд, но падающие объёмы убивают чувствительность. Ориентиры по чувствительности (80% мощности, 5% уровень, две недели на плечо, конверсия корзина→заказ ~30%):

Объём на плечо Ловится разница в заказах Ловится разница в конверсии
240 заказов / 900 корзин ~26% относительных ~6 п.п.
480 заказов / 1 800 корзин ~18% ~4 п.п.
100 заказов / 400 корзин ~40% ~9 п.п.

ELS004 Black в июле–августе 2026 давал ~120 заказов и ~460 корзин в неделю на цвет — то есть на границе, где тест ещё имел смысл. К концу сезона объёмы падают, и окно закрывается.

Практический вывод: тест закладывается в план запуска нового товара, а не изобретается задним числом. Статус запусков — ../Strategy-Operations/products/<товар>/launch.yaml (скилл launch-operations); там же держать окно теста, плечо и контроль.

6.6a. Цена как фактор: проверено, зависимости не найдено

Разбор ELS004 Black M, Siluetta Ozon, 05.07–22.08.2026, 41 день с полными данными (ozon_buyer_price_daily + воронка + постинги).

Корреляции цены покупателя со всей воронкой около нуля и со знаком «дороже → чуть больше корзин»: корзины +0.18, заказы +0.16, корзина→заказ +0.10. Внутри уровней остатков (главный конфаундер снят) — +0.08 / +0.05 / +0.19. Терцили по цене плоские: корзин в день 24.9 / 27.1 / 27.9, конверсия 29 / 33 / 28%.

⚠ Корреляция цены кабинета с корзинами (−0.59) — артефакт времени, а не эффект: 5 690 стояла почти весь июль, 5 100/5 490 — в августе. Уровней всего четыре и они не пересекаются во времени. Не читать как эластичность.

Квази-эксперимент 13.08 (цену black подняли 5 100 → 5 490, у brown и grafit не трогали):

Товар 29.07–05.08 06–12.08 13–18.08 Цену меняли
black-M 37% 26% 20% −10%, затем +7.6%
black-S 30% 34% 30% −10%, затем +7.6%
brown-M 27% 35% 32% нет
grafit-M 33% 34% 35% нет

Направление не воспроизводится: на снижении цены black-M ухудшился, а black-S улучшился; grafit при неизменной цене не просел вовсе. Второй эпизод — 14–15.07, самая низкая цена покупателя за период (−15%): заказы 6 и 9 при 10 накануне и 7 назавтра, конверсия 24 и 31% против 43 и 37%. Скидка подъёма не дала.

Почему цену покупателя нельзя брать за рычаг. За период цена кабинета имела размах 11.6% (5 100–5 690), а цена покупателя — 34.1% (2 408–3 229), при corr(кабинет, покупатель) = +0.13. При неизменной цене кабинета 5 690 витринная цена гуляла на 30.9% (σ = 6.6%), день к дню в среднем на 3.6%, максимум на 14.7%. Доля покупателя от кабинета — 43–59%. Ozon двигает витрину своей скидкой сильнее и почти независимо от нас.

⚠ Это не доказательство отсутствия эластичности: свою цену за 41 день подвинули всего на 11.6%, на фоне падающего сезона и рушащейся географии остатков. Такой дизайн не поймал бы и эффект средней силы. Мерить эластичность — только намеренным тестом по §6.6, на растущем товаре.

Что из этого следует для разбора: версию «просели заказы, потому что подняли цену» проверять этими данными можно и обычно она закрывается. Заявлять «цена не влияет» вообще — нельзя.

6.7. Дефицит рекламируемого размера бьёт по всей карточке

Карточка одна: реклама размера M генерирует показы и корзины всех размеров. Поэтому проверять остатки надо по тому размеру, на который льют бюджет, и не в штуках, а в числе складов с остатком — товар может быть «в наличии» суммарно, но отсутствовать в кластере покупателя, и тогда корзина не превращается в заказ из-за срока доставки.

Кейс ELS004 Black, август 2026 (M — 2/3 бюджета):

Размер Неделя Расход Корзины Заказы Корз.→заказ Складов Остаток
M 26.07 12 020 145 52 36% 22 121
M 09.08 11 617 230 50 22% 14 56
M 16.08 12 523 195 47 24% 6–8 24–33
S 16.08 4 279 170 53 31% 13 37
L 16.08 0 96 16 17% 2 2

Цена корзины при этом держалась 50–70 ₽ без тренда — реклама отработала честно. L показывает второй эффект: реклама выключена с 26.07, а корзины идут (74–111/нед) — их приносит карточка целиком; заказы упирались в остаток 1–6 шт.

Следствия: рекламировать размер с околонулевым остатком — слив; чинить надо поставку по географии (ozon/supply-playbook.md), а не ставку.

6.8. Пороги мониторинга

Метрика Порог Что означает
CTR < 5.5% две недели подряд аукцион отдаёт худшую аудиторию — фото/ставка
Цена корзины (CPL) > 80% безубыточной подбирается к пределу
Корзина→заказ < 28% проблема ниже воронки, реклама ни при чём
Складов с остатком по рекламируемому размеру < 10 чинить поставку, не ставку

Источники цен для проверки версии «дело в цене» — ozon_buyer_price_daily (ozon/buyer-price-playbook.md). ⚠ Покрытие дырявое: по shop 4 за 05.07–22.08 есть 41 день из 49 (нет 25–28.07 и 19–26.08); по shop 2 прогоны с 23.08.2026 висят в статусе running с rows_count = 0. Считать только по дням, где снимок есть.

Пороги калиброваны на ELS004 Black (комбинезон, средний чек ~5 400 ₽ цены продавца). Для другой категории пересчитывать, а не переносить.

6.9. Зависимость от подписки Premium

Блок опережающих метрик по карточке (все корзины, все показы, конверсия корзина→заказ) идёт из ozon_sku_traffic_daily и доступен только при активной платной аналитике. С 22.08.2026 на shop 4 тариф отключён, и Ozon отдаёт лишь 8 метрик вместо ~60 (revenue, orderedUnits, soldRevenue и динамика) — это не поломка сбора. Без подписки знаменатель «все корзины» пропадает и остаются только рекламные счётчики плюс постфактумный CPS.


7. Чек-лист разбора рекламы Ozon

Данные

  1. ☐ Кампании и ad-stats доингещены за окно? Креды Performance резолвятся (perf_cred_path Linux)?
  2. ☐ В ozon_ad_metrics_daily есть ненулевой money за окно? Если orders=0 при money>0 — смотреть §5 «Колонки CSV переименованы» (парсер не нашёл Продано товаров).
  3. ☐ Свод расхода по placement/campaign_id снят — видно, где деньги (CPO vs поиск vs поиск+реком)?
  4. ☐ Реклама сверена с finance (0.1%)?
  5. ☐ Дни с неполными данными исключены, а не занулены? (пропуски снимков аналитики, отключённый тариф §6.9, оборванный день по расходу). Все метрики считаются на одном наборе дней, и число дней указано в выводе.

Оценка (§6)

  1. ☐ Недели полные и выровнены по дню недели; недозревшие когорты помечены?
  2. ☐ Знаменатель — ВСЕ корзины и ВСЕ заказы; разложения на «рекламные/органические» нет?
  3. ☐ Продажи считаются после возвратов (delivered минус финансовые возвраты), а не по delivered?
  4. ☐ Вклад до рекламы пересчитан по свежему finance-окну, а не взят из старого разбора?
  5. ☐ Вердикт дан по CPS < вклад, а не по CPO/CPA из кабинета?
  6. ☐ Опережающие и основные метрики разведены; если основные едут при ровных опережающих — диагноз ищется ниже воронки, а не в ставке?
  7. ☐ Проверены остатки рекламируемого размера в разрезе числа складов (§6.7)?
  8. ☐ Версия «дело в цене» проверена по ozon_buyer_price_daily — и цена кабинета, и цена покупателя, только на днях со снимком (§6.6a)?

Вывод пользователю

  1. ☐ Названо, какие решения данные поддерживают, а какие требуют эксперимента (§6.6)?
  2. ☐ Если предлагается тест — товар в фазе роста и объёмов хватает на чувствительность, а не «поставим на уходящем сезоне»?
  3. ☐ Если копаем поиск — фразы тянем только по TOP_PROMOTION; помним: CTR ≠ конверсия, минус-слов на Ozon нет?

8. Реальный кейс (HZ030-Brown, Мой Ozon, июнь 2026)

Артикул убыточен по юните; реклама 25 263 ₽ = 60% бюджета магазина. Разложение: CPO (ДРР 10%, управляемо) ≈ 8.5к + CPC внутри CPO ≈ 8.6к + CPC «Поиск» 6.4к + CPC «поиск+полки» 1.7к. ~16.7к из 25.3к = CPC на общих фразах («комбинезон женский летний», «Каталог» — CTR 1.7–2.7%) с неуправляемым ДРР — вот это утечка, а не CPO-10%. Решение: по убыточным SKU оставлять CPO (ДРР-cap), а поисковый CPC резать. См. [[ads-playbook]] §3 (фразы), §2 (разрез по типам).


9. Реальный кейс (ELS004 Black, Siluetta Ozon, июль–август 2026)

Разбор, на котором построен §6. Окно 05.07–22.08, недели полные, только дни с полными данными (39 из 42 — пропуски снимков 25–27.07 и 20.08).

Опережающие держатся ровно: цена корзины 50–70 ₽, клик→корзина 3.7–4.1% без тренда, CPC ~2.3 ₽. Реклама отработала предсказуемо весь период.

Основные поехали:

Неделя Расход Корз. всего Заказы Корз.→заказ CPO Продажи CPS ДРР
05.07–11.07 13 529 322 98 30% 138 23 588 10.3%
19.07–25.07 13 016 323 105 33% 124 37 352 6.2%
02.08–08.08 16 910 519 171 33% 99 50 338 6.4%
09.08–15.08 ⚠ 16 753 502 129 26% 130 43 390 7.5%
16.08–22.08 ⚠ 16 802 461 116 25% 145 29 579 10.7%

Итого за зрелое окно: расход 90 798 ₽, корзины 1 516 РК из 2 402 всего (доля РК 63%), заказы 731, продажи после возвратов 222 шт на 1 207 365 ₽. CPS 408 ₽ при вкладе 1 189 ₽ — запас 2.9×. Безубыточная цена корзины 110 ₽ против факта 60 ₽.

Диагноз: опережающие ровные, основные вниз → смотреть ниже воронки. Нашлось в §6.7: у размера M (2/3 бюджета) число складов с остатком упало 22 → 6, и конверсия корзина→заказ там просела 36% → 22%. Цена покупателя в тот же период снизилась (3 093 → 2 702 ₽), то есть версия «подняли цену» отпадает.

Принятые решения: рекламу не трогать (запас 2.9× и r=0.94 расход↔заказы); приоритет — поставка M по географии; L включать только после пополнения (с 26.07 по 27.08 расход на него был 0 при живых 74–111 корзинах в неделю и остатке 1–6 шт).

Фильтры