Сколько денег вы теряете на инцидентах

Привет! Меня зовут Женя, я аналитик данных в онлайн-школе Skyeng. 

Недавно наши спецы на полтора часа уронили прод, и на этом мы потеряли… Кстати, а сколько?

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

Зачем вычислять стоимость потерь

Итак, случился инцидент. 

f6938ed91f406c4dd5127a34d485f37b.png

Руководство предпочитает выкатывать новые фичи, чтобы получить результат «здесь и сейчас». Но инциденты повторяются и приносят миллионные убытки. Как же убедить бизнес искать корень проблемы?  

Сравним два аргумента:

  1. «Дима, за последние полгода у нас произошло 10 инцидентов. Давай проанализируем их и проведём рефакторинг, чтобы сбоев было меньше?»

  2. «Дима, за последние полгода мы потеряли 10 млн ₽ из-за технических сбоев. Давай поработаем с архитектурой и кодом, иначе к концу года убытки вырастут вдвое».

Второй вариант будет понятнее руководству, потому что бизнес говорит на языке денег.

Bonus reason: когда узнаете стоимость потерь, сможете рассчитать ROI трудозатрат на технические работы и выделить ресурсы на команды, которым это необходимо. 

Как мы оформляем инцидент

Процесс обработки инцидента

Процесс обработки инцидента

Как спроектировать модель управления инцидентами

Разработкой модели тоже занимается наша команда (да, не мониторингом единым живы аналитики в Skyeng).

Мы работаем по такой схеме:

  1. Формируем набор сценариев.

Сценарии инцидентов — это самые частые технические сбои. В зависимости от компании их набор будет меняться, и даже внутри одного бизнеса инциденты могут иметь разную специфику. 

  1. Определяем триггеры того, что тот или иной сценарий сработал.

  1. Рассчитываем потери для каждого сценария.

Этап 1. Создание набора сценариев

Для этого нужно изучить CJM пользователя и понять, как и в какой момент сбои могут повлиять на пользовательский опыт.

В нашем случае основные сценарии такие:

  • сорванные уроки;

  • сорванные вводные уроки;

  • недополученные оплаты;

  • недополученные регистрации;

  • несостоявшиеся дозвоны;

  • сорванные маркетинговые коммуникации.

Реализовывать свой набор сценариев можно в итеративном режиме — начиная с самых дорогих и часто встречающихся.

Этап 2. Определение триггеров

Чтобы распределить инциденты по сценариям, можно:

  • маркировать сценарии для каждого инцидента вручную в задаче в Jira;

  • добавить триггер сценария в модель.

Первый вариант проще, но у него есть ряд минусов:

  1. Придётся вручную маркировать сценарии для всех случившихся инцидентов (во втором варианте они промаркируются автоматически).

  2. Появится bias на «экспертную» оценку: не всегда очевидно, какой сценарий задействован в инциденте.

Поэтому мы пошли по второму пути. 

Выбор метрики для триггера

Рассмотрим на примере сценария «Сорванные уроки» на платформе Skyeng. 

В этом сценарии мы предполагаем, что из-за инцидента какая-то часть уроков была сорвана. Для триггера логично взять метрику «Доля успешных уроков».

Динамика изменения доли успешных уроков по дням за полгода

Динамика изменения доли успешных уроков по дням за полгода

Нормированная диаграмма успешных и неуспешных уроков по дням за один месяц

Нормированная диаграмма успешных и неуспешных уроков по дням за один месяц

Чтобы выявить задетые сценарием инциденты, нужно сравнить выбранную метрику в период инцидента и в предыдущий период (без инцидента). 

Но на графике мы видим, что на эту метрику сильно влияет сезонность. Показатели меняются в зависимости от:

Как компенсировать сезонность, расскажу дальше.

Выбор интервалов для сравнения

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

Для начала определим временные интервалы для инцидента — здесь пригодятся наши кастомные поля в Jira.

Временные интервалы инцидента

Временные интервалы инцидента

Текущий период

  • Во время инцидента (интервал равен длительности инцидента): этот период захватывает уроки, которые должны были начаться во время инцидента.

  • После инцидента (интервал 30 минут): может захватить уроки, которые были перенесены из-за инцидента, — было неясно, когда платформа заработает.

Предыдущий период

Анализируем аналогичные временные интервалы за 7, 14, 21, 28 дней до инцидента

Интервал в 7 дней выбираем по умолчанию, чтобы недельная сезонность не влияла на показатели. А 4 предыдущие недели берём, чтобы усреднить значения и получить более «чистую» метрику до инцидента. Если брать только одну неделю, в ней может быть случайное отклонение.

Основные критерии для выбора предыдущего периода:

  • День недели инцидента должен совпадать с днём недели предыдущего периода.

  • Время и длительность каждого интервала в день инцидента должны совпадать с временем и длительностью каждого интервала в предыдущий период.

  • День предыдущего периода не должен быть праздничным. Праздники влияют на все метрики, поэтому неделя с праздничным днём исключается из выборки. 

Статистическая значимость изменения метрики до и во время инцидента

Мы пробовали определить её двумя способами.

❌ Использовали фиксированные пороги для выявления триггера.

Например, считали, что сценарий активирован, если метрика отклонилась на 3 процентных пункта. Но в этом случае нарушался баланс между чувствительностью и точностью модели:

  • Сложно фиксировать убытки для немасштабных инцидентов.

  • Случается много ложных срабатываний триггера.

✅ Применили Критерий хи-квадрат Пирсона.

Почти весь ETL у нас написан на SQL, поэтому мы создали функцию для расчёта хи-квадрат Пирсона двух выборок:

CREATE FUNCTION chi_square(group_a_target_cnt integer, group_a_total_cnt integer, group_b_target_cnt integer, group_b_total_cnt integer)
RETURNS float AS $$
DECLARE expected_group_a_target_cnt float;
DECLARE expected_group_b_target_cnt float;
DECLARE expected_group_a_non_target_cnt float;
DECLARE expected_group_b_non_target_cnt float;
BEGIN
    expected_group_a_target_cnt = group_a_total_cnt * (group_a_target_cnt + group_b_target_cnt)::float / nullif((group_a_total_cnt + group_b_total_cnt),0);
    expected_group_b_target_cnt = group_b_total_cnt * (group_a_target_cnt + group_b_target_cnt)::float / nullif((group_a_total_cnt + group_b_total_cnt),0);
    expected_group_a_non_target_cnt = group_a_total_cnt * ((group_a_total_cnt - group_a_target_cnt) + (group_b_total_cnt - group_b_target_cnt))::float / nullif((group_a_total_cnt + group_b_total_cnt),0);
    expected_group_b_non_target_cnt = group_b_total_cnt * ((group_a_total_cnt - group_a_target_cnt) + (group_b_total_cnt - group_b_target_cnt))::float / nullif((group_a_total_cnt + group_b_total_cnt),0);
    RETURN power(group_a_target_cnt - expected_group_a_target_cnt, 2) / nullif(expected_group_a_target_cnt,0) + power((group_a_total_cnt - group_a_target_cnt) - expected_group_a_non_target_cnt, 2) / nullif(expected_group_a_non_target_cnt,0) + power(group_b_target_cnt - expected_group_b_target_cnt, 2) / nullif(expected_group_b_target_cnt,0) + power((group_b_total_cnt - group_b_target_cnt) - expected_group_b_non_target_cnt, 2) / nullif(expected_group_b_non_target_cnt,0);
END; $$
LANGUAGE plpgsql;

На вход в эту функцию подаём переменные из таблицы сопряжённости, а на выводе получаем значение хи-квадрат Пирсона.

Критерий

До инцидента

Во время инцидента

Успешные уроки

500

100

Неуспешные уроки

40

10

Всего уроков

540

110

Пример таблицы сопряжённости для сценария «Сорванные уроки».

Преимущества подхода

Имея эту функцию и таблицу сопряжённости, легко:

  • посчитать p-value;

  • определить, была ли статистически значимая разница между метриками. Таблица критических значений хи-квадрат лежит здесь (в нашем примере число степеней свободы равно одному).

Недостатки подхода

Из-за сезонности метрики данные могут быть некорректными. Например, хи-квадрат Пирсона может показать статистически значимые различия метрики в сравнении с прошлыми периодами, если инцидент произойдёт:

Чтобы этого избежать, можно использовать статистический инструмент Casual Impact. Он позволяет спрогнозировать, как менялась бы метрика, если бы инцидент не случился. Тогда мы сможем сравнить этот прогноз с фактическим значением, оценить, повлиял ли инцидент на нашу метрику, и построить на этом триггер для сценария.

Этап 3. Расчёт потерь для сценария

Возьмём тот же сценарий «Сорванные уроки» и определим переменные для расчёта.

Переменная

Описание

diff

Разница долей успешных уроков до и во время инцидента

total

Общее количество уроков во время инцидента

price

Средняя цена урока во время инцидента

Формула прямых потерь:

DirectLosses = diff * total * price

В этом сценарии есть компенсационные потери: при технических неполадках ученик может запросить компенсацию в виде бонусных уроков или денег.

Чтобы рассчитать компенсационные потери, берём все проведённые бонусные уроки и компенсационные выплаты за период [начало инцидента 24 часа после завершения инцидента].

CompensatoryLosses = MoneyRefunds + BonusClasses * price

Общая формула потерь:

Losses = DirectLosses + CompensatoryLosses

Важно рассчитывать потери для каждого сценария в одной метрике. Для сценария с уроками потери будут в GMV, а для сценария с оплатами — в Cash-In. Мы приводим все метрики в GMV, чтобы потом можно было их суммировать и понять общий эффект от инцидента.

Точность модели

Оценить точность суммы потерь — нетривиальная задача. Но понять, правильно ли сработал триггер, вполне возможно. 

Не все инциденты приносят потери, так как это может быть:

а) внутренний инцидент;

б) инцидент, который не касается пользовательского опыта.


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

Точность модели составила 85%. Для инцидентов, в которых сценарий сработал по ошибке, можно вручную обнулить стоимость в задаче в Jira. Статистика обновится на итоговом дашборде.

Применение

Когда вы настроите модель расчёта и управления инцидентами:

  • поймёте масштаб влияния инцидентов на финансы компании;

  • сможете ставить для команд разработки реалистичные таргеты по проработке техдолга;

  • сможете рассчитывать ROI трудозатрат на технические работы и выделять ресурсы на команды, которым это необходимо.

Результат — экономия финансовых и человеческих ресурсов.

А как вы работаете с инцидентами? Делитесь кейсами в комментариях, обсудим!

© Habrahabr.ru