0023. Движок пересчёта цен репрайсера
Копия ADR из репозитория:
backend/docs/adr/0023-repricer-engine.md. Канонический текст — в репозитории; портал обновляется командойnpm run sync:adr.
- Статус: accepted
- Дата: 2026-06-25
- Связанные задачи: OP-63 (5.3 · Репрайсер)
Контекст и проблема
ТЗ 5.3: движок репрайсера берёт список артикулов с текущими ценами и показателями, оценивает активные правила и применяет изменения через модуль 5.2 (API цен WB). Ключевые вопросы дизайна:
- Как доставить «текущие показатели» в движок?
- Как разрешать конфликт нескольких подходящих правил для одного артикула?
- Как интегрироваться с историей цен (модуль 5.2)?
Рассмотренные варианты
Вопрос 1: источник показателей
| Вариант | Описание | Оценка |
|---|---|---|
| Push API (выбрано) | Клиент передаёт показатели в запросе | Decoupled; движок не зависит от хранилища метрик |
| Pull от сервиса | Движок сам запрашивает показатели из оцифровки | Сильная связность; требует реализации агрегации в движке |
| Периодический cron | Движок сам читает БД по расписанию | Неуправляемость; latency неизвестна |
Вопрос 2: разрешение конфликтов
| Вариант | Описание | Оценка |
|---|---|---|
| Oldest-first (выбрано) | Побеждает правило с наименьшим createdAt | Детерминировано, предсказуемо, прозрачно для пользователя |
| Приоритет явным полем | Числовой priority в таблице правил | Нужна дополнительная ручная настройка |
| Самое строгое правило | Выбирать максимальное/минимальное действие | Непредсказуемо при разных типах действий |
Вопрос 3: интеграция с историей цен
| Вариант | Описание | Оценка |
|---|---|---|
| PriceHistoryService.recordBulk (выбрано) | Движок вызывает уже готовый bulk-метод | Повторно использует существующий путь; source=repricer |
| Новая запись через PricingService | Добавить метод applyRepricerPrices | Лишний слой; PricingModule уже экспортирует PriceHistoryService |
Принятые решения
1. Push API для показателей
POST /repricer/run принимает RepricerRunDto — список { nmId, currentPrice, brand?, metrics }.
Движок не знает, откуда взялись метрики. Клиент (или будущий cron-сервис 5.3) отвечает
за их сборку и передачу.
2. Oldest-first для разрешения конфликтов
При нескольких подходящих правилах для одного артикула побеждает правило с наименьшим
createdAt (старейшее = наивысший приоритет). Правила загружаются из БД c ORDER BY created_at ASC; первое совпадение выбирается и цикл прерывается.
Обоснование: «старые правила = намеренно заданные» — это соответствует ожиданиям
пользователя, настраивающего правила последовательно. Детерминизм гарантируется
монотонностью created_at + id (UUID v4, не сортируется, но created_at уникален
в пределах одного кабинета на практике).
3. Интеграция через PriceHistoryService
Движок напрямую вызывает WbApiClient.request (глобальный сервис, не требует
дополнительных импортов) и PriceHistoryService.recordBulk (экспортируется из
PricingModule) с source = PriceChangeSource.Repricer. oldValue в bulk-записях
равен null — это поведение идентично массовому изменению через PricingProcessor и
задокументировано в ADR-0021.
Последствия
- Движок stateless — легко тестируется и масштабируется.
- Conflict resolution прозрачен и воспроизводим без знания priority-полей.
POST /repricer/runможно вызывать как вручную, так и из будущего cron-планировщика.- oldValue в истории = null для репрайсерных изменений (аналогично bulk). Если нужно
отслеживать «до», используйте последнюю запись истории перед отметкой
repricer.