Расчёты (рейтинг и отгрузка)
Замер от: 2026-08-14 · чем проверено: замер базы, замер карты, прогоны тестов рейтинга (86 passed) и отгрузки (11 passed) Для кого: тому, кто трогает рейтинг или отгрузку, читает их числа на страницах или сверяет расхождения между контурами.
Что делает
Расчёты у нас двух разных родов, и путать их нельзя. Рейтинг считается пакетно, отгрузка — во время запроса:
| Рейтинг | Отгрузка | |
|---|---|---|
| Когда считается | пакетно, шагом 14 сборки ядра | во время запроса |
| Где код | marketplace-collector-v3/kernel/etl/build_fact_rating.py | data_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.08 —
shipmentsотстаёт от остального ядра на два дня (12.08 против 14.08, замер базы), но механизм отставания установлен (см. «Какие правила»): таблица растёт только от действия пользователя, у неё нет планового писателя. Осталась невыясненной причина отсутствия сохранений — вопрос использования, а не кода. Чинить запрещено — это неприкосновенная область; отметить обязаны.
Куда за подробностью
- formulas.yaml — формула рейтинга;
- shipment.yaml — канон отгрузки;
- замер базы —
fact_rating(6007 строк),shipments(38 721); - замер карты — блоки 5 «Расчёты» и 6 «Выдача» (прямое соединение);
data_api/routers/reports/shipment/—calc.py,stored.py,corrective.py,ozon_fbo.py;- модель данных — факты ядра.