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

Hourly vs monthly: как считать стоимость в смешанных командах

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

Финансовый лид команды из 12 разработчиков открывает дашборд расходов. Общий burn за месяц: $58 000. Четверо в штате на месячном окладе, пятеро контракторов на почасовой ставке, ещё трое работают через вендора с месячным инвойсом. Дашборд показывает одно среднее значение стоимости разработчика. Это неверная цифра, и каждое решение по cost-per-feature, построенное на ней, тоже неверное.

Стандартное решение — конвертация 160 часов: делим месячную ставку на 160, получаем «эквивалентную часовую» и сравниваем. По данным US Bureau of Labor Statistics, средний работник реально отрабатывает 1 791 час в год. Это 149 часов в месяц, не 160. В Казахстане 24 рабочих дня отпуска плюс 13 праздничных дней дают эффективную цифру ближе к 144 часам. 160 — это наследие, которое уже не соответствует данным даже в стране-источнике.

{/* truncate */}

Эта статья для финансовых лидов в смешанных командах (штат, почасовые контракторы, аутсорсинг через вендора), которым нужен один cost view, который не врёт о том, какая группа реально дороже.

Почему один тип ставки ломает смешанные команды

Если форсировать всех на почасовку, штатные сотрудники в месяцы отпуска выглядят искусственно дешёвыми. Если на месячную ставку — контрактор, который отработал 80 часов, выглядит так же, как тот, кто отработал 180. Оба варианта прячут сигнал, который нужен финансам.

Отчёт International Labour Organization 2023 Working Time and Work-Life Balance количественно фиксирует разрыв: контракторы через агентства в среднем биллят на 15-22% часов меньше в месяц, чем эквивалентные штатные сотрудники, если учитывать неоплачиваемое время, переходы между задачами и ramp-up. Cost-модель смешанной команды, которая это игнорирует, — не cost-модель. Это догадка в обёртке таблицы.

Stack Overflow Developer Survey 2024 подтверждает паттерн. Компенсация независимых контракторов, нормализованная на 40-часовую неделю, на 23-31% выше, чем у штата в той же стране. Но контракторы биллят только то, что отработали. Отпуск не оплачивается, праздники не оплачиваются, больничные не оплачиваются. Годовая стоимость в итоге оказывается ближе, чем подсказывают часовые ставки. С одним типом ставки этого не видно.

Два типа ставки и для кого они подходят

PanDev Metrics моделирует это через таблицу UserRate: у каждого пользователя может быть одна или несколько ставок, и каждая несёт rateTypeId, ссылающийся на строку в user_rates_type. Сегодня доступны два значения: HOURLY и MONTHLY. Ниже decision-matrix, по которой мы работаем с клиентами.

Тип ставкиКому подходитПочему
HOURLYНезависимые контракторы, агентский персонал, on-demand специалистыБиллит ровно отработанные часы. Отпуска не искажают cost. Легко сверять с инвойсами.
MONTHLYШтатные сотрудники с фиксированным окладомКорректно отражает реальную cost-of-employment независимо от отпусков, больничных, ramp-up. Совпадает с payroll.
MONTHLY (вендор)Аутсорс через вендорский договорВендор выставляет месячный инвойс за seat. Передаём как monthly, чтобы cost совпадал с тем, что вы реально платите вендору.
HOURLY для штатаПочти никогдаНаказывает компанию за каждый праздник и больничный. Прячет реальную cost-of-employment.
MONTHLY для контракторовПочти никогдаПрячет факт, что контрактор отсутствовал две недели. Cost выглядит идентично busy-месяцу.

Дело не в том, какой тип лучше. Дело в том, что в одной команде нужны оба одновременно, и cost-система должна это поддерживать без сведения вручную в Excel.

Как PanDev сводит ставки

В модели данных каждая строка UserRate имеет rate, rateTypeId, startPeriod, endPeriod. У пользователя может быть несколько ставок во времени. Контрактор, перешедший в штат в середине квартала, имеет две перекрывающиеся записи: одна закрывается, другая открывается. Отчёты не теряют историю, потому что используется ставка, активная в период расчёта.

Правило согласования, которое платформа применяет при сравнении cost между типами:

if rateTypeId == HOURLY:
cost = rate * tracked_hours
elif rateTypeId == MONTHLY:
cost = rate # за весь период
hourly_equivalent = rate / 160 # только при сравнении часовой стоимости

Вид /dashboard/finances на уровне департамента — место, где это работает. Он показывает общий burn за период, разбивку по типу ставки и колонку «нормализованная стоимость часа», выведенную из формулы выше. Захардкоженное число 160 — это константа конвертации, и она же является лимитом этого подхода (об этом ниже).

Тип ставки пользователя поступает в reconciliation проектной cost и далее в дашборд департамента Тип ставки определяет, как cost доходит до проектного view. Дашборд не смешивает формулы. Reconciliation срабатывает только при сравнении часовой стоимости между типами.

Worked example: команда из 12 человек, $58K burn в месяц

Вот команда с теми же ставками, что в нашей референсной выборке:

#РольТипСтавкаЧасыCost / месяц
1-4Штатные инженерыMONTHLY$7 000 каждый168 avg$28 000
5-9КонтракторыHOURLYmix $45-$140/ч142 avg$16 800 (см. разбивку)
10-12ВендорMONTHLY (vendor)$4 400 каждый165 avg (по отчёту вендора)$13 200

Строка с контракторами — там, где математика становится интересной. Пул смешан: senior $140/ч, три mid в районе $80/ч, junior $45/ч. Часы биллят в среднем по 142 на контрактора в месяц, но взвешенная средняя ставка ниже заголовочных $80/ч после учёта джунов. Реальный месячный счёт от контракторов: $16 800 на пятерых. Плюс $28K штата и $13,2K вендора равно $58K total. Сходится.

Теперь два cost-view:

ViewРасчётЧто показывает
Наивный single-rate$58 000 / 12 = $4 833 на человека в месяц«Средний разработчик стоит $4,8K. Можем ли позволить ещё одного?» Вводит в заблуждение. Ничего не говорит про разбивку.
PanDev dual-rate$28K штат (4) = $7K/чел/мес; $16,8K контракторы (5) = $3,4K/чел/мес billed, но $80/ч в среднем; $13,2K вендор (3) = $4,4K/чел/месКонтракторы — 29% общего burn, но всего 28% часов, и разрыв в почасовой стоимости между штатом ($7 000 / 160 = $43,75/ч) и контракторами ($80/ч) составляет 83%.

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

Функция материализованного view f_mv_activity_total_user_daily_today в PanDev отдаёт это ежедневно: live cost на человека, joined по активной ставке, сегментированный по типу. Цифра, которая попадает на экран CFO в 9 утра, согласована за ночь, а не собрана в Excel перед митингом.

Где ломается конвертация 160 часов

Константа 160 захардкожена в правиле MONTHLY → hourly. Для большинства рынков US и APAC она работает. Для трёх категорий она тихо искажает реальность:

  • Франция (35 ч/нед по закону): 35 × 52 / 12 = 151,7 ч/мес. Француз с окладом €5 000/мес стоит €32,96/ч при 152, а не €31,25/ч при 160. Недооценка реальной стоимости на 5,5%.
  • Казахстан с полным отпуском: 24 рабочих дня отпуска + 13 праздников из 250 рабочих дней в году дают эффективные 144 ч/мес. Месячная ставка ₸600 000 — это ₸4 167/ч при 144, а не ₸3 750/ч при 160. Разрыв 11%.
  • Senior в onboarding: новичок на $9 000/мес не выдаёт 160 часов output в первый месяц. Ставка корректна, но cost-per-feature в окне ramp-up искажается.

Это honest limit. Текущая модель PanDev использует одну константу 160. Override по юрисдикциям — в roadmap; сегодня клиентам во Франции или KZ-командах с полным отпуском нужно либо принять небольшое занижение, либо вручную задать кастомную часовую ставку. Лучше об этом сказать прямо, чем делать вид, что константа универсальна.

Migration path: переход существующей команды на dual rates

Если сейчас все на одном типе ставки, переход на dual занимает один квартал аккуратной перерегистрации. Ретроактивный recalc делает остальное.

  1. Аудит текущих ставок. Выгрузите всю таблицу UserRate или её эквивалент в табличке. Для каждого человека решите: контрактор по часам или сотрудник на окладе?
  2. Установите endPeriod на всех текущих ставках в дату cutover миграции. Не удаляйте — история нужна для ретроактивных отчётов.
  3. Добавьте новые строки ставок с правильным rateTypeId и startPeriod, совпадающим с cutover.
  4. Запустите OverheadCoefficientFullRecalcCronJob (admin action), чтобы пересчитать каждый зависимый отчёт: cost-per-feature, project burn, дашборды департаментов. Recalc проходит по всей истории с новыми ставками.
  5. Сверьте на финансовом view департамента, что сумма совпадает с payroll + инвойсы контракторов за прошлый месяц. Если нет — не хватает ставки, обычно это вендорский seat, который никто не моделировал.

Этот же механизм защищает от худшего сценария финансов: ретроактивной коррекции. Если через три месяца обнаружили, что у контрактора была неверная ставка, поправили startPeriod строки ставки, дёрнули recalc — и каждый отчёт от этой даты вперёд показывает корректное число. Без раскручивания таблиц.

Когда подход не подходит

Два случая, в которых dual-rate модель — больше friction, чем пользы:

  • Команда из 4 или меньше, все одного типа найма. Если все в штате или все контракторы, одного типа ставки хватит. Reconciliation окупается только на смешанной команде.
  • Чисто агентская модель, где агентство выставляет один общий счёт в месяц. Если разбивка по контракторам недоступна, моделируйте всё агентство как одного синтетического «пользователя» с месячной ставкой. Dual-rate тут ничего не даёт.

Модель построена под смешанный кейс, потому что именно там математика самая сложная. Если команда однородна — игнорируйте сложность.

Похожие материалы


Самый жёсткий тезис из всего сказанного: если команда смешанная, а финансовый дашборд показывает одно число, это число прячет, какая группа реально дорогая. Выбирайте тип ставки, который соответствует контракту, а не таблице, которая у вас уже есть.

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

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

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