Перейти к основному содержимому

Расчёты (рейтинг и отгрузка)

Замер от: 2026-08-14 · чем проверено: замер базы, замер карты, прогоны тестов рейтинга (86 passed) и отгрузки (11 passed) Для кого: тому, кто трогает рейтинг или отгрузку, читает их числа на страницах или сверяет расхождения между контурами.

Что делает

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

РейтингОтгрузка
Когда считаетсяпакетно, шагом 14 сборки ядраво время запроса
Где кодmarketplace-collector-v3/kernel/etl/build_fact_rating.pydata_api/routers/reports/shipment/calc.py, SQL в _helpers.py
Где итогgwptd_kernel.fact_rating, 6007 строк (13.08)сам расчёт нигде не лежит, считается заново; сохранённые партии (черновики и подтверждённые) — gwptd_kernel.shipments (38 721 строк, 12.08)

stored.py рядом с calc.py — не второй способ расчёта. Он читает сохранённые партии по адресам /reports/shipment-batches и /reports/shipment-lines; экран просмотра использует их через ShipmentApiPage (комментарий «ПРОСМОТР» в ShipmentCalcApiPage.php:35).

Про рейтинг, со ссылкой на канон formulas.yaml и БЕЗ переписывания формулы:

  • он считается по группам маркетплейсов, а не по каждому: строки сохраняются под первичными mp {1,3,6,9,10}, по одной на группу (canon.py::PRIMARY_GROUPS, build_fact_rating.py::_RATING_MP_SPEC). У площадок 2/4/5/7/8 своей строки нет по устройству — это не пропажа. Mp 10, «Золотое яблоко», включён решением владельца 12.08.2026; его источник — скользящая сводка, поэтому месячный рейтинг допустим, а в суточную оборачиваемость площадка не входит (test_goldapple_v_kanone_reytinga.py);
  • сравнивать надо усаженную величину, а не сырой рейтинг: в отгрузке приоритет задаёт rating_adj, а не rating_raw (канон shipment.yaml, раздел allocation; реализация _calc.py:36);
  • в документации нашлось двенадцать копий формулы рейтинга, три из них — в старой редакции без вычета возвратов. Эта страница тринадцатой копией быть не должна: здесь только ссылка.

Отгрузка неприкосновенна (RULES.md, раздел «НЕПРИКОСНОВЕННОЕ»): кнопка отгрузки работает и используется ежедневно. Описывать можно, менять нельзя — ни формулу, ни пределы складов, ни страховку, ни распределение по площадкам.

Запись отгрузки идёт мимо Data API. Единственный явный вызов DB::connection('kernel') в Laravel находится в app/Filament/Pages/ShipmentCalcApiPage.php:444; внутри этой транзакции строки пишет модель KernelShipment на том же соединении (замер карты, блок 6).

Точка входа в код

Две двери, по роду расчёта:

  • рейтинг — marketplace-collector-v3/kernel/etl/build_fact_rating.py, функция build();
  • отгрузка — data_api/routers/reports/shipment/calc.py, функция shipment_calc (endpoint /reports/shipment-calc).

Чем задаётся

  • рейтинг: formulas.yaml (раздел rating), курс ЦБ (замораживается до первого шага сборки, resolve_steady_state_rate), месячный коэффициент сезонности; состав групп — в marketplace-collector-v3/kernel/etl/canon.py, активные выходные группы — в build_fact_rating.py::_RATING_MP_SPEC;
  • отгрузка: канон shipment.yaml и параметры запроса (marketplace_id, warehouses, limit_qty, limit_rub, forecast_month, coverage_days); точные правила страховки и ограничений брать только из канона, не из пересказа на этой странице.

Какие правила обязан соблюдать

  • формулы — только канон в machine-rules, копий не плодить (см. про двенадцать копий выше);
  • оценка модели — только от реальных продаж: выкуплено минус возвраты. Заказы, спрос и корзины в числитель не идут (DO-NOT-REGRESS.md);
  • рейтинг по группам: отсутствие строк у площадок 2/4/5/7/8 — устройство, а не дефект; mp 10 входит отдельной группой по решению владельца 12.08.2026;
  • отгрузка — неприкосновенная область: любые правки, даже косвенно её задевающие, — стоп и доклад, а не изменение.
  • shipments — операционная таблица, не факт ETL: строки пишет только приложение при нажатии «Рассчитать/Сохранить» в калькуляторе отгрузки — шапка миграции 2026-06-15_shipments.sql, строки 2–17 («В ОТЛИЧИЕ от fact_* это НЕ derived-факт ETL», «ПИСАТЕЛЬ: Laravel-приложение через отдельное соединение 'kernel'»). Data API её только читает. Поэтому дата последней строки — это дата последнего сохранения партии человеком, а не прогона сборки.

Чем проверяется

# Рейтинг (группы и сторожевые условия):
python3 -m pytest marketplace-collector-v3/tests/test_build_fact_rating_guards.py \
marketplace-collector-v3/tests/test_ozon_product_rating_groups.py \
marketplace-collector-v3/tests/test_goldapple_v_kanone_reytinga.py -q
# → 86 passed, 0 skipped (прогон 14.08)

# Отгрузка (прогноз):
cd data_api && python3 -m pytest tests/test_shipment_forecast.py -q
# → 11 passed, 0 skipped (прогон 14.08)

Чего делать нельзя

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

Что НЕ установлено

  • Почему партии не сохраняли 13–14.08shipments отстаёт от остального ядра на два дня (12.08 против 14.08, замер базы), но механизм отставания установлен (см. «Какие правила»): таблица растёт только от действия пользователя, у неё нет планового писателя. Осталась невыясненной причина отсутствия сохранений — вопрос использования, а не кода. Чинить запрещено — это неприкосновенная область; отметить обязаны.

Куда за подробностью