Репрайсер
ENRU
Кейс - ценовой контур

Цена перестала быть догадкой.

Селлер уходовой косметики на маркетплейсе: каталог, две трети которого это наборы и мультипаки. Четыре механики тянут цену в разные стороны: не остаться без товара, подтянуться за медианой, не уйти в минус по марже, не проиграть соперникам. Все четыре прогоняются одним движком, и каждая рекомендация умеет объяснить себя.

FastAPIPostgreSQL 16CeleryReact 18 TypeScriptSeller APIPerformance APIФинмодуль
64
таблицы данных в 13 модулях бэкенда
97
API-эндпоинтов портала
137
автотестов бэкенда, ночной прогон
4
механики цены в одном конвейере расчёта
60 дней
истории цен, расходов и рекламы собрано
0
методов записи на площадку: только рекомендация
01 Контекст и боль

Цену двигали руками, а витринную цену никто не видел

Заказчик менял цены вручную в кабинете. Решение принималось по цене продавца, а покупатель видит другую цену: площадка добавляет свою скидку. Из-за этого сравнение с соперниками шло по числам, которых на полке нет.

Скидка площадки прячет реальную цену

Цена продавца и цена на полке отличаются почти вдвое. Пока в интерфейсе стояла только цена продавца, менеджер держал пересчёт в голове и ошибался, а сравнение с конкурентом было бессмысленным.

Товар кончался, а цена этого не замечала

Остаток на три дня и остаток на две недели требуют разной скорости реакции. Единого правила "поднять на N процентов" для обоих случаев не хватает: в первом оно опаздывает, во втором мешает.

Себестоимость стояла в таблице дважды

Колонка "только смотреть" и поле правки в другой части экрана. Человек выбирал между ними наугад, а расходились они молча.

Наборы и мультипаки ломают привычные формулы

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

02 Архитектура

Один конвейер расчёта, четыре экрана поверх него

Синки собирают каталог, заказы, финансы и рекламу. Финмодуль превращает транзакции в расходы на единицу. Ценовой движок считает рекомендацию. Репрайсер прогоняет тот же движок в режиме чтения и раскладывает результат по четырём вкладкам: дублировать логику на экране нельзя, иначе появятся две цены и два ответа на один вопрос.

ИСТОЧНИКИ · ТОЛЬКО ЧТЕНИЕ Seller API каталог · цены · остатки отправления · финансы пагинация и лимиты Performance API кампании и расход отчёт по заказам активный отчёт один Себестоимость шаблон xlsx на все товары история и автор правки Синки и финмодуль Celery, журнал прогонов номер заказанного отчёта живёт в Redis: после сбоя отчёт доскачивается расходы долей от выручки, а не делением рублей на штуки дубли в пачке схлопываются до записи PostgreSQL 16 64 таблицы · история 60 дней Redis 7: кэш, брокер, память отчётов Ценовой движок pricing.calc_sku: один вход и один выход на товар предложения механик и список споров между ними пол цены и потолок рекомендация, а не запись Репрайсер Out of Stock · Медиана Контроль маржи · Конкуренты настройка кабинета и товара Рука на пульсе ежедневная таблица та же цена, что в движке Алерты и лента сообщение по каждому артикулу: что движок сделал и почему ГЛАВНОЕ РЕШЕНИЕ Репрайсер не считает цену. Он прогоняет тот же конвейер в режиме чтения и раскладывает результат по вкладкам. Своя логика на экране означала бы две цены и два ответа на один вопрос.
03 Четыре механики

Каждая вкладка отвечает за свой риск

ВкладкаЧто показываетЧто настраивает
Out of Stock остаток, скорость заказов, на сколько дней хватит, шаг лестницы подъёма цель в днях запаса и шаг: общий по кабинету и свой у каждого товара
Медианная цена медиана за 30 дней, рост в процентах, ряд цен графиком, правило подъёма минимальная и максимальная цена, два периода сравнения, шаг подъёма
Контроль маржи себестоимость правится прямо в ячейке, расходы на единицу, маржа, реклама, доля рекламы, акции в минус минимальная маржинальность, учёт рекламы, окно фактических расходов
Конкуренты цены соперников после скидки площадки, насколько мы дороже, когда проверяли на сколько быть дешевле, преследовать ли цену, сколько соперников брать
Вкладка Out of Stock: остаток, скорость заказов, шаг лестницы, текущая и рекомендованная цена парой
Лестница читается от самой острой нехватки к мягкой. Под каждой ценой второй строкой стоит цена на полке: и под текущей, и под рекомендованной.

Значение кабинета по умолчанию, значение товара важнее. Пустая ячейка показывает серым унаследованное значение кабинета, заполненная - своё. Стереть ячейку значит вернуться к общему значению: на сервер уходит пусто, а не ноль. Пусто и ноль здесь разные вещи, и путать их нельзя: ноль в шаге повышения означал бы "не повышать никогда".

Кнопка "ко всем" делает два действия

Пишет значение в настройки кабинета и снимает личные значения товаров по этому полю. Иначе кнопка обходила бы как раз те товары, которые настраивали руками, и выглядела бы сломанной.

Лестница вместо одного шага

Список ступеней читается от самой острой нехватки к мягкой. Один шаг на все случаи вёл себя одинаково и при остатке на шесть дней, и при остатке на день, хотя второй случай требует другой скорости реакции.

04 Пол цены

Консервативный принцип: max(факт; тариф)

Если минимальная цена не задана руками, пол берётся из требуемой маржинальности. А для этого нужны расходы на единицу, и здесь есть два источника, которые врут в разные стороны.

расходы на единицу = max(фактические за окно; тариф прайс-листа) + хранение + услуги
пол цены = (себестоимость + расходы на единицу + реклама) / (1 − требуемая маржинальность)

Тариф не знает про негабарит и дальние склады и потому занижает расход. Факт за удачное окно тоже занижает: дорогие отгрузки в него не попали. Занижение пола продаёт товар в минус, завышение всего лишь оставляет часть заказов, поэтому берётся большее.

Источник виден в строке

Под числом стоит "max(факт за окно; тариф)" или "тариф прайс-листа". Второе означает, что у товара за окно не было выручки, и на это число полагаться нельзя.

Хранение и услуги прибавляются

В тарифе их нет вовсе, они не альтернатива ему. Складывать альтернативы через max, а дополнения через плюс: разные вещи считаются по-разному.

Расходы долей от выручки

Не делением рублей на штуки. Деньги приходят из транзакций, а счётчик штук из отправлений, и они расходятся: за день выручка есть, а счётчик нулевой. Деление на такой счётчик давало логистику дороже самого товара.

Вкладка контроля маржи: себестоимость в ячейке, расходы на единицу с источником, маржа и пол цены
Себестоимость правится прямо в ячейке и уходит в модуль себестоимости, а не в настройки цены: у неё свой каскад, история и автор. Порог изменения односторонний: поднять можно на сколько угодно, сторожится только снижение.
05 Разрешение споров

Четыре механики тянут цену в разные стороны

Порядок их применения зафиксирован в одном месте, а не разбросан по механикам. Иначе два слоя начинают переставлять цену по кругу, и объяснить итог человеку нечем.

1. Стратегиязадаёт отправную точку
2. Конкурентможет опустить цену, но не ниже пола маржи
3. Out of Stockможет поднять её поверх конкурента
4. Медианадобавляет ступеньку сверху
5. Красивая ценаограничивает величину скачка
6. Минимальная ценасильнее всех надстроек, это финальная проверка

Остаться без товара дороже, чем уступить в цене. Поэтому Out of Stock стоит после конкурента и может перебить преследование. Этот случай всегда записывается в споры: в таблице у строки появляется значок "спор" с расшифровкой, а не молча изменившееся число.

Потолок двигать можно, пол нет

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

Колонка "Кто решил"

У каждой строки видно, какая механика оказалась решающей. Без этой колонки таблица цен превращается в оракула: число есть, а спросить не у кого.

Вкладка конкурентов: цены соперников на полке и порядок применения механик
Соперники заводятся руками. Автоподбор с полки снят после проверки: полка предлагает соседние товары категории, а не соперников по этому продукту и цене.
Лента сообщений по артикулам: что движок сделал с ценой и почему
Лента сообщений по артикулу: новая рекомендация, спор механик или "упёрлись в пол". Тестовый формат: система ничего не пишет на площадку.
06 Цена на полке

Две цены всегда парой

Под каждой ценой, и под текущей, и под рекомендованной, второй строкой стоит цена на полке после скидки площадки. Менеджер сравнивает себя с соперниками по витринной цене, а не по цене продавца.

Обязательно парой, и вот почему. Одна витринная цена читается как "столько на полке сейчас". Заказчик открыл карточку, увидел на площадке одно число и не сошёлся с показанным. Ошибки не было: показанное число было полкой рекомендации, а сегодняшняя цена продавца давала другую полку. С тех пор цены всегда стоят парой.

Считает движок, экраны читают

Витринную цену считает тот же конвейер, что и рекомендацию. Свой пересчёт на экране означал бы разные скидки в разных местах: последний замер против среднего за период.

Нет замера - прочерк с причиной

Если скидку площадки замерить не удалось, вместо цены стоит прочерк, а не цена продавца. Подставленная единица показывала бы число, по которому нельзя сравнивать себя с соперниками.

Замер по фактическим отправлениям

Витрина площадки закрыта проверкой на бота даже с браузерным отпечатком. Поэтому скидка считается из цен фактических отправлений: точность порядка половины процента, и она честно указана.

07 Что выяснилось на живом кабинете

Восемь фактов, которые изменили реализацию

Задание писалось по документации площадки. Живой кабинет ответил иначе, и каждый такой ответ стал строкой в отчёте с образцом.

Ожидание по документацииЧто оказалось на самом деле
Воронка отдаёт показы, переходы, корзину и конверсии.Отдаёт только выручку и заказы, остальное отвечает отказом "устаревшие метрики". В таблицах эти колонки стоят прочерком с причиной, а не нулём.
Бренд лежит в характеристике с фиксированным номером.Номер зависит от категории и резолвится по справочнику характеристик.
Метод цен отдаёт артикул площадки.Не отдаёт: только идентификатор товара и артикул продавца. Артикул подставляется из каталога, иначе таблица выглядит заполненной, а связка пустая.
Витрину можно прочитать для замера скидки.Витрина закрыта проверкой на бота даже с российского адреса. Скидка считается из фактических отправлений.
Каталог это отдельные товары.Две трети каталога это наборы и мультипаки. Себестоимость ведётся на артикул целиком.
Активных отчётов может быть несколько.Активный отчёт может быть только один, поэтому его номер хранится и после перезапуска отчёт доскачивается, а не заказывается заново.
Лимиты скидки любые.Допустимы только фиксированные значения из списка, остальные площадка отвергает.
Отправление содержит уникальные артикулы.Один и тот же артикул может встретиться дважды, и пачка отвергалась целиком. Дубли схлопываются до записи, количество суммируется.
08 Итоги

Что изменилось у заказчика

БылоСтало
Цену меняли руками, по цене продавца. Рекомендация с парой цен: цена продавца и цена на полке, и у текущей, и у рекомендованной.
Правила конфликтовали и переставляли цену по кругу. Порядок применения зафиксирован в одном месте, споры записываются и показываются значком с расшифровкой.
Минимальная цена была на глаз. Пол цены считается из требуемой маржинальности по консервативному принципу max(факт; тариф) с видимым источником.
Себестоимость стояла в таблице дважды. Одна ячейка, она же и правится, а правка уходит в модуль себестоимости со своей историей и автором.
Конкурентов подбирали автоматически. Список заводится руками: автоподбор давал соседние товары категории, а не соперников. Записи гасятся, а не удаляются, история цен соперника остаётся.
Непонятно, почему цена такая. Колонка "Кто решил" и лента сообщений по артикулу: что движок сделал с ценой и почему.

Тестовый формат по решению заказчика. Система ничего не пишет на площадку: результат это рекомендация в карточке товара, алерт и сообщение в ленте. Ни одного метода записи в клиентах площадки нет. Так проверяется доверие к движку до того, как ему дадут руль.

Кирилл Седов

Бизнес-аналитик и строитель систем. Начинаю с бизнес-процесса и контракта данных, потом автоматизирую. Здесь ценовое решение перестало быть догадкой и научилось объяснять себя одной строкой.

Связаться в LinkedIn