Яндекс Маркет: как строится таблица заказов
Проверено 16 июля 2026 года по текущим локальным рабочим деревьям
wolle-economy-viewer,ya-reports,ya-updaterиya-order-notify, а также read-only данным PostgreSQL. Пример во всех разделах — заказ55830644865.
Назначение и границы
Гайд описывает вкладку «Яндекс Маркет» страницы «Заказы» проекта wolle-economy-viewer при включённом переключателе «Показать все колонки». Авторитетный список — DISPLAY_COLUMNS в src/wolle_economy/ui/columns.py; на дату проверки в нём 43 колонки.
Здесь описано текущее фактическое поведение. Даже если название колонки допускает другое толкование, формула приводится именно такая, какую выполняет код.
Не входят в область гайда: фильтры и верхние KPI страницы, вкладки других маркетплейсов, конкурентное ценообразование после создания заказа и бухгалтерское признание доходов.
Короткая модель
Одна строка на экране — одна позиция ya_order_items, а не один заказ. Основная финансовая модель такова:
Цена продажи sell_price
− комиссии ЯМ market_services
= ожидаемая выплата expected_payout
+ наши промо-списания promo_discounts (они отрицательные)
− наши затраты our_costs
= прибыль с учётом промо profit
Где:
our_costs = фактическая закупка доставленных единиц
+ упаковка доставленных единиц
+ переходник доставленных единиц
+ доставка из Китая доставленных единиц (только seller.location = 'CN')
Субсидия Яндекс Маркета (bonus_points) уже входит в sell_price. Повторно прибавлять её к прибыли нельзя.
Поток данных
Yandex Partner API /v1/businesses/{businessId}/orders
└─ ya-order-notify → ya_orders + ya_order_items
├─ снимок закупочной цены, минимальной цены и плановых комиссий
└─ ссылки на продавца, фид и товар
Отчёт «Заказы» (united-orders)
├─ лист «Услуги и маржа по заказам» → ya_order_margin_report
└─ лист «Транзакции по заказам и товарам» → ya_order_transactions_report
Отчёт «Платежи» (united-netting)
└─ лист «Отчёт о платежах» → ya_payments_reports
Складская цепочка
└─ all_split_orders + stock_movement_transactions + order_to_supplier
→ фактическая закупочная цена
wolle-economy-viewer
├─ queries.py: три SQL-запроса
├─ loader.py: merge по item_id и ya_order_id
├─ economics.py: производные показатели
└─ columns.py + table.py: порядок, подписи и форматирование
ya-updater рассчитывает минимальную цену предложения по очень близкой формуле и ведёт историю цен. Однако значения конкретного заказа фиксируются при создании заказа проектом ya-order-notify; именно его снимок находится в ya_order_items и читается таблицей.
Гранулярность и опасность двойного счёта
Базовая строка — e_com.ya_order_items.id. К ней присоединяются поля разных уровней:
| Уровень | Примеры | Что происходит при заказе с несколькими позициями |
|---|---|---|
| Заказ | sell_price, market_services, payment_status |
Одна и та же сумма повторяется в каждой позиции после JOIN. Суммировать строки нельзя. |
| Позиция | offer_id, quantity, base_price, расчётные комиссии |
Свои значения для каждой позиции. |
| Единица товара | base_price, ff_fee, socket_adapter_fee |
Python умножает на количество. |
| Платёжная транзакция | строки ya_payments_reports |
SQL агрегирует все строки заказа. |
Для multi-item заказа текущий код не распределяет sell_price и market_services между позициями. Следовательно, expected_payout, profit и часть производных также повторяют заказные суммы в каждой позиции, но вычитают затраты конкретной позиции. Это ограничение нужно учитывать при экспорте и агрегации.
Какие отчёты и столбцы используются
Отчёт «Заказы» (united-orders)
Файл скачивает ya-reports через POST /reports/united-orders/generate.
Лист «Услуги и маржа по заказам» парсится позиционно:
| Excel | iloc |
Назначение в ya-reports |
Таблица БД |
|---|---|---|---|
| H | 7 | номер заказа → order_id для связи |
ya_orders.order_id |
| J | 9 | статус → status |
ya_order_margin_report.status |
| M | 12 | услуги Маркета → market_services |
ya_order_margin_report.market_services |
| N | 13 | цена продажи → sell_price |
ya_order_margin_report.sell_price |
| Q | 16 | статус платежа → payment_status |
ya_order_margin_report.payment_status |
Строки читаются с динамически найденной строкой заголовка: код ищет маркер «Номер заказа» в Excel-строке 4 или 7.
Лист «Транзакции по заказам и товарам» читается с header=9 (Excel-строка 10). Для этой таблицы важны:
| Excel | iloc |
Поле БД | Использование |
|---|---|---|---|
| H | 7 | связь через номер заказа | поиск ya_orders |
| L | 11 | связь через Offer ID | поиск ya_order_items |
| O | 14 | sell_price |
цена возвращённой транзакции |
| S | 18 | other_market_discounts |
диагностика скидок |
| Y | 24 | status |
статус позиции и распознавание возврата |
| AG | 32 | customer_payment_date |
приоритетная дата выплаты |
| AJ | 35 | bonuses |
справочная субсидия ЯМ |
| BA | 52 | customer_refund_amount |
диагностика возврата |
| BC | 54 | refund_payment_date |
дата возвратного платежа |
Отчёт «Платежи» (united-netting)
Файл скачивается через POST /reports/united-netting/generate. Лист «Отчёт о платежах» читается с header=1 (Excel-строка 2):
| Excel | iloc |
Поле БД | Использование |
|---|---|---|---|
| J | 9 | order_id |
связь с заказом |
| P | 15 | offer_id |
необязательная связь с позицией |
| S | 18 | item_name_or_service_name |
распознавание штрафов |
| T | 19 | quantity |
сохраняется для диагностики |
| U | 20 | transaction_amount |
комиссии, промо, штрафы |
| V | 21 | transaction_type |
сохраняется как источник отчёта |
| W | 22 | transaction_source |
различение нового формата отчёта |
| X | 23 | payment_status |
различение старого формата и выплат |
| AA | 26 | payment_date |
fallback даты выплаты |
| AB | 27 | payment_id |
идентификатор платежа |
| AC | 28 | payment_or_withheld_amount |
сохраняется, но таблица заказов не использует |
Парсер ориентируется на позиции столбцов, а не на текст заголовка. При изменении формата Excel индексы требуется сверить в ya-reports/backend/src/services/orders_to_db.py и payments_to_db.py.
Сквозной пример YM-55830644865
Заказ содержит одну позицию (ya_order_items.id = 27032, quantity = 1), поэтому заказные суммы не дублируются между товарами.
| Параметр | Значение |
|---|---|
| Магазин | ТехноПравда ЯНД |
| Offer ID | BRITVS100 |
| Плановая закупка | 900,00 ₽ |
| Фактическая закупка | 820,00 ₽ |
| Упаковка | 50,00 ₽ |
| Наши учитываемые затраты | 870,00 ₽ |
| Минимальная цена / цена продажи | 2 340,00 ₽ |
| Субсидия ЯМ, уже в цене продажи | 739,00 ₽ |
| Комиссии из margin report | 382,01 ₽ |
| Ожидаемая выплата | 1 957,99 ₽ |
| Наши промо-списания | −798,06 ₽ |
| Прибыль с промо | 289,93 ₽ |
| Прибыль без промо | 1 087,99 ₽ |
Проверка основной формулы:
expected_payout = 2 340,00 − 382,01 = 1 957,99 ₽
our_costs = 820,00 + 50,00 + 0,00 + 0,00 = 870,00 ₽
profit = 1 957,99 − 798,06 − 870,00 = 289,93 ₽
profit_no_promo = 1 957,99 − 870,00 = 1 087,99 ₽
Сводный список колонок (UI-43)
| № | Заголовок в UI | Техническое имя | Тип источника |
|---|---|---|---|
| 1 | Заказ создан | created_at |
БД/API |
| 2 | Дата отгрузки | shipment_date |
БД/API |
| 3 | Магазин | seller_name |
БД |
| 4 | Номер заказа | order_id_str |
Вычисление |
| 5 | Offer ID | offer_id |
БД/API |
| 6 | Наименование | product_name |
БД |
| 7 | Статус заказа | order_status |
Отчёт + fallback БД |
| 8 | Статус заказа 2 | fulfillment_status |
Отчёт + fallback БД |
| 9 | Статус платежа | payment_status |
Отчёт «Заказы» |
| 10 | Количество | quantity |
БД/API |
| 11 | Отправлено от/из | supplier_name |
Снимок расчёта заказа |
| 12 | Цена закупки | base_price_total |
Вычисление |
| 13 | Факт. закупка | effective_purchase_total |
Складская цепочка + вычисление |
| 14 | Цена упаковки | ff_fee_total |
БД + вычисление |
| 15 | Цена переходника | socket_adapter_total |
БД + вычисление |
| 16 | Доставка из Китая | custom_delivery_fee_total |
Снимок заказа + вычисление |
| 17 | Цена + маржа | price_with_margin |
Вычисление |
| 18 | Маржа план ₽ | our_margin |
Вычисление |
| 19 | Маржа факт ₽ | margin_fact_rub |
Вычисление |
| 20 | Маржа план % (от цены продажи) | margin_plan_pct |
Вычисление |
| 21 | Маржа факт % (от цены продажи) | margin_fact_pct |
Вычисление |
| 22 | Маржа план % (от закупки) | margin_plan_on_cost_pct |
Вычисление |
| 23 | Маржа факт % (от закупки) | margin_fact_on_cost_pct |
Вычисление |
| 24 | Минимальная цена | min_sell_price_total |
Снимок расчёта + вычисление |
| 25 | Ожидаемая прибыль | expected_profit |
Вычисление |
| 26 | Цена продажи | sell_price |
Отчёт «Заказы» + fallback |
| 27 | Субсидия ЯМ (справочно, в sell_price) | bonus_points |
Отчёт «Заказы», транзакции |
| 28 | Промо-расходы (наши баллы) | promo_discounts |
Отчёт «Платежи» |
| 29 | Разница от мин. цены | diff_from_min_price |
Вычисление |
| 30 | Расчётные комиссии | calc_commissions |
Снимок расчёта + вычисление |
| 31 | Комиссии (margin_report) | market_services |
Отчёт «Заказы» |
| 32 | Факт. комиссии (ЛК) | fact_commissions |
Отчёт «Платежи» |
| 33 | Доход за вычетом комиссий | income_after_fees |
Вычисление |
| 34 | Прибыль | profit |
Вычисление |
| 35 | Разница Прибыли Факт/Ожид | profit_vs_expected |
Вычисление |
| 36 | Доход за вычетом комиссий и промо | income_after_fees_promo |
Вычисление |
| 37 | Прибыль без учёта промо-расходов | profit_no_promo |
Вычисление |
| 38 | Штраф за отмену заказа | seller_cancel_penalty |
Отчёт «Платежи» |
| 39 | Штраф за позднюю отгрузку | late_ship_penalty |
Отчёт «Платежи» |
| 40 | Нам перевели за заказ | payout_if_paid |
Статус отчёта + вычисление |
| 41 | Сумма выплаты (если нет даты, то ожидаемая) | expected_payout |
Вычисление |
| 42 | Дата выплаты | last_payment_date |
Два отчёта |
| 43 | Фактическая прибыль | actual_profit |
Вычисление |
| Авторитетный программный список содержит 43 уникальные позиции. Далее описаны все 43 в том же порядке. |
Подробное описание колонок
YM-01. Заказ создан — created_at
Источник. e_com.ya_orders.created_at. Поле заполняет ya-order-notify из creationDate ответа Yandex Partner API POST /v1/businesses/{businessId}/orders. Схема Order приводит время к UTC и убирает timezone перед записью; PostgreSQL в текущей базе возвращает timezone-aware значение.
Преобразование. SQL передаёт значение без вычислений. В таблице Streamlit применяется формат DD.MM.YYYY HH:mm. Конкретное отображаемое часовое смещение может зависеть от клиента; для сверки используйте значение БД в UTC.
Пример. Для заказа 55830644865: 2026-04-06 21:24:03.930000+00:00.
YM-02. Дата отгрузки — shipment_date
Источник. e_com.ya_orders.shipment_date, исходно delivery.shipment.shipmentDate заказа из Yandex Partner API. ya-order-notify также обновляет поле при изменении даты в API.
Преобразование. Расчёта нет; UI показывает только дату в формате DD.MM.YYYY. Пустое значение остаётся пустым.
Пример. 2026-04-09 00:00:00+00:00 → 09.04.2026.
YM-03. Магазин — seller_name
Источник. e_com.platform_sellers.seller_name. Связь: ya_orders.seller_id = platform_sellers.id через обязательный JOIN.
Следствие JOIN. Если связанного продавца нет, позиция вообще не попадёт в таблицу, а не получит пустой магазин.
Пример. seller_id = 2 → ТехноПравда ЯНД.
YM-04. Номер заказа — order_id_str
Источник. Число e_com.ya_orders.order_id, пришедшее из orderId Yandex Partner API.
Формула. После загрузки Python выполняет:
df["order_id_str"] = df["order_id"].astype(str)
Преобразование в строку предотвращает научную запись и форматирование длинного идентификатора как числа.
Пример. 55830644865 → строка "55830644865".
YM-05. Offer ID — offer_id
Источник. e_com.ya_order_items.offer_id; исходное поле API — items[].offerId. Это идентификатор предложения магазина, а не marketSku и не внутренний item_id Яндекса.
Пример. BRITVS100.
YM-06. Наименование — product_name
Источник. Не ya_order_items.offer_name. Используется текущее внутреннее название группы товара:
ya_order_items.feed_item_id
→ yandex_feed_items.id
→ yandex_feed_items.unique_product_group_id
→ unique_product_groups.id
→ unique_product_groups.name
Оба соединения обязательные. Если фид или группа удалены физически, позиция исчезнет из результата. Переименование unique_product_groups.name изменит отображение старых заказов, потому что это не снимок на дату заказа.
Пример. Электробритва Xiaomi Mijia Electric Shaver S100 (MSX201/NUN4062CN) CN.
YM-07. Статус заказа — order_status
Это наиболее подробный статус позиции из транзакционного отчёта с fallback на технический API-статус заказа.
Источники. e_com.ya_order_transactions_report.status и e_com.ya_orders.status.
Агрегация транзакций по ya_order_items_id. У одной позиции может быть несколько транзакций. SQL формирует tr.status:
- если есть хотя бы одна возвратная строка со статусом
Возврат оформленилиНевыкуп передан вами хотя бы одна невозвратная —Частично возвращён; - иначе берётся максимальный возвратный статус;
- если возвратных нет —
MAX(status).
Итоговая формула:
COALESCE(tr.status, o.status)
В отчёте это поле берётся с листа «Транзакции по заказам и товарам», Excel-столбец Y (iloc[24]). Если транзакционный отчёт ещё не загружен, показывается код API вроде DELIVERED.
Пример. ya_order_transactions_report.status = 'Доставлен покупателю', поэтому UI показывает Доставлен покупателю, а не ya_orders.status = 'DELIVERED'.
YM-08. Статус заказа 2 — fulfillment_status
Это нормализованный финансовый статус заказа.
Приоритет:
COALESCE(
CASE WHEN tr.status = 'Частично возвращён'
THEN 'Частично возвращён' END,
ya_order_margin_report.status,
русская_расшифровка(ya_orders.status)
)
Частичный возврат намеренно сильнее margin_report: отчёт о марже иногда продолжает показывать Доставлен. Если margin_report отсутствует, коды API преобразуются: DELIVERED → Доставлен, CANCELLED → Отменён, DELIVERY → В доставке, PROCESSING → В обработке, PICKUP → Самовывоз; неизвестный код остаётся как есть.
Отчёт. ya_order_margin_report.status приходит с листа «Услуги и маржа по заказам», Excel-столбец J (iloc[9]).
Пример. margin_report.status = 'Доставлен', возврата нет → Доставлен.
YM-09. Статус платежа — payment_status
Источник. e_com.ya_order_margin_report.payment_status, лист «Услуги и маржа по заказам», Excel-столбец Q (iloc[16]).
Преобразование. Нет. Если отчёт отсутствует, значение NULL. Статусы Переведён и Удержан из платежей покупателей считаются оплаченными при расчёте payout_if_paid и actual_profit.
Пример. Переведён.
YM-10. Количество — quantity
Источник. e_com.ya_order_items.count, исходное items[].count Yandex Partner API.
Использование. Количество не только отображается: им умножаются плановая закупка, упаковка, переходник, китайская доставка и минимальная цена. Для затрат при частичном возврате вместо него может использоваться tr_delivered_quantity.
Пример. 1.
YM-11. Отправлено от/из — supplier_name
Источник. e_com.ya_order_items.supplier_name. Это снимок выбора источника товара в момент создания заказа, сохранённый ya-order-notify, а не вычисление wolle-economy-viewer и не фактический поставщик из order_to_supplier.
ItemsCalculator выбирает склад либо предложение поставщика. Для внешнего поставщика выбирается минимальная доступная цена; при отсутствии доступных остатков может использоваться последнее известное предложение. Вместе с ценой в позицию записывается имя поставщика.
Ограничение. Фактический товар позднее может быть взят с другого складского прихода; колонка всё равно показывает первоначальный снимок. Для фактической цены используется отдельная складская цепочка в следующей колонке.
Пример. Opt-instock.
YM-12. Цена закупки — base_price_total
Это плановая закупка всей позиции по снимку на момент заказа.
Исходное поле. e_com.ya_order_items.base_price — цена одной единицы, выбранная ya-order-notify при создании заказа. Для рублёвого предложения это цена поставщика; для валютного предложения ItemsCalculator переводит цену в рубли по используемым им курсам и сохраняет результат. Значение приводится к целому перед записью.
Формула:
base_price_total = ya_order_items.base_price × quantity
Эта колонка не заменяется фактической закупкой и используется как база плановой маржи.
Пример. 900 × 1 = 900,00 ₽.
YM-13. Факт. закупка — effective_purchase_total
Назначение. Максимально близкая к реально понесённой закупочной стоимости сумма всей позиции.
Источник фактической цены. e_com.order_to_supplier.ru_custom_price или ru_price, найденные через all_split_orders и движения склада:
- для каждой позиции берутся её
all_split_orders; - определяется последнее
stock_movement_transactionsдля split-заказа; - если движений нет или последнее движение имеет
type = 'lost', источником остаётся текущийall_split_orders; - иначе выбирается первый split-заказ, который привёз товар на соответствующий
warehouse_new_id; - по его
order_to_supplier_idнаходится цена; - обычно применяется
COALESCE(ru_custom_price, ru_price, 0); - защита исторических данных: если валюта не RUB/RUR и
ru_custom_price = custom_price, используется корректно пересчитанныйru_price.
Из-за нескольких split-заказов SQL использует DISTINCT ON (ya_order_items.id).
Формула отображаемой суммы:
если supplier_price_fact > 0:
effective_purchase_total = supplier_price_fact × quantity
иначе:
effective_purchase_total = base_price_total
Важно для возвратов. Сама отображаемая колонка всегда умножается на полное quantity. Но our_costs и прибыль при частичном возврате считают закупку только доставленных единиц.
Пример. Для split-заказа 58908 найден order_to_supplier.id = 56074: ru_custom_price = NULL, ru_price = 820. Поэтому 820 × 1 = 820,00 ₽, а плановые 900 ₽ не используются в фактических затратах.
YM-14. Цена упаковки — ff_fee_total
Источник за единицу. Текущая справочная ставка e_com.ff_fees.value через цепочку:
ya_order_items.feed_item_id
→ yandex_feed_items.market_modifier_yandex_id
→ market_modifier_yandex.ff_fees_id
→ ff_fees.value
Соединения с модификатором и ставкой — LEFT JOIN, пустое значение превращается в 0.
Формула:
ff_fee_total = COALESCE(ff_fees.value, 0) × quantity
Нюанс истории. В ya_order_items.markup_ff_fees_amount также хранится снимок упаковки при создании заказа, но wolle-economy-viewer его для этой колонки не читает. Если справочник изменился, старый заказ на экране может пересчитаться по новой ставке.
Пример. Текущая ставка 50 ₽; снимок заказа тоже 50 ₽: 50 × 1 = 50,00 ₽.
YM-15. Цена переходника — socket_adapter_total
Источник за единицу. e_com.socket_adapter_fee.value через yandex_feed_items → market_modifier_yandex.socket_adapter_fee_id. Пустое значение становится нулём.
Формула:
socket_adapter_total = COALESCE(socket_adapter_fee.value, 0) × quantity
Как и упаковка, это текущая справочная ставка, хотя в ya_order_items.markup_socket_adapter_fee_amount существует исторический снимок.
Пример. Ссылка на ставку отсутствует, снимок равен 0: 0 × 1 = 0,00 ₽.
YM-16. Доставка из Китая — custom_delivery_fee_total
Исходное значение за единицу. e_com.ya_order_items.markup_custom_delivery_fee_value_amount, сохранённое ya-order-notify из market_modifier_yandex.delivery_fee_value, когда включён is_need_custom_delivery_value.
Условие:
если platform_sellers.location = 'CN':
custom_delivery_fee_total = markup_custom_delivery_fee_value_amount × quantity
иначе:
custom_delivery_fee_total = 0
Для RU-магазина эта надбавка трактуется как доставка ЯМ, уже содержащаяся в market_services, и не считается нашим отдельным расходом. Для CN-магазина это собственная доставка из Китая и она входит в our_costs.
Пример. В позиции сохранено 410 ₽, но platform_sellers.location = 'RU', поэтому UI показывает 0,00 ₽. Это намеренное обнуление, а не отсутствие исходного значения.
YM-17. Цена + маржа — price_with_margin
Плановая закупка с нашей процентной маржой, без комиссий ЯМ, упаковки и прочих надбавок.
price_with_margin = base_price_total × (1 + margin_percent / 100)
margin_percent берётся из e_com.ya_order_items.margin_percent. При создании заказа это сумма базового множителя, рассчитанного по категории/магазину/остаткам, и дополнительной маржинальности группы товара; для товара не со склада дополнительная маржинальность обнуляется.
Пример. 900 × (1 + 29 / 100) = 1 161,00 ₽.
YM-18. Маржа план ₽ — our_margin
Плановая рублёвая маржа на плановой закупке:
our_margin = base_price_total × margin_percent / 100
Если margin_percent пуст, Python подставляет 0.
Пример. 900 × 29% = 261,00 ₽.
YM-19. Маржа факт ₽ — margin_fact_rub
Это алиас уже рассчитанной колонки profit:
df["margin_fact_rub"] = df["profit"]
То есть «фактическая маржа» здесь учитывает market_services, наши промо-списания и our_costs, но не использует fact_commissions. Подробная формула приведена в разделе «Прибыль».
Пример. 289,93 ₽.
YM-20. Маржа план % (от цены продажи) — margin_plan_pct
margin_plan_pct = round(our_margin / sell_price × 100, 2)
При sell_price = 0 знаменатель заменяется на NaN, поэтому результат пустой, а не бесконечность.
Пример. 261 / 2 340 × 100 = 11,1538…% → в данных 11,15%, UI форматирует одним знаком как 11,2 %.
YM-21. Маржа факт % (от цены продажи) — margin_fact_pct
Сначала вычисляется техническая margin_pct, затем копируется:
margin_fact_pct = round(profit / sell_price × 100, 2)
Пример. 289,93 / 2 340 × 100 = 12,390…% → 12,39%, в UI 12,4 %.
YM-22. Маржа план % (от закупки) — margin_plan_on_cost_pct
margin_plan_on_cost_pct = round(our_margin / base_price_total × 100, 2)
При ненулевой закупке это восстанавливает ya_order_items.margin_percent. База — плановая, а не фактическая закупка.
Пример. 261 / 900 × 100 = 29,00%.
YM-23. Маржа факт % (от закупки) — margin_fact_on_cost_pct
margin_fact_on_cost_pct = round(profit / base_price_total × 100, 2)
Название «от закупки» означает деление именно на плановую base_price_total, даже когда для прибыли применяется более низкая фактическая закупка.
Пример. 289,93 / 900 × 100 = 32,214…% → 32,21%, в UI 32,2 %.
YM-24. Минимальная цена — min_sell_price_total
Источник за единицу. e_com.ya_order_items.final_price. Это снимок минимально допустимой цены, рассчитанный ya-order-notify в момент создания заказа. Поле modifier_price в текущей записи содержит то же значение, но viewer читает именно final_price.
Упрощённо расчёт снимка включает закупку, плановую маржу, упаковку, обработку, приём платежа, переходник/доставку при необходимости, категорийную комиссию, перевод платежа и ограниченную минимумом/максимумом доставку. Затем итог приводится к целому. ya-updater использует родственную логику для последующих обновлений цены.
Формула таблицы:
min_sell_price_total = ya_order_items.final_price × quantity
Пример. 2 340 × 1 = 2 340,00 ₽.
YM-25. Ожидаемая прибыль — expected_profit
expected_profit = expected_payout − our_costs
promo_discounts в ожидаемую прибыль не входит. our_costs считается по доставленному количеству:
delivered_q = tr_delivered_quantity, если транзакционный отчёт существует
иначе quantity
our_costs = effective_purchase_price_per_unit × delivered_q
+ ff_fee × delivered_q
+ socket_adapter_fee × delivered_q
+ custom_delivery_fee × delivered_q, только для CN
Для частичного возврата возвращённые единицы не считаются потерянной закупкой: предполагается, что товар вернулся на склад.
Пример. 1 957,99 − 870,00 = 1 087,99 ₽.
YM-26. Цена продажи — sell_price
Основной источник. e_com.ya_order_margin_report.sell_price, лист «Услуги и маржа по заказам», Excel-столбец N (iloc[13]). Это сумма, которую ЯМ признаёт продажей: платёж покупателя плюс субсидия Маркета.
Fallback при отсутствии margin report:
sell_price = (ya_order_items.buyer_price + ya_order_items.subsidy) × quantity
После этого для частичного возврата вычитается сумма sell_price возвратных строк ya_order_transactions_report. Возвратные строки определяются по статусам Возврат оформлен и Невыкуп передан вам. Нельзя вычитать только customer_refund_amount: оно не включает возвращаемую субсидию.
sell_price_final = sell_price − SUM(transaction.sell_price для возвратов)
Гранулярность. margin_report.sell_price относится ко всему заказу и повторяется у каждой позиции multi-item заказа.
Пример. Margin report содержит 2 340,00 ₽, возвратов нет. В транзакционном отчёте эта сумма раскладывается как customer_payment_amount = 1 601 ₽ и bonuses = 739 ₽: 1 601 + 739 = 2 340 ₽.
YM-27. Субсидия ЯМ (справочно, в sell_price) — bonus_points
Источник. Сумма e_com.ya_order_transactions_report.bonuses по позиции. В Excel это лист «Транзакции по заказам и товарам», столбец AJ (iloc[35]).
bonus_points = SUM(transactions.bonuses по ya_order_items_id)
Это скидка/субсидия за счёт ЯМ. Она уже включена в sell_price и показывается только для объяснения состава цены. Прибавление к доходу или прибыли создаст двойной счёт.
Пример. 739,00 ₽; проверка: 1 601 ₽ покупателя + 739 ₽ субсидии = 2 340 ₽ продажи.
YM-28. Промо-расходы (наши баллы) — promo_discounts
Источник. Агрегат e_com.ya_payments_reports.transaction_amount по заказу. Значение намеренно остаётся отрицательным.
Поддерживаются два формата отчёта:
SUM(CASE
WHEN transaction_source = 'Скидка за участие в совместных акциях'
OR (transaction_source IS NULL AND payment_status = 'Списание')
THEN transaction_amount
ELSE 0
END)
В Excel используются лист «Отчёт о платежах», сумма из U (iloc[20]), источник W (iloc[22]) и статус X (iloc[23]).
Пример. Старый формат содержит два списания: −126,25 ₽ («Буст продаж») и −671,81 ₽ («Размещение товарных предложений»). Сумма: −798,06 ₽.
YM-29. Разница от мин. цены — diff_from_min_price
diff_from_min_price = COALESCE(sell_price, 0) − min_sell_price_total
Положительное значение означает продажу выше снимка минимальной цены, отрицательное — ниже. При отсутствующей цене продажи код подставляет ноль, поэтому получится отрицательная минимальная цена, а не пустое значение.
Пример. 2 340 − 2 340 = 0,00 ₽.
YM-30. Расчётные комиссии — calc_commissions
Это плановые суммы, рассчитанные ya-order-notify при создании заказа и сохранённые в позиции:
calc_commissions =
ya_order_items.commission_yandex_category_fee_amount
+ ya_order_items.commission_yandex_transfer_payments_fee_amount
+ ya_order_items.commission_yandex_delivery_fee_actual_amount
В каждое пустое поле SQL подставляет 0. Надбавки markup_* сюда не входят: это состав цены, а не комиссии ЯМ.
Расчёт снимка:
- категорийная комиссия = рассчитанная минимальная цена × ставка категории;
- комиссия за перевод = минимальная цена × ставка перевода платежа;
- доставка = процент от предварительной цены, ограниченный настроенными минимумом и максимумом; для
ch_mappingравна нулю.
Пример. 481,2972258917 + 50,3174372523 + 0 = 531,614663144 ₽; UI показывает 531,61 ₽.
YM-31. Комиссии (margin_report) — market_services
Источник. e_com.ya_order_margin_report.market_services, лист «Услуги и маржа по заказам», Excel-столбец M (iloc[12]). Расчёта в viewer нет; пустое значение становится нулём только при последующих формулах.
Это основной источник комиссий для экономики заказа. В отличие от расчётных комиссий, сумма пришла из итогового отчёта ЯМ.
Гранулярность. Сумма относится ко всему заказу и повторяется у всех его позиций.
Пример. 382,01 ₽.
YM-32. Факт. комиссии (ЛК) — fact_commissions
Источник. Фактические отрицательные строки e_com.ya_payments_reports.transaction_amount, агрегированные по заказу. Сумма превращается в положительное число:
SUM(CASE
WHEN transaction_amount < 0
AND (
transaction_source = 'Оплата услуг Яндекс.Маркета'
OR (transaction_source IS NULL AND payment_status = 'Удержание')
)
THEN -transaction_amount
ELSE 0
END)
Новый формат определяется по transaction_source, старый — по payment_status. Промо-строки со статусом Списание исключаются.
Не участвует в прибыли. Колонка диагностическая. expected_payout использует market_services, а не fact_commissions.
Пример и обнаруженное расхождение. У заказа один полный набор удержаний составляет:
0,12 + 320,00 + 53,82 + 1,28 + 6,79 = 382,01 ₽
В ya_payments_reports этот набор записан дважды (десять строк вместо пяти), поэтому SQL честно возвращает 764,02 ₽. Это объясняет отличие от market_services = 382,01 ₽ и указывает на дубль исходных строк/слияние файлов отчёта, а не на формулу прибыли.
YM-33. Доход за вычетом комиссий — income_after_fees
В текущем коде это прямой алиас ожидаемой выплаты:
income_after_fees = expected_payout
= max(sell_price − market_services, 0)
fact_commissions не используется.
Пример. 2 340 − 382,01 = 1 957,99 ₽.
YM-34. Прибыль — profit
Основная фактическая экономика с учётом наших промо-расходов:
profit = expected_payout + promo_discounts − our_costs
Так как promo_discounts отрицательные, операция + уменьшает прибыль. Затраты при возврате считаются только по доставленным единицам.
Пример. 1 957,99 + (−798,06) − 870 = 289,93 ₽.
YM-35. Разница Прибыли Факт/Ожид — profit_vs_expected
Несмотря на название, «факт» здесь — actual_profit, а не колонка profit:
profit_vs_expected = actual_profit − expected_profit
actual_profit не учитывает promo_discounts, поэтому у обычного оплаченного заказа оно совпадает с expected_profit, а разница равна нулю. Эта колонка не показывает влияние промо; для него сравнивайте profit и profit_no_promo.
Пример. 1 087,99 − 1 087,99 = 0,00 ₽, хотя profit = 289,93 ₽.
YM-36. Доход за вычетом комиссий и промо — income_after_fees_promo
income_after_fees_promo = expected_payout + promo_discounts
Закупка и упаковка здесь ещё не вычтены.
Пример. 1 957,99 − 798,06 = 1 159,93 ₽.
YM-37. Прибыль без учёта промо-расходов — profit_no_promo
profit_no_promo = income_after_fees − our_costs
= expected_payout − our_costs
Для текущей модели это то же арифметическое значение, что expected_profit.
Пример. 1 957,99 − 870 = 1 087,99 ₽.
YM-38. Штраф за отмену заказа — seller_cancel_penalty
Источник. ya_payments_reports, строки с точным именем услуги Отмена заказа по вине продавца и отрицательной суммой:
SUM(CASE
WHEN item_name_or_service_name = 'Отмена заказа по вине продавца'
AND transaction_amount < 0
THEN -transaction_amount ELSE 0
END)
Excel-поля: название S (iloc[18]), сумма U (iloc[20]). Результат показывается положительным расходом.
Пример. Подходящих строк нет → 0,00 ₽.
YM-39. Штраф за позднюю отгрузку — late_ship_penalty
Аналогичный агрегат платёжного отчёта для точного названия Отгрузка или доставка не вовремя:
SUM(-transaction_amount для отрицательных подходящих строк)
Пример. Подходящих строк нет → 0,00 ₽.
YM-40. Нам перевели за заказ — payout_if_paid
Это не сумма строк платёжного отчёта. Viewer проверяет ya_order_margin_report.payment_status:
если payment_status ∈ {'Переведён', 'Удержан из платежей покупателей'}:
payout_if_paid = expected_payout
иначе:
payout_if_paid = 0
Пример. Статус Переведён → 1 957,99 ₽.
YM-41. Сумма выплаты (если нет даты, то ожидаемая) — expected_payout
Название историческое: формула не зависит от даты выплаты.
expected_payout = max(COALESCE(sell_price, 0) − COALESCE(market_services, 0), 0)
Ограничение снизу не позволяет показать отрицательную выплату. Фактические строки ya_payments_reports не суммируются: отчёт смешивает перечисления, комиссии и промо, а старые выгрузки могут содержать дубли.
Пример. max(2 340 − 382,01, 0) = 1 957,99 ₽.
YM-42. Дата выплаты — last_payment_date
Два источника с приоритетом транзакционного отчёта.
MAX(ya_order_transactions_report.customer_payment_date)по позиции — лист «Транзакции по заказам и товарам», Excel AG (iloc[32]).- Fallback из
ya_payments_reports.payment_date: - новый формат:
transaction_source IS NOT NULLи статусУдержан из платежей покупателейилиПереведён по графику выплат; - старый формат:
transaction_source IS NULLиpayment_status = 'Начисление'.
Python приводит обе серии к UTC и применяет transaction_date.fillna(payment_report_date). UI показывает DD.MM.YYYY.
Пример. Платёжный отчёт старого формата даёт максимум 01.04.2026, но транзакционный отчёт содержит 20.04.2026 и имеет приоритет. UI показывает 20.04.2026.
YM-43. Фактическая прибыль — actual_profit
Сначала определяется факт оплаты:
has_payment = last_payment_date заполнена
ИЛИ payment_status считается оплаченным
Затем:
если оплаты нет:
actual_profit = 0
иначе если заказ отменён до отгрузки:
actual_profit = expected_payout
− seller_cancel_penalty
− late_ship_penalty
иначе:
actual_profit = expected_payout − our_costs
Отмена «до отгрузки» распознаётся по статусам Заказ отменен до обработки, Отменен при обработке и Отменён; закупочные затраты в этой ветке считаются непонесёнными. Для доставленных/возвращённых заказов используется полный расчёт затрат доставленного количества.
Критически важно. actual_profit не вычитает promo_discounts, поэтому не равно основной колонке profit при наличии промо.
Пример. Оплата есть, заказ доставлен: 1 957,99 − 870 = 1 087,99 ₽.
Исходные строки примера
Ниже минимальный набор фактов, достаточный для ручного воспроизведения результата.
ya_orders
id=43367
order_id=55830644865
seller_id=2
status=DELIVERED
sub_status=DELIVERY_SERVICE_DELIVERED
created_at=2026-04-06T21:24:03.930000+00:00
shipment_date=2026-04-09T00:00:00+00:00
ya_order_items
id=27032, feed_item_id=1812, offer_id=BRITVS100, count=1
base_price=900, margin_percent=29, final_price=2340
supplier_name=Opt-instock, subsidy=739
markup_ff_fees_amount=50
markup_socket_adapter_fee_amount=0
markup_custom_delivery_fee_value_amount=410
commission_yandex_category_fee_amount=481.29722589167767
commission_yandex_transfer_payments_fee_amount=50.317437252311755
commission_yandex_delivery_fee_actual_amount=0
ya_order_margin_report
sell_price=2340.00
market_services=382.01
status=Доставлен
payment_status=Переведён
ya_order_transactions_report
status=Доставлен покупателю
sell_price=2340.00
customer_payment_amount=1601.00
bonuses=739.00
other_market_discounts=739.00
customer_payment_date=2026-04-20
customer_refund_amount=0
Складская фактическая цена
all_split_orders.id=58908
order_to_supplier.id=56074
currency=RUB
ru_custom_price=NULL
ru_price=820.00
ya_payments_reports
Промо-строки:
| Услуга | Статус | Сумма |
|---|---|---|
| Буст продаж, оплата за продажи | Списание | −126,25 ₽ |
| Размещение товарных предложений | Списание | −671,81 ₽ |
Один набор комиссий:
| Услуга | Статус | Сумма |
|---|---|---|
| Приём платежа | Удержание | −0,12 ₽ |
| Начисления за доставку | Удержание | −320,00 ₽ |
| Перевод платежа | Удержание | −53,82 ₽ |
| Буст продаж, оплата за продажи | Удержание | −1,28 ₽ |
| Размещение товарных предложений | Удержание | −6,79 ₽ |
Этот набор удержаний присутствует в таблице дважды; поэтому диагностическая сумма fact_commissions удваивается до 764,02 ₽.
Известные ограничения и ловушки
Заказные деньги повторяются у каждой позиции
sell_price, market_services, агрегаты платежей и их производные присоединяются по ya_order_id. В multi-item заказе они повторяются. Нельзя получать выручку простым SUM по строкам таблицы; сначала дедуплицируйте заказ либо разработайте правило распределения.
Основная прибыль не использует «Факт. комиссии (ЛК)»
profit строится от margin_report.market_services. fact_commissions служит контролем. Поэтому дубль платежных строк не портит основную прибыль примера, но сигнализирует о качестве импорта.
«Фактическая прибыль» не учитывает промо
actual_profit подтверждает наличие выплаты и вычитает затраты, но не promo_discounts. Для итоговой экономики с промо смотрите profit / margin_fact_rub.
Упаковка и переходник берутся из текущих справочников
Хотя заказ хранит markup_ff_fees_amount и markup_socket_adapter_fee_amount, запрос viewer берёт ff_fees.value и socket_adapter_fee.value через текущий модификатор товара. Изменение справочника способно ретроспективно изменить старые строки.
Китайская доставка зависит от локации магазина
Ненулевой markup_custom_delivery_fee_value_amount у RU-магазина отображается как ноль и не входит в затраты. Это видно на заказе примера: исходно 410 ₽, итог 0 ₽.
Дата выплаты — гибрид двух отчётов
Дата транзакционного отчёта приоритетнее платёжного, даже если платёжный отчёт содержит другую дату. При расследовании всегда смотрите оба источника.
Кэш Streamlit
Загрузчики помечены @st.cache_data с TTL из CACHE_TTL (по умолчанию 3600 секунд). Только что импортированный отчёт может не появиться до истечения TTL или очистки кэша.
Округление
Деньги рассчитываются в pandas как числовые значения и только форматируются в UI до двух знаков. Расчётные комиссии могут хранить длинную дробную часть. Процентные колонки округляются до двух знаков в данных, а UI показывает один.
Диагностика расхождения
Проверяйте сверху вниз:
- найдите
ya_orders.idпо внешнемуorder_id; - посчитайте позиции и убедитесь, что понимаете их гранулярность;
- сравните
ya_order_margin_report.sell_priceиmarket_services; - проверьте возвратные строки
ya_order_transactions_report; - сгруппируйте
ya_payments_reportsпоtransaction_source,payment_status, названию и сумме; найдите дубли; - проследите
all_split_orders → stock_movement_transactions → order_to_supplier; - воспроизведите
our_costs, затемexpected_payout,profit_no_promoиprofit; - учитывайте TTL кэша.
Безопасный базовый запрос для поиска сущностей:
SELECT o.id AS ya_order_id, i.id AS item_id, i.offer_id, i.count
FROM e_com.ya_orders o
JOIN e_com.ya_order_items i ON i.order_id = o.id
WHERE o.order_id = 55830644865;
Проверка дублей платёжного отчёта:
SELECT
item_name_or_service_name,
transaction_source,
payment_status,
transaction_amount,
COUNT(*) AS rows_count
FROM e_com.ya_payments_reports
WHERE order_id = 55830644865
GROUP BY 1, 2, 3, 4
ORDER BY rows_count DESC, item_name_or_service_name;
Где менять поведение
| Задача | Проект и файл |
|---|---|
| Добавить/убрать/переименовать колонку | wolle-economy-viewer/src/wolle_economy/ui/columns.py |
| Изменить основной SQL и JOIN | wolle-economy-viewer/src/wolle_economy/db/queries.py |
| Изменить порядок объединения запросов | wolle-economy-viewer/src/wolle_economy/domain/loader.py |
| Изменить финансовую формулу | wolle-economy-viewer/src/wolle_economy/domain/economics.py |
| Изменить формат/скрытие UI | wolle-economy-viewer/src/wolle_economy/ui/components/orders/table.py |
| Изменить импорт united-orders | ya-reports/backend/src/services/orders_to_db.py |
| Изменить импорт united-netting | ya-reports/backend/src/services/payments_to_db.py |
| Изменить снимок цены и комиссий нового заказа | ya-order-notify/backend/src/services/items_calculator.py и repository/ya_orders.py |
| Изменить последующее ценообразование предложения | ya-updater/backend/src/services/items_calculator.py и services/ya_sync.py |
После изменения логики обновите этот гайд, пример и dist/ в одном коммите.
Зафиксированные ревизии источников
| Проект | Ветка | Commit |
|---|---|---|
wolle-economy-viewer |
main |
ed9619c85b97f9f0820f537db30ae9c4c195a51f |
ya-reports |
master |
ed2c18c393f9bfc80a5c0fe936cfc33857f31acb |
ya-updater |
master |
12a5d76e85622df34837e6158809712fa834d530 |
ya-order-notify |
main |
931294ecf2cef809cc07f71db29ca7ef81b2f8ea |
wolle-economy-viewer, ya-reports и ya-order-notify были без незакоммиченных изменений. В ya-updater присутствовали два неотслеживаемых Markdown-файла (calculation_2526849.md, example.md); они использовались только как дополнительный контекст и не меняли исполняемый код.