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

Variance analysis в инженерии: 5 причин, почему план уехал от факта

· 9 мин. чтения
Artur Pan
CTO & Co-Founder at PanDev

Открываете отчёт Plan-vs-Actual за Q3. План по инженерке — $1.8M. Факт — $2.34M. Расхождение — +30%. CFO хочет объяснение к пятнице.

Учебник советует: «разбирайтесь с любой строкой, где |факт − план| > 10%». На этом большинство разборов и заканчивается — и здесь же ломаются. У 30%-ного гэпа в инженерном бюджете минимум 5 разных причин. У каждой — своя сигнатура в данных. Если не декомпозировать variance, рискуете уволить PM-а, когда настоящий виновник — внеплановый раунд повышений зарплат в августе.

Variance analysis по CIMA разбирает расхождение как дерево: rate × volume × mix. С инженерным бюджетом сложнее — труд не однородный товар. Ниже версия, которая работает на реальных деньгах разработческих команд.

{/* truncate */}

Декомпозиция variance: 5 компонентов 30% перерасхода Q3 в инженерной организации на 40 человек Реальная разбивка: scope creep — крупнейшая статья, K seasonality — самая незаметная.

Хватит смотреть на разрыв. Начните называть причину.

Deloitte CFO Insights регулярно отмечают инженерку как категорию с худшей точностью прогноза. Причины две: cost-of-labor сезонит, и скоуп подвижен. 30% мимо по аренде — ошибка биллинга. 30% мимо по разработке — норма, если только вы не можете расписать каждый процент по причинам.

По ~40 mid-size B2B-командам, которые мы наблюдаем через модуль Plan-vs-Actual в PanDev Metrics, квартальное расхождение > 15% всегда раскладывается на пять повторяющихся причин. Частоты ниже — это как часто причина была главным контрибьютором перерасхода квартала.

Таблица 1 — 5 причин variance в инженерном бюджете

#ПричинаСигнатура в данныхГде смотреть в PanDevЧастота как #1Средняя доля
1Ретро-пересчёт ставокЧасовая ставка прошлых месяцев меняется задним числомAudit log loaded rate; история OverheadCoefficientFullRecalcCronJob8%10–15%
2Сезонный скачок KТа же FTE и часы, но cost-per-hour лезет вверх в отпускные месяцыГрафик OverheadCoefficient per-month28%20–30%
3Scope creepНовые issue_keys появляются после старта спринта; задача удвоиласьДельта issue-key в mv_activity_total_user_issue_daily35%30–45%
4Capacity overrun (FTE > 90%)Инженеры стабильно в красной зоне; следом — наймКрасные счётчики в employee-utilization-widget16%15–25%
5Внеплановые отсутствияБольничные / экстренные отпуска закрыты подрядчиками по более высокой ставкеOverride-ы в CustomEmployeeWorkingTime13%5–10%

Два вывода. Scope creep лидирует по частоте — что большинство CFO и так подозревают. Сюрприз — что K spike самая незаметная причина: её не видно в headcount-отчёте, потому что никого не нанимали и никто не уволился, а cost-per-hour тихонько вырос с 0.39 до 0.46 между июнем и августом.

Причина #1 — Ретро-пересчёт ставок (8%, сигнатура: ставки задним числом)

В августе подписан review зарплат с эффективной датой 1 июня. Внезапно loaded hourly rate за июнь и июль другие, чем были на прошлой неделе. Отчёты, запущенные в июле, выглядели аккуратно. Отчёты в сентябре показывают $80K дыру в Q2.

Это сценарий запуска нашего OverheadCoefficientFullRecalcCronJob — когда историческая ставка меняется, все зависимые финансовые метрики пересчитываются с нуля. Сигнатура однозначная: один и тот же запрос за один и тот же период даёт разные числа в двух прогонах. Если в variance-отчёте «двигается» прошлое — это ваша причина.

Лечение не аналитическое, а операционное: закрывать прошедшие периоды после month-end close, выделять ретро-повышения отдельной строкой и прямо называть пересчёт в комментарии к variance. Не прятать в шуме.

Подробнее о том, как это распространяется по cost stack, — в нашей статье retroactive rate change handling (следующая в серии).

Причина #2 — Сезонный скачок K (28%, сигнатура: cost вверх, часы на месте)

Overhead coefficient K — отношение оплачиваемого времени к закодированному — не константа. Лето его убивает. Декабрьские праздники в части рынков — тоже. Наурыз в СНГ — тоже. Математика разобрана в статье per-month K seasonality, и сигнатура у этой причины самая чистая: та же FTE, тот же объём задач, тот же темп поставки — но cost-per-feature растёт на 15–20%.

В разборе ниже K вырос с 0.39 в Q2 до 0.46 в Q3. Те же инженеры. Те же проекты. Зарплатный фонд почти не сдвинулся. Но каждый закодированный час теперь несёт на 18% больше overhead. На плане в $1.8M это уже примерно $130K — четверть всего variance из-за одного коэффициента.

Большинство планировочных инструментов не моделируют K помесячно. Excel-шаблоны используют годовое среднее — поэтому лето стабильно ломает бюджет, и никто не понимает, почему.

Причина #3 — Scope creep (35%, сигнатура: issue keys приходят с опозданием)

Очевидная причина, но обычно её меряют неправильно. Сравнивают plan story points vs. delivered story points. Правильное сравнение — запланированные issue keys vs. issue keys, которые когда-либо касались спринта. Наша materialised view mv_activity_total_user_issue_daily хранит ежедневный набор «трогавшихся» ключей — можно реплеить, какие задачи появились на 3-й день спринта, который скоупили на 0-й.

Два паттерна scope creep:

  • Bolt-on: новая фича, добавленная после kickoff. Видимая. Защитимая.
  • Stealth growth: уже запланированная фича, у которой acceptance criteria разрослись по ходу спринта. Невидимая ни в одном трекере без версионирования требований.

В наших данных stealth growth добавляет около 220 dev-hours на типичный план «1 квартал, 2 фичи». При loaded rate $100/час это $22K на фичу, $44K за квартал. В worked example ниже две фичи поглотили 220 незапланированных dev-hours, что добавило $220K к расхождению.

DORA State of DevOps пишет, что элитные команды быстрее не на задаче — у них меньше variance в cycle time. Та же логика и в бюджете: важна стабильность, а не пиковая скорость.

Причина #4 — FTE > 90% (16%, сигнатура: всплеск красной зоны)

Когда инженеры квартал сидят с утилизацией > 90%, через 90 дней происходят две вещи: люди уходят, и менеджеры начинают паниковать с наймом. Оба эффекта попадают в variance двумя кварталами позже, не сейчас. Лидирующий индикатор — счётчик красной зоны в employee-utilization-widget: инженеры выше 90% на протяжении 4+ недель подряд.

В наших данных на каждого инженера в красной зоне 6+ недель приходится 38% вероятности backfill или net-new hire в течение 90 дней. Один внеплановый найм на 40-персонной команде — это 1–2% годового бюджета. 4 человека в красной зоне → 2 найма → ~$90K непланового кост в Q3.

Честная оговорка: это лидирующий индикатор, а не объяснение текущего квартала. Когда он попадает в variance, ущерб уже состоялся. Виджет — еженедельно. Variance-отчёт — ежеквартально.

Причина #5 — Внеплановые отсутствия (13%, сигнатура: кластер working-time override-ов)

Один сломал ногу. Двое ушли в декрет на одной неделе. Команда закрывает дыру подрядчиком за $150/час против планового loaded rate $90/час. Каждый override в CustomEmployeeWorkingTime оставляет след — если кластеризовать их по месяцу, leave gaps выглядят совсем не как плановый отпуск.

Самая мелкая из пяти причин, но её стоит выделять, потому что её часто записывают в scope creep. Различающий признак: scope creep растит набор issue-keys, leave gap растит долю контракторских часов.

Worked example — декомпозиция 30% variance в Q3

Команда 40 человек, план Q3 $1.8M, факт $2.34M. Variance: $540K (30%). Атрибуция:

Таблица 2 — Атрибуция variance в примерном квартале

ПричинаМеханикаVariance ($)Доля от $540KСигнал детекции
Scope creep2 фичи поглотили 220 незапланированных dev-hours$220,00041%Issue-key дельта в mv_activity_total_user_issue_daily
K seasonalityK вырос 0.39 → 0.46 (июл–сен)$130,00024%Per-month K chart: скачок > 10% MoM
FTE > 90% → найм4 инженера в красной зоне → 2 найма$90,00017%Виджет утилизации: счётчик красной зоны
Ретро-ставкаАвгустовские повышения с 1 июня$80,00015%Дельта в audit log loaded rate
Внеплановые отсутствия1 контрактор подменил $150/час$20,0004%Override-ы в CustomEmployeeWorkingTime
Итого$540,000100%

Этот разбор меняет три вещи:

  1. Разговор с CFO перестаёт быть «инженерка перетратила» и становится «scope creep — 41%, K seasonality — 24%». Разные проблемы, разные владельцы, разные решения.
  2. Самая незаметная строка — $130K из-за K — никогда бы не всплыла в headcount-обзоре. Те же люди, те же часы, больше денег.
  3. Инстинктивное желание CFO «сократить штат» лечит причину #4 (15% variance), оставляя нетронутой более крупную причину #3 (41%).

Endpoint POST /departments/{id}/finance/projects в PanDev возвращает percentageChange по проектам — это первый срез: отсортированный список, куда смотреть в первую очередь. Декомпозиция выше — то, что вы делаете после того, как выбрали самый «двигающийся» проект.

Как вшить variance-алерты в месячный цикл

Variance analysis — post-hoc анализ. Правильное время — раз в месяц, а не раз в квартал. К моменту, когда Q3-обзор выпадает в начале октября, данные уже устарели на 90 дней, а люди, которые могли вмешаться в июле, занимаются другими пожарами. Настройте email-алерты variance раз в месяц, чтобы проблемы всплывали внутри квартала, а не после.

Чек-лист на закрытие месяца:

  • Закрыть ставки прошлого месяца. Ретро-правки только с пометкой в комментарии.
  • Посчитать K за закрытый месяц; сравнить с trailing 12-month средним. Флаг при отклонении > 5%.
  • Снять issue-key дельту по активным спринтам. Флаг, если спринт «распух» по issue-keys больше чем на 15% после kickoff.
  • Посчитать инженеров в красной зоне (> 90%) за месяц. Флаг при росте.
  • Список всех override-ов в CustomEmployeeWorkingTime за месяц. Сумма часов по контракторской ставке.

Весь цикл занимает меньше часа, если он инструментирован. По сравнению с квартальным post-mortem это разница между управлением и отчётностью.

Когда этот фреймворк не подходит

  • Меньше 10 инженеров: шум по каждой причине шире самой variance. Считайте суммарно, не по причинам.
  • Чисто проектный шоп / агентская модель: client billing и есть variance — scope creep становится событием выручки, а не затрат. Другая математика.
  • Тяжёлая contractor-mix (> 40%): причины #4 и #5 доминируют по построению; таблица перевешивается в сторону leave / overrun.
  • Первый квартал на новом трекере: ещё нет истории issue-keys, чтобы чисто детектить scope creep. Подождите полный квартал данных, прежде чем доверять атрибуции.

По теме

30% variance — это информация, а не провал. Провал — это закрыть квартал, не назвав, какая из пяти причин потратила ваши деньги.

Готовы увидеть метрики своей команды?

30-минутная персональная демонстрация. Покажем как PanDev Metrics решает задачи именно вашей команды.

Забронировать демо