Меня зовут Иван Васильев, я ведущий аналитик в Ви.Tech, IT-дочке ВсеИнструменты.ру. Хочу показать, как из на первый взгляд простой формулы KPI выросла инженерная задача: нам нужно было не просто посчитать интегральный показатель эффективности поставщика, а объяснить, какая именно закупка его ухудшила и почему. В статье разберу, почему для агрегированных метрик не работает наивный подход, как мы пришли к remove-one analysis и где пришлось отдельно фиксировать логику расчета, чтобы результат вообще можно было интерпретировать.
В одной из систем, над которой я работаю, мы рассчитываем интегральный показатель эффективности поставщика. Сначала задача казалась очень простой: несколько метрик, несколько весов, обычная агрегированная формула. Но однажды бизнес задал вопрос:
| Почему показатель поставщика снизился?
Ответить на него можно было бы общими словами. Но затем появился уточняющий вопрос:
| А какая именно закупка ухудшила показатель?
И в этот момент простая аналитическая задача внезапно превратилась в интересную инженерную проблему: с агрегированными метриками, нелинейными функциями и неожиданными эффектами, которые больше похожи на задачи из дискретной математики, чем на обычный backend.
Бизнес‑цель
Основная цель была сделать показатель объяснимым для поставщика.
Например:
ИЭП снизился на -5 базисных пуктов, например:
→ из-за OTIF
→ закупка №XXX XXX опоздала на N дней от плановой и согласованной даты доставки
→ фактическая дата > плановой
То есть система должна уметь объяснить: какое конкретное событие ухудшило KPI.
То есть чтобы вместо абстрактного числа поставщик видел:
ИЭП = 82.4
Причины снижения:
OTIF −3.1
FillRate −1.2
Quality −0.7
А внутри метрики:
OTIF
Закупка №3395588
план: 18.01
факт: 21.01
опоздание: 3 дня
влияние: −1.4
Что такое explainable KPI
В итоге мы пришли к простой модели объяснения: KPI можно разложить на несколько уровней.

То есть структура объяснения выглядит так:
KPI
↓
метрика
↓
конкретное событие
Именно третий уровень делает KPI понятным для поставщика.
Как считается ИЭП
ИЭП агрегирует несколько метрик:
-
Fill Rate
-
Quality Score
-
OTIF
-
Lead Time
Формула выглядит так:

где веса суммарно дают 1:
Например:
OTIF = 0.25
Fill Rate = 0.25
QualityScore = 0.25
Lead Time = 0.25
Период расчета
ИЭП пересчитывается каждый день.
Окно данных определяется следующим образом:
1. берем текущую дату
2. отнимаем 14 дней
3. от полученной даты берем закупки на 30 дней назад
Такое окно позволяет учитывать задержки поставок и корректно обрабатывать фактические даты доставки.
Почему наивный подход не работает
Первая идея — посчитать метрику по каждой закупке отдельно.
Но это неправильно.
Потому что метрики считаются по агрегированным суммам.

-
Orderedi - количество заказанного товара в закупке
-
SupplierCancelsiSupplierCancels_iSupplierCancelsi - количество товара, не поставленного поставщиком
То есть метрика считается не по закупкам отдельно, а по сумме показателей всех закупок.
Правильная идея: remove-one analysis
Чтобы понять вклад элемента в агрегат, используется простой метод.
-
считаем метрику по всем данным Metricall
-
считаем метрику без одной записи Metricwithout i
-
разница - это влияние записи

Почему remove-one работает
Интуитивно этот метод выглядит так:

Если после удаления закупки метрика улучшается, значит эта закупка ее ухудшала.
Пример с Fill Rate
Пусть есть три закупки:
|
Закупка |
Ordered |
Cancels |
|
A |
100 |
5 |
|
B |
200 |
0 |
|
C |
50 |
15 |
Общий Fill Rate
Ordered_total = 350
Cancels_total = 20
FillRate=(1−350/20)×100=94.29
Убираем закупку C
Ordered = 300
Cancels = 5
FillRate=(1−300/5)×100=98.33
Влияние
ΔFillRateC=94.29−98.33=−4.04
Закупка ухудшила метрику на 4.04 пункта.
Когда все стало сложнее
Fill Rate оказался простой метрикой.
Но дальше была Quality Score. - QualityScore=100−DefectRate
где DefectRate может считаться двумя способами.

Логика выбора ветки
В системе есть правило. Если качество падает ниже 90%, используется другая ветка.
если (100 − DefectRate_pcs) < 90
используем pcs
иначе
если (100 − DefectRate_rub) < 90
используем rub
иначе
используем pcs
Неожиданная проблема
Когда мы начали считать влияние закупок, произошло следующее.
QualityScore_all считается по pcs
Удаляем одну закупку.
И внезапно:
QualityScore_without_i считается по rub

Мы сравниваем две разные функции.
Результат становится математически некорректным.
Инженерное решение
Мы приняли правило: ветка расчета фиксируется на уровне агрегированного показателя.
Алгоритм:
1. считаем QualityScore_all
2. фиксируем calculation_mode
3. считаем влияние закупок в той же ветке

Подводные камни агрегированных метрик
Во время реализации мы столкнулись с несколькими неожиданными вещами.
-
Нелинейность метрик
Удаление одного элемента может менять результат непропорционально. -
Переключение формул
Если метрика выбирает ветку расчета, remove-one может переключить ее. -
Скользящие окна
Если окно данных меняется каждый день, влияние одной записи может -
изменяться со временем.
Почему мы не использовали альтернативный подход
На этапе проектирования мы также рассматривали альтернативный способ расчета влияния.
Идея заключалась в следующем:
вместо того чтобы исключать закупку из расчета, можно было бы “исправить” ее и пересчитать метрику — например, считать, что поставка прошла вовремя и в полном объеме.
Формально это выглядело бы как: заменяем статус закупки → пересчитываем KPI → сравниваем результат.
Такой подход часто используется в аналитике и известен как what-if анализ.
В чем отличие подходов
Если упростить, различие можно описать так:
remove-one:
показывает фактический вклад закупки в KPI
what-if:
показывает, как KPI изменился бы, если бы закупка была идеальной
Почему мы выбрали remove-one
В нашем случае ключевым было бизнес-требование:
- показать, какие закупки ухудшили показатель
Для такой задачи важно измерять именно фактическое влияние, а не потенциальное улучшение.
Поэтому мы остановились на подходе с исключением закупки из расчета.
Небольшое наблюдение
Интересно, что оба подхода дают численно похожие результаты в простых случаях,
но начинают заметно расходиться по мере усложнения метрик и роста объема данных.
Это особенно проявляется при работе с агрегированными показателями, где вклад одного элемента зависит от всей выборки.
Вывод
В итоге выбор метода оказался не столько математическим, сколько семантическим:
мы отвечаем на вопрос:
"что повлияло на показатель?"
а не
"что могло бы его улучшить?"
И это различие напрямую влияет на интерпретацию результата пользователем.
Где еще встречается такая задача
На самом деле мы решали задачу из области sensitivity analysis.
Похожие методы используются:
-
в машинном обучении (leave-one-out validation)
-
в explainable AI
-
в экономике (Shapley value)
-
в аналитике данных
Но в нашем случае это была обычная backend-задача.
Финал
Мы начинали с простого вопроса бизнеса:
"Покажите плохие закупки".
Но чтобы ответить на него, пришлось разобраться с:
-
агрегированными метриками
-
нелинейными функциями
-
переключением веток расчёта
-
стабильностью показателей
Иногда backend-разработка выглядит как работа с таблицами.
Но стоит попробовать ответить на простой вопрос:
"Какой вклад внесла эта запись в общий KPI?"
и внезапно оказывается, что внутри:
-
немного дискретной математики
-
немного теории агрегатов
-
и довольно много инженерных компромиссов.
И это, пожалуй, одна из причин, почему задачи из реальных систем иногда оказываются интереснее учебных примеров.
Автор: accidental_analyst
