Ритейл · движок распределения

Распределение товара по магазинам

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

  • Три пайплайна разбивки
  • Объяснимые стоп-причины
  • Воспроизводимый расчёт
  • ДоменРаспределение товара по магазинам розничной сети. Конечного заказчика не называем: проект под NDA.
  • СистемаGo-монолит из двух служб — шлюз и вычислительное ядро — поверх gRPC. Наружу GraphQL и REST-загрузка xlsx.
  • ПайплайныПрямая логистика (Split), обратная (BackLogic) и Blackbox — жадное перераспределение магазин → магазин.
  • РольАрхитектура, алгоритмы разбивки, схема MS SQL Server, операторская панель и выгрузки.
3пайплайна разбивки товара
11разделов операторской панели
до 9вкладок в журнале разбивки
MS SQLисточник данных и снимков метрик

Контекст и задача

Распределение, которое нужно уметь объяснить

Конечного заказчика мы не называем — проект под NDA, и прямая оговорка здесь честнее умолчания. Речь о розничной сети: склады, магазины и ежедневное решение, куда что отправить.

Распределение товара — задача, у которой нет одного правильного ответа. Можно везти туда, где быстрее продаётся; можно — туда, где полка пустая; можно оставить запас на складе. Любой выбор кто-то оспорит, поэтому от системы требуется не только результат, но и основание: почему в этот магазин отправили две единицы, а в соседний ни одной.

Второе требование — воспроизводимость. Расчёт опирается на скорость продаж, остатки и потребность, а они меняются каждый день. Если пересчитать вчерашнее распределение сегодня, цифры не совпадут, и разбор превращается в спор о том, какие данные были «на самом деле».

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

Отсюда рамки проекта:

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

Решение

Три пайплайна и то, что делает их проверяемыми

Алгоритмы здесь — только половина работы. Вторая половина в том, чтобы результат можно было прочитать, оспорить и повторить.

  1. 01Split — прямая логистика. Основной пайплайн разбивки идёт по шагам: сначала закрываются обещания, затем считается потребность, после этого запас балансируется в днях продаж, и в конце раздаются аксессуары. Порядок важен: если балансировать раньше, чем закрыты обязательства, система начнёт «щедро» раздавать товар, которого уже кто-то ждёт.
  2. 02BackLogic — обратная логистика. Излишки не растворяются в сети, а собираются обратно на склад: пайплайн находит, где товара больше, чем нужно по продажам, и формирует вывоз. Без этого прямая логистика со временем упирается в товар, который лежит не там, где спрос.
  3. 03Blackbox — перераспределение между магазинами. Жадный алгоритм переброски магазин → магазин по score: товар едет от того, кто им не торгует, к тому, кто его продаёт, минуя склад. Жадная стратегия выбрана осознанно — она даёт объяснимый порядок шагов, а не оптимум, который невозможно защитить перед оператором.
  4. 04Метрики, снимок и задание. Скорость продаж считается по EMA со сглаживанием выбросов — одна акционная неделя не должна выглядеть как новый уровень спроса. Потребность учитывает сезонность и страховой запас. Снимок shop_article_metrics привязан к JobId, поэтому расчёт всегда можно повторить на тех же данных. Сами расчёты выполняются как асинхронные задания со статусами PENDING → RUNNING → DONE/FAILED.
  5. 05Панель, в которой видно основание. Одиннадцать разделов операторской панели и журнал разбивки, разворачивающийся до девяти вкладок: по ним видно не только что уехало, но и почему не уехало — стоп-причины NO_STOCK_WH, NEED_MET, TURNOVER_SENDER проставляются на строке. Любой отфильтрованный срез выгружается в Excel через excelize, а сама панель встроена в бинарь через go:embed — отдельного фронтенд-деплоя нет.

ADR · снимок метрик вместо расчёта «по живым данным»

Контекст. Исходные метрики меняются ежедневно, а разбор распределения происходит позже — иногда сильно позже. Решение. Каждый запуск фиксирует снимок shop_article_metrics и привязывает его к JobId; все шаги пайплайна читают снимок, а не текущее состояние. Следствие. Результат воспроизводится и защищается цифрами. Цена — хранение снимков и дисциплина: ни один шаг не имеет права ходить в живые данные напрямую.

Архитектура

Как это устроено

Две службы вместо россыпи сервисов: шлюз с GraphQL, REST-загрузкой xlsx и встроенной панелью — и вычислительное ядро с тремя пайплайнами. Между ними gRPC, под ними MS SQL Server.

  • Go
  • gRPC
  • GraphQL
  • REST · xlsx
  • MS SQL Server
  • excelize
  • go:embed
  • EMA
  • Асинхронные задания
  • xlsx / csv
  • Режим РКН
  • Whitelist таблиц
allocation / pipeline-topology
Оператор → шлюз (GraphQL, загрузка xlsx, UI через go:embed) → ядро с тремя пайплайнами → MS SQL Server

Результат

Что получил оператор

Движок распределения ценен ровно настолько, насколько его решениям доверяют. Поэтому основная часть результата — про объяснимость и самостоятельность.

Объяснимая разбивка

Журнал разворачивается до девяти вкладок, а строка, по которой товар не поехал, несёт причину. Разговор переходит от «система посчитала странно» к обсуждению конкретного правила.

NO_STOCK_WH · NEED_MET · TURNOVER_SENDER

Повторяемый расчёт

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

JobId · PENDING → RUNNING → DONE/FAILED

Данные без разработчика

Загрузка xlsx и csv с одиннадцатью типами настраиваемых правил разбора и live-CRUD справочников через whitelist управляемых таблиц: оператор правит то, что ему разрешено, и не может задеть остальное.

11 типов правил · whitelist таблиц

Выгрузка любого среза

Любой отфильтрованный список уходит в Excel — данные не приходится копировать руками, чтобы показать их коллеге. Отдельно поддержан режим РКН для комиссионного товара.

excelize · режим РКН

Роль maincode

Что делали мы

Проект под NDA, поэтому цифры сети и коммерческий эффект здесь не приводятся. Инженерная часть описана как есть.

  • АрхитектураGo-монолит из двух служб — шлюз и вычислительное ядро — поверх gRPC; GraphQL и REST-загрузка наружу, панель внутри бинаря через go:embed.
  • АлгоритмыТри пайплайна: Split с порядком шагов от обещаний до аксессуаров, BackLogic для вывоза излишков, Blackbox с жадным перераспределением по score.
  • МетрикиСкорость продаж на EMA со сглаживанием выбросов, потребность с сезонностью и страховым запасом, снимки метрик, привязанные к заданию.
  • ДанныеСхема MS SQL Server, справочники и правила загрузки xlsx/csv, live-CRUD через whitelist управляемых таблиц.
  • ПанельОдиннадцать разделов, журнал разбивки со стоп-причинами, фильтры и выгрузка в Excel на excelize.
  • ГраницыЗаказчика не называем и его показатели не публикуем. В кейсе только то, что относится к системе.

Что применили

Услуги, из которых собран этот проект

Если ваша задача другая — посмотрите разбор мультиарендной ERP или логистическую платформу про микросервисы. Полный список — на странице проектов.

Похожая задача?

Расскажите, какое решение должна принимать система — разберём алгоритм и данные.

Смотрим на исходные данные, правила и то, кому потом придётся объяснять результат. Возвращаемся с архитектурой расчёта и планом. maincode — инженерная студия ИП Диденко М. С.