AI Asist
v2
Д
Д

Siluetta Ozon: быстрый план-факт заказов и CPO через корзины

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

Siluetta Ozon: быстрый план-факт заказов и CPO через корзины

После получения JSON и до выводов «как дела» проверить журнал изменений и тестов: активные тесты и изменения, пересекающие период/базу сравнения. Отразить релевантные изменения рекламы и фото, сезонные решения и зрелость результата. Если новая цель владельца отличается от config/plan.yaml, назвать расхождение; не выдавать прежний норматив за новую цель и не менять план молча. Для этой проверки не нужны дополнительные API или полный рекламный разбор.

Проверено 12.09.2026. Маршрут для «как дела по план-факту», «вчера и 7 дней по позициям», «заказы против плана», «плановое CPO на основе стоимости корзин». Поддерживается Siluetta Ozon, shop_id=4. WB и другие магазины не включать.

Один запуск

Из корня Bot_TG:

.\report.cmd plan-fact
.\report.cmd plan-fact --format text
.\report.cmd plan-fact --date yesterday
.\report.cmd plan-fact --period "за неделю"
.\report.cmd plan-fact --since 2026-09-05 --to 2026-09-11

Из корня Big-System-MP:

.venv\Scripts\python.exe scripts\report_cli.py plan-fact

Без периода — вчера, последние 7 закрытых дней и предыдущие 7 дней в одном JSON. Все относительные даты по Москве. Текущий день запрещён. Магазин по умолчанию — siluetta-ozon; можно указать явно. --summary-only убирает строки по позициям. Фильтры остатков и --sku здесь не поддерживаются.

Windows-команда сама вызывает Hetzner через общий маршрут scripts/report_cli.py. На Hetzner из /home/mi/Bot_TG/mp-system:

.venv/bin/python scripts/report_cli.py plan-fact --source-label production

Команда читает production-БД через read-only соединение в одной транзакции. API, запись данных, смена цен/кампаний и отправка сообщений в неё не входят. Локальный mp.db, старые JSON и разовый скрипт от 12.09 не использовать как fallback. Штатному запросу не нужны SSH-SQL, исследование кода, соседний репозиторий и полный финансовый/рекламный анализ. После получения JSON использовать его; дополнительные плейбуки открывать только для недостающей диагностики.

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

Что Источник
План config/plan.yaml: need_per_day, ads, buyout_planned, match
Все заказы и корзины ozon_sku_traffic_daily.ordered_units, total_hits_to_cart
Полнота трафика ozon_sku_traffic_runs: success, rows_count; реальные строки и корзины
Весь расход ozon_ad_metrics_daily.money, включая впоследствии отключённые РК
Выкуп для норматива ozon_postings + ozon_posting_items.quantity
Коэффициенты существующий app/web/plan_fact.py::compute
Покрытие и JSON app/reports/plan_fact.py, вход scripts/report_cli.py

Заказы — заказанные единицы с отменами, не количество ID и не выкупы. Факт всегда из аналитики карточек, в том числе при остановке постингов. Не менять источник между периодами. Страница /plan-fact использует постинги и включает WB: её общий итог может отличаться; формулы коэффициентов остаются общими.

Артикулы объединять по явному match и существующему удалению размерного суффикса. Не использовать sku_alias: skirt-atlas-max-2 не равен skirt-milk. Двойной match вызывает ошибку. Несопоставленные offer_id — в unmatched_offers; для контроля всего магазина — total.shop_orders, total.shop_ad_spend.

Позиции без привязки к магазину — excluded, не нулевые продажи. on_shelf — запись плана, не проверка сегодняшних остатков. Норматив задан на общий пул, включая будущие партии, и не распределён между магазинами. Итог называть «выполнение общего норматива выбранных товаров силами Siluetta Ozon». После deadline сравнение справочное. При обсуждении цен читать ../Strategy-Operations/docs/Цены — плановые по артикулам.md: решения владельца сильнее арифметического пола. Для обычного получения чисел это не нужно.

Формулы

A = ads из плана, рубли на выкуп (CPS), не дневной бюджет и не ставка РК. b = выкуп; q = корзина→заказ. Коэффициенты — доли, не проценты.

Заказы план/день = need_per_day / b
Заказы план за период = план/день × все календарные дни запрошенного периода
CPO план = A × b
CPL план = A × b × q
CPL факт = весь расход / все корзины
CPO через корзины (оценка) = CPL факт / q
CPS через корзины (оценка) = CPL факт / (q × b)
CPO факт = весь расход / все заказанные единицы

Рекламные atbs и атрибутированные orders + orders_model здесь не используются. Не заменять этот отчёт report.cmd ads: у него другой знаменатель.

q — отношение сумм заказов и корзин скользящего окна buyout_window_days (сейчас 60) до вчера, только по полным дням с ненулевыми общими корзинами магазина. Своя конверсия: ≥50 корзин и 0 < q <= 1; иначе медиана по правилу существующего план-факта, с пометкой. Если нет медианы — неизвестно. Не закреплять HZ020 на медиане навсегда: проверять отношение каждый раз. Число пригодных дней — methodology.conversion_covered_days. Это отношение потоков периода, не отслеживание конкретной корзины.

b = delivered / (delivered + cancelled/canceled) за скользящее окно. При <20 разрешившихся единицах используется buyout_planned, buyout_source=plan. Считать SUM(quantity), не COUNT строк. Для b сохраняется календарная конвенция постингов действующего план-факта; московские даты спроса берутся из трафика карточек. Это не зрелая когорта с отступом 15 дней; возвраты после вручения не вычтены.

Коэффициенты общие на дату расчёта для всех периодов. Исторический запрос — ретроспектива с нынешним планом/коэффициентами, не реконструкция старого плана. Корзинный прогноз не доказывает финансовую прибыль и не заменяет финансовый CPS. ads=0 — действительный нулевой бюджет. Расход при нём обязательно показать. Нулевой расход при корзинах даёт CPL/CPO=0: это отсутствие расходов за окно. При нуле заказов фактический CPO неизвестен. <30 корзин — малая выборка; день не тренд.

Полнота: сначала флаги

JSON: source, shop, periods, freshness, methodology, warnings, excluded, elapsed_ms. В периодах: абсолютные даты, rows, total, coverage, unmatched_offers.

  • fact_complete: есть дневной трафик для заказов и строки каждой позиции;
  • funnel_complete: дополнительно корзины и реклама на всех тех же днях;
  • complete: всё выше, свежие постинги, определённая конверсия, действующий срок плана и отсутствие несопоставленных позиций.

complete=false при старом выкупе не отменяет свежие заказы: назвать конкретную причину. Постинги свежие, если fetched_at не раньше начала сегодняшнего дня МСК. Это проверка возраста, не независимый аудит полноты API. Реклама требует строк на каждый день и fetched_at после окончания этого дня МСК; отсутствие строк магазина не доказывает ноль. Контроль не заменяет сверку Performance с finance при полном рекламном разборе.

Если день пропущен/неполон, соответствующие полные периодные метрики = null; план сохраняет исходную длительность. observed_aligned — отдельный частичный срез: расход и корзины строго на coverage.aligned_dates. Не выдавать его за полную неделю и не уменьшать план до числа доступных дней. Нет строки артикула — неизвестно, не ноль.

Добор при необходимости

  1. Назвать недостающие даты/источники из JSON. Проверить, что сборщик того же источника не работает. Аналитический кэш обновлять штатно от mi, из корня production: WAL от root ломает последующие сборы.
  2. Корзины добирать только за отсутствующие даты, последовательно:
cd /home/mi/Bot_TG/mp-system
sudo -u mi .venv/bin/python scripts/backfill_ozon_sku_traffic.py YYYY-MM-DD YYYY-MM-DD 4

collect_ozon_sku_traffic.py собирает только вчера. Success с нулевыми корзинами при живых заказах может означать отсутствие платной аналитики. Не повторять backfill бесконечно; назвать проблему подписки/сессии. Покупка подписки требует команды владельца.

  1. Для старого выкупа при необходимости один штатный прогон:
sudo -u mi .venv/bin/python scripts/ingest_ozon.py --shop-id 4 postings --days 60

После встроенных повторов и OzonRateLimitError/429 остановиться, не запускать снова и не менять лимиты. Дать свежие заказы из трафика с оговоркой о старом/плановом выкупе. Добор рекламы — по data/notes/ozon/ads-playbook.md, без параллельных Performance-выгрузок. 4. Повторить report.cmd plan-fact. Он читает production напрямую: не ждать и не форсировать синхронизацию часового snapshot ради этого отчёта.

Ответ и чек-лист

Начать с состояния и покрытия. За неделю и вчера — отдельные таблицы:

Артикул | Заказы факт / план | Выполнение % | Все корзины | Весь расход
        | CPL факт / план | CPO через корзины / план

Итог обязателен. Общий CPL/CPO факт — отношение сумм; проценты/стоимости строк не усреднять. Общий плановый CPO и смесь прогнозов по разным коэффициентам не изобретать. Выделить отставание по темпу, превышение CPO, расход при нулевом бюджете и выполнение обоих условий. Предыдущую неделю сравнивать только при полном покрытии. Дешёвая корзина не доказывает качество трафика, причинный эффект рекламы и прибыль.

При необходимости сохранять срез с временем и JSON в data/ads/YYYY-MM-DD/, не перезаписывая предыдущий. Архивы не входят в синхронизацию плейбуков. Настройки кабинета по итогам анализа автоматически не менять.

  1. Магазин 4, закрытые московские дни, один источник заказов?
  2. Названы пропуски, возраст постингов и fallback коэффициентов?
  3. CPO заказа и CPS выкупа различены; прогноз подписан?
  4. Все корзины и весь расход на одинаковых датах?
  5. План на исходное число дней, будущие партии и общий товарный пул названы?
  6. Нулевой бюджет, unmatched/excluded и малые выборки показаны?
  7. Нет выводов о прибыли/причинности только из корзинной оценки?

Контроль 12.09 после добора: 05–11.09 — 348 единиц, 1266 корзин, 40 717,33 ₽; 11.09 — 52, 165, 5924 ₽. Источники корректируются задним числом: это контрольный срез, не числа для подстановки. Проверки: .venv\Scripts\python.exe -m unittest tests.test_plan_fact_report tests.test_report_cli.

Фильтры