Меня зовут Иван Васильев, я ведущий аналитик в Ви.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

Чтобы понять вклад элемента в агрегат, используется простой метод.

  1. считаем метрику по всем данным Metricall

  2. считаем метрику без одной записи Metricwithout i

  3. разница - это влияние записи

Почему 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. считаем влияние закупок в той же ветке

Подводные камни агрегированных метрик

Во время реализации мы столкнулись с несколькими неожиданными вещами.

  1. Нелинейность метрик
    Удаление одного элемента может менять результат непропорционально.

  2. Переключение формул
    Если метрика выбирает ветку расчета, remove-one может переключить ее.

  3. Скользящие окна
    Если окно данных меняется каждый день, влияние одной записи может 

  4. изменяться со временем.

Почему мы не использовали альтернативный подход

На этапе проектирования мы также рассматривали альтернативный способ расчета влияния.

Идея заключалась в следующем:
вместо того чтобы исключать закупку из расчета, можно было бы “исправить” ее и пересчитать метрику — например, считать, что поставка прошла вовремя и в полном объеме.

Формально это выглядело бы как: заменяем статус закупки → пересчитываем KPI → сравниваем результат.

Такой подход часто используется в аналитике и известен как what-if анализ.

В чем отличие подходов

Если упростить, различие можно описать так:

remove-one:
показывает фактический вклад закупки в KPI

what-if:
показывает, как KPI изменился бы, если бы закупка была идеальной

Почему мы выбрали remove-one

В нашем случае ключевым было бизнес-требование:
- показать, какие закупки ухудшили показатель

Для такой задачи важно измерять именно фактическое влияние, а не потенциальное улучшение.

Поэтому мы остановились на подходе с исключением закупки из расчета.


Небольшое наблюдение

Интересно, что оба подхода дают численно похожие результаты в простых случаях,
но начинают заметно расходиться по мере усложнения метрик и роста объема данных.

Это особенно проявляется при работе с агрегированными показателями, где вклад одного элемента зависит от всей выборки.

Вывод

В итоге выбор метода оказался не столько математическим, сколько семантическим:

мы отвечаем на вопрос:
"что повлияло на показатель?"

а не
"что могло бы его улучшить?"

И это различие напрямую влияет на интерпретацию результата пользователем.

Где еще встречается такая задача

На самом деле мы решали задачу из области sensitivity analysis.

Похожие методы используются:

  • в машинном обучении (leave-one-out validation)

  • в explainable AI

  • в экономике (Shapley value)

  • в аналитике данных

Но в нашем случае это была обычная backend-задача.

Финал

Мы начинали с простого вопроса бизнеса:
"Покажите плохие закупки".

Но чтобы ответить на него, пришлось разобраться с:

  • агрегированными метриками

  • нелинейными функциями

  • переключением веток расчёта

  • стабильностью показателей

Иногда backend-разработка выглядит как работа с таблицами.
Но стоит попробовать ответить на простой вопрос:

"Какой вклад внесла эта запись в общий KPI?"

и внезапно оказывается, что внутри:

  • немного дискретной математики

  • немного теории агрегатов

  • и довольно много инженерных компромиссов.

    И это, пожалуй, одна из причин, почему задачи из реальных систем иногда оказываются интереснее учебных примеров.