AI Asist
v2
Д
Д

Как считается прибыль и аналитика на WB — методичка

← Заметки  ·  Раздел: Wildberries  ·  Файл: data/notes/wb/profit-methodology.md

Как считается прибыль и аналитика на WB — методичка

Единый подход к расчёту фин-отчёта и аналитики WB в Big-System-MP. Применимо к любому магазину (Мой WB, Siluetta WB, Tote WB и т.д.).

TL;DR — формула прибыли

прибыль = K_выплате − cost × net_qty − Логистика − Хранение − Реклама − Прочее

где (всё из fin_report_detail за окно):
  K_выплате       = SUM(for_pay)   WHERE seller_oper_name='Продажа'
                  − SUM(for_pay)   WHERE seller_oper_name='Возврат'
  net_qty         = SUM(quantity)  WHERE seller_oper_name='Продажа'
                  − SUM(quantity)  WHERE seller_oper_name='Возврат'
  Логистика       = SUM(delivery_rub)
  Хранение        = SUM(paid_storage)
  Прочее          = SUM(paid_acceptance + deduction + penalty + acquiring_fee − additional_payment)
                    − SUM(deduction WHERE bonus_type_name LIKE '%Продвижение%')   ← см. ⚠ ниже
  Реклама         = SUM(sum_cost) FROM ad_metrics_daily WHERE nm_id IN (sku.nm_ids)
  cost            = costs.cost WHERE sku = '<sku>' AND marketplace='wb'

⚠ Реклама не должна задваиваться (deduction × ad_metrics_daily)

Реклама WB приходит в данные дважды: 1. ad_metrics_daily.sum_cost — дневной расход кампаний из отчёта по рекламе (/adv/v2/fullstats). 2. fin_report_detail.deduction со строкой bonus_type_name = «Оказание услуг "WB Продвижение", документ №…» — акт списания за рекламу в отчёте реализации.

Это одна и та же реклама, просто WB показывает её и в рекламном отчёте, и удержанием в финрепе (суммы и периоды не совпадают 1:1 — акт выставляется своими датами/документами).

Правило: берём рекламу из ad_metrics_daily (точнее, по дням и nm_id), а из «Прочего» финрепа исключаем deduction где bonus_type_name LIKE '%Продвижение%'. Иначе реклама вычитается дважды и прибыль занижается. (Реальный кейс Tote WB, 01–15.06.2026: с задвоением −6 633 ₽, без — +13 367 ₽.)

⚠ В deduction бывают и другие крупные строки не относящиеся к операционной прибыли — например «Перевод на баланс заёмщика … по кредиту …» (погашение кредита WB). Это финансовая операция, а не расход бизнеса; в опер. прибыль её включать тоже не нужно (фильтровать отдельно при появлении).

⚠ Источники WB — два разных API, разные цифры

Это самый частый источник путаницы. WB отдаёт разные числа через разные эндпоинты — это не баг ingest, это дизайн WB.

Источник Endpoint Что значит Когда использовать
Statistics API /api/v1/supplier/orders заказы которые ушли в логистику (после фильтрации фрода/быстрых отмен) финансы, склады, прибыль
Statistics API /api/v1/supplier/sales реально выкупленные товары (доставлены) финансы, выкуп %
Seller-Analytics /content/api/v2/analytics (Воронка) все нажатия «Заказать» в кабинете (включая мгновенные отмены) маркетинг, конверсия, реальный спрос
Statistics API /api/v5/supplier/reportDetailByPeriod отчёт о реализации — выплаты от WB точный фин-расчёт
Ad API /adv/v2/fullstats реклама per advert/nm атрибуция рекламы
Costs таблица costs себестоимость вычитание из payout

Ключевой кейс: 142 vs 166 (Tote WB, skirt-atlas-max-2)

Реальный пример из 06.06.2026, период 27.04-31.05:

Метрика Воронка /supplier/orders Δ
Заказов 166 142 −24
Отменено 112 87 −25
Не отменено 54 55
Выкуплено 44 43

Корень: /supplier/orders исключает быстро-отменённые заказы (отмена клиентом в первые минуты до отгрузки). Воронка их включает.

На прибыль это не влияет (быстрые отмены не несут логистических расходов). Влияет на: - Метрику «отказы %» - Метрику «конверсия из корзины в заказ» - Стоимость рекламы за заказ

Какие цифры брать для какой цели

Цель Источник Какое поле
Финансы / ROI / прибыль fin_report_detail for_pay, delivery_rub, paid_storage, etc.
Кол-во продаж (=выкупов) sales или fin_report.Продажа sale_id LIKE 'S%' / qty
Кол-во возвратов sales или fin_report.Возврат sale_id LIKE 'R%' / qty
Реальный спрос / конверсия funnel_period или funnel_daily orders_count, cancel_count
Эффективность рекламы funnel + ad_metrics_daily order_count vs sum_cost
Сток-планирование stocks_snapshot quantity по складам

Структура fin_report_detail (главная таблица WB)

Один день / SKU / WB-операция = одна строка.

Ключевые поля: | Поле | Что | |---|---| | rrd_id | PK — ID отчётной строки WB (уникален) | | period_start / period_end | границы отчёта (обычно неделя) | | rr_date | дата выплатной транзакции | | sale_dt / order_dt | даты заказа/продажи | | sku | артикул продавца | | tech_size | размер | | seller_oper_name | тип операции: «Продажа», «Возврат», «Логистика», «Возмещение издержек по перевозке», «Хранение», «Штраф» и т.д. | | quantity | штук | | retail_amount | retail цена (то что заплатил покупатель) | | for_pay | к выплате от WB (retail − комиссия − СПП-удержания) | | commission_percent | комиссия WB в % | | delivery_rub | логистика за эту строку | | paid_storage | хранение | | paid_acceptance | платная приёмка | | deduction / penalty | удержания / штрафы | | additional_payment | дополнительные выплаты (бонусы) | | acquiring_fee | эквайринг |

Структура orders + sales

Schema: - orders.srid — PK, sales reference ID (один заказ = один srid) - orders.is_cancel — отменён ли (boolean) - orders.date — когда создан - sales.sale_id — PK, начинается с: - S* — выкуп (продажа покупателю) - R* — возврат - D* — коррекция (доплата/возврат денег)

ИНГЕСТ: подводные камни

/supplier/orders — фильтр по lastChangeDate, не по date

Endpoint принимает dateFrom и возвращает заказы у которых lastChangeDate >= dateFrom. То есть: - Заказ создан 20.04, выкуплен 05.05 → lastChangeDate=05.05 → попадёт если dateFrom ≤ 05.05 - Заказ создан 30.05, никогда не менялся → lastChangeDate=30.05 → попадёт если dateFrom ≤ 30.05

Рекомендация: при инкрементальном пуле всегда брать dateFrom = now − 60 дней чтобы не упустить заказы которые менялись поздно.

Финреп — квота 2 запроса в 24 часа

/api/v5/supplier/reportDetailByPeriod жёстко лимитирован WB на Базовом тарифе. Если перетянуть — ждать сутки. Безопасное окно: 7 дней раз в неделю (как WB сам присылает отчёты).

Воронка — квота ~2 запроса в час

/content/api/v2/analytics — Seller-Analytics. Чанкуем по 20 nm_ids × 7 дней между паузами 30 мин.

Cost-ключ

В отличие от Ozon, у WB обычно ключ costs.sku совпадает 1:1 с sku из orders/sales/fin_report — потому что у нас один артикул на одной площадке. Размер cost не различает (одна модель = одна цена).

Исключение: если cost зависит от размера — придётся ввести tech_size в costs.

Полный SQL расчёта прибыли по одному SKU

WITH fin AS (
  SELECT
    tech_size,
    SUM(CASE WHEN seller_oper_name='Продажа' THEN quantity ELSE 0 END) AS sold,
    SUM(CASE WHEN seller_oper_name='Возврат' THEN quantity ELSE 0 END) AS ret,
    SUM(CASE WHEN seller_oper_name='Продажа' THEN retail_amount ELSE 0 END)
    − SUM(CASE WHEN seller_oper_name='Возврат' THEN retail_amount ELSE 0 END) AS gross,
    SUM(CASE WHEN seller_oper_name='Продажа' THEN for_pay ELSE 0 END)
    − SUM(CASE WHEN seller_oper_name='Возврат' THEN for_pay ELSE 0 END) AS for_pay_net,
    SUM(delivery_rub) AS lib,
    SUM(paid_storage) AS stor,
    SUM(paid_acceptance + deduction + penalty + acquiring_fee − additional_payment) AS other
  FROM fin_report_detail
  WHERE shop_id = :shop AND sku = :sku AND rr_date BETWEEN :since AND :to
  GROUP BY tech_size
),
ad AS (
  SELECT SUM(sum_cost) AS spend FROM ad_metrics_daily
  WHERE shop_id = :shop AND date BETWEEN :since AND :to
    AND nm_id IN (SELECT DISTINCT nm_id FROM fin_report_detail
                  WHERE shop_id = :shop AND sku = :sku)
),
c AS (SELECT cost FROM costs WHERE sku = :sku AND marketplace='wb' ORDER BY valid_from DESC LIMIT 1)
SELECT
  fin.tech_size,
  fin.sold,
  fin.ret,
  fin.sold − fin.ret AS net,
  fin.gross,
  fin.for_pay_net,
  fin.lib,
  fin.stor,
  fin.other,
  fin.for_pay_net − fin.lib − fin.stor − fin.other
    − (fin.sold − fin.ret) * c.cost
    − ad.spend * fin.sold / (SELECT SUM(sold) FROM fin)  -- реклама пропорц.
  AS profit
FROM fin, ad, c;

Привязка периода — где можно ошибиться

fin_report_detail отдаёт данные по неделям, которые WB закрыл (отправил отчёт). На момент написания (06.06): - Последний закрытый отчёт: неделя 26.05-01.06 - Текущая неделя 02.06-08.06 — ещё «в работе», цифр в финрепе НЕТ

Если просим финансы за период 27.04 → 31.05 — окно «закрыто». WB уже всё прислал. Если просим за 27.04 → 06.06 — последние 5 дней в финрепе пустые. Орders/Sales есть, fin_report — нет.

Рекомендация для отчётов: брать окно до конца последнего понедельника прошлой недели — тогда фин данные точно полные.

WB vs Ozon — ключевые отличия в расчёте

Аспект WB Ozon
Комиссия 15-25%, зашита в for_pay 30-46%, зашита в sale_commission
Логистика Отдельной строкой в fin_report Отдельные операции в transaction_list
СПП Часть for_pay (WB сам считает) «Программы партнёров» + «Баллы за скидки»
Реклама ad_metrics_daily.sum_cost per nm_id ozon_ad_metrics_daily.money per offer_id
Хранение paid_storage в каждой строке fin_report Отдельные операции в transaction_list
Источник истины fin_report_detail (выплаты по неделям) transaction_list (или Excel «Отчёт по начислениям»)
Расхождение API vs кабинет 142 vs 166 (Statistics vs Воронка) ~0.1% (округление в копейках)

История находок

Дата Что найдено
?? — Siluetta WB API /supplier/orders возвращает 1090, БД 794 — разница 296. Корень: ingest_orders с малым dateFrom не захватил все lastChangeDate. Fix: тянуть с dateFrom = now − 60 дней.
2026-06-06 — Tote WB Кабинет (Воронка) 166, БД (Statistics) 142. Разница 24 = быстро-отменённые заказы. Это разница методологий API, не баг ingest. На прибыль не влияет.

Воспроизведение

# 1. Освежить данные на проде (квоты учитывать)
ssh wbbot 'cd /root/mp-system && .venv/bin/python -c "
from datetime import date, datetime
from app.wb.ingest import ingest_orders, ingest_sales, ingest_stocks, ingest_ad_metrics_history, ingest_funnel
ingest_orders(\"2026-02-01T00:00:00\", shop_id=5)
ingest_sales(\"2026-02-01T00:00:00\", shop_id=5)
ingest_stocks(shop_id=5)
ingest_ad_metrics_history(date(2026,4,7), date(2026,6,6), shop_id=5)
ingest_funnel(datetime(2026,4,27), datetime(2026,5,31), shop_id=5)
"'

# 2. Запустить расчёт прибыли по SKU
python scripts/wb_profit_sku.py  # параметры внутри

Скрипты в репозитории: - scripts/wb_profit_sku.py — фин-отчёт по одному SKU с разбивкой по размерам - scripts/wb_orders_diagnose.py — диагностика расхождений orders / sales / fin_report

Фильтры