Плейбук: прогноз заказы-продажи, статика WB
Срабатывает, когда пользователь просит:
- «прогноз заказов и продаж»;
- «статичный прогноз на месяц»;
- «прогноз по артикулам с учетом размеров»;
- «возьми последнюю неделю как эталон»;
- «прогноз заказы-продажи статика».
Статика означает: не экстраполируем темп роста и не строим тренд. Берём последнюю
закрытую неделю как текущий эталон недельного спроса по артикулу и умножаем на число недель.
Когда использовать
Использовать для короткого операционного прогноза поставки/закупки, когда:
- Нужен прогноз на 4 недели / месяц.
- По артикулу уже есть последняя закрытая неделя заказов.
- По размерам недельных данных мало, поэтому размерную сетку надо брать по более длинному окну.
Не использовать как динамический forecast. Если пользователь просит «с учётом темпа роста», нужно делать отдельный сценарий с WoW/трендом.
Магазины
| Магазин | shop_id |
|---|---|
| Siluetta WB | 3 |
| Tote WB | 5 |
Если магазин не указан, брать из контекста. Если контекста нет — уточнить.
Источники
| Метрика | Источник |
|---|---|
| Эталон заказов по артикулу | funnel_period.orders_count |
| Размерная доля | orders.tech_size, orders.srid |
| Выкуп по размеру | orders + sales по srid |
| Продажа/выкуп | sales.srid, sale_id NOT LIKE 'R%' |
| Возврат | sales.srid, sale_id LIKE 'R%' |
| Отмена | orders.is_cancel |
Главное правило: недельную базу заказов по артикулу брать из funnel_period, а размерную
структуру и выкуп по размеру считать по ID через orders/sales.
Периоды
Эталон недели
Последняя закрытая календарная неделя Пн-Вс.
Пример для текущей даты 2026-07-08:
2026-06-29 … 2026-07-05
Брать exact-window из funnel_period:
WHERE period_start = :week_since
AND period_end = :week_to
Размерная доля
Брать весь доступный период по SKU или согласованное длинное окно, например с мая.
orders.date >= :size_since
Причина: по размерам одной недели мало, сетка может шуметь.
Выкуп по размеру
Брать тот же длинный период, что и размерную долю. Считать по закрытым ID:
Выкуп по размеру = buyout_net / (buyout_net + returns + cancels)
unresolved / в пути в знаменатель не включать.
Формулы
weekly_orders_sku = funnel_period.orders_count за последнюю закрытую неделю
forecast_orders_total = weekly_orders_sku × weeks
size_share = orders_size / orders_all
forecast_orders_size = forecast_orders_total × size_share
buyout_rate_size = buyout_net_size / (buyout_net_size + returned_size + cancelled_size)
forecast_sales_size = forecast_orders_size × buyout_rate_size
Округление:
forecast_orders_sizeокруглять до целых штук.forecast_sales_sizeокруглять до целых штук.- После округления проверить, что сумма размеров близка к итогу; если расходится на 1-2 шт, поправить самый крупный размер.
SQL: недельный эталон по артикулу
SELECT
sku,
nm_id,
orders_count,
orders_sum_rub,
cancel_count,
buyouts_count,
buyout_percent
FROM funnel_period
WHERE shop_id = :shop_id
AND sku = :sku
AND period_start = :week_since
AND period_end = :week_to;
Если exact-window нет, не складывать дневные orders как замену без пометки. Сказать, что
кабинетная воронка за закрытую неделю не загружена, и при необходимости запустить ingest:
.venv/bin/python scripts/ingest_wb.py --shop-id :shop_id funnel --from YYYY-MM-DD --to YYYY-MM-DD
SQL: размерная доля и выкуп по размеру
WITH sales_by AS (
SELECT
shop_id,
srid,
MAX(CASE WHEN lower(coalesce(sale_id, '')) NOT LIKE 'r%' THEN 1 ELSE 0 END) AS has_sale,
MAX(CASE WHEN lower(coalesce(sale_id, '')) LIKE 'r%' THEN 1 ELSE 0 END) AS has_return
FROM sales
WHERE shop_id = :shop_id
AND sku = :sku
GROUP BY shop_id, srid
),
ids AS (
SELECT
o.srid,
o.tech_size,
o.is_cancel,
COALESCE(s.has_sale, 0) AS has_sale,
COALESCE(s.has_return, 0) AS has_return,
CASE
WHEN COALESCE(s.has_sale, 0) = 1
AND COALESCE(s.has_return, 0) = 0
THEN 1 ELSE 0
END AS buyout_net,
CASE
WHEN o.is_cancel = 0
AND COALESCE(s.has_sale, 0) = 0
AND COALESCE(s.has_return, 0) = 0
THEN 1 ELSE 0
END AS unresolved
FROM orders o
LEFT JOIN sales_by s
ON s.shop_id = o.shop_id
AND s.srid = o.srid
WHERE o.shop_id = :shop_id
AND o.sku = :sku
AND o.date >= :size_since
AND o.date < :size_to
)
SELECT
tech_size,
COUNT(*) AS orders_ids,
SUM(is_cancel) AS cancels,
SUM(has_sale) AS sold_ids,
SUM(has_return) AS returned_ids,
SUM(buyout_net) AS buyout_net_ids,
SUM(unresolved) AS unresolved_ids,
ROUND(100.0 * COUNT(*) / SUM(COUNT(*)) OVER (), 1) AS size_share_pct,
ROUND(
100.0 * SUM(buyout_net)
/ NULLIF(SUM(buyout_net) + SUM(has_return) + SUM(is_cancel), 0),
1
) AS buyout_closed_pct
FROM ids
GROUP BY tech_size
ORDER BY tech_size;
Пример расчёта
Вход:
weekly_orders_sku = 96
weeks = 4
forecast_orders_total = 384
Размерная сетка и выкуп:
L: доля 26.7%, выкуп 33.3%
M: доля 43.6%, выкуп 29.1%
S: доля 29.7%, выкуп 35.1%
Результат:
Размер | Доля | Ср. заказов/нед | Прогноз заказов 4 нед | Выкуп | Прогноз продаж
L | 26.7% | 25.6 | 102 | 33.3% | 34
M | 43.6% | 41.8 | 167 | 29.1% | 49
S | 29.7% | 28.6 | 114 | 35.1% | 40
ИТОГО | 100% | 96.0 | 384 | 32.0% | 123
Формат ответа
База:
- эталон недели: 2026-06-29 … 2026-07-05
- заказы по артикулу: 96/нед
- прогноз: 4 недели
- размерная доля: по всему доступному периоду с 2026-05-01
- выкуп: по закрытым ID через srid
Размер | Доля размера | Средн. заказов/нед | Прогноз заказов 4 нед | Выкуп по размеру | Прогноз продаж
...
После таблицы дать короткий вывод:
- общий прогноз заказов;
- общий прогноз продаж/выкупов;
- какой размер основной по заказам;
- какой размер лучше по выкупу.
Чек-лист
- Эталон недельных заказов взят из
funnel_period.orders_count, не изorders. - Неделя закрытая Пн-Вс.
- Размерная доля не взята из одной недели, если данных мало.
- Выкуп по размеру посчитан через
srid, возвраты привязаны к тому же ID. - В пути/unresolved не включены в знаменатель выкупа по закрытым.
- В ответе явно сказано, что прогноз статичный: рост WoW не применяется.