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

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 — это наследие, которое уже не соответствует данным даже в стране-источнике.

Эта статья для финансовых лидов в смешанных командах (штат, почасовые контракторы, аутсорсинг через вендора), которым нужен один 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 решает задачи именно вашей команды.

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