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

Retroactive rate change: что происходит при ставке задним числом

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

VP of Engineering выходит после Q1-ревью и объявляет 8% повышение для 12 backend-инженеров с эффективной датой 1 марта. На календаре 18 мая. Три месяца отчётов уже улетели в борд со старыми ставками. У HR два варианта: сделать вид, что повышение начинается сегодня, или ретроактивно обновить март, апрель и май. Большинство engineering finance инструментов вынуждают выбрать первый. PanDev Metrics поддерживает второй, и Sarbanes-Oxley Act 2002 — причина, почему делать это нужно очень аккуратно.

Это одна из немногих областей, где наш продукт реально расходится с LinearB, Jellyfish и Code Climate Velocity. Те инструменты построены на forward-only моделях ставок. Таблица UserRate в PanDev битемпоральная: у каждой ставки есть startPeriod и endPeriod, и OverheadCoefficientFullRecalcCronJob переигрывает activity-события через новую ставку × overhead K, когда исторические строки меняются. Это мощно. И это ровно та функциональность, на которую аудиторы смотрят дважды.

{/* truncate */}

Каскад retroactive rate change: обновление UserRate триггерит пересчёт OverheadCoefficient, replay cost-отчётов и запись в audit log Полный каскад от одной строки UserRate с датой в прошлом. Все зависимые cost-строки переигрываются.

Почему большинство инструментов отказываются это делать

Cost-модуль LinearB использует месячный snapshot. Как только февраль закрыт, ставка, активная на момент построения отчёта, замораживается. То же у allocation-движка Jellyfish. Их логика обоснована: если разрешить редактировать историю, можно отредактировать «выход» из плохих чисел.

Проблема в том, что реальная компенсация не уважает дату закрытия. Циклы salary review идут квартально или раз в полгода, потом аппрувы стопкой лежат в HR, потом новая ставка коммуницируется через несколько недель с ретроактивным эффектом. WorldatWork 2024 Salary Increase Survey сообщает, что 62% американских компаний делают retroactive effective date минимум на одном merit cycle в год, со средним смещением 47 дней. Если ваша finance-система это не моделирует, ваши «фактические данные» систематически неверны на четверть квартала — каждый квартал.

Дизайн PanDev сделан так, чтобы ретроактивность была нативной, а audit trail — громким. Вы можете менять историю. Каждое изменение timestamp-нуто, привязано к пользователю и видно в панели версий ставки. SOX Section 404 требует «internal controls over financial reporting» — это ровно то, чем audit log и является.

Как UserRate устроен внутри

У каждого пользователя список строк ставок в таблице UserRate:

user_rate (
id,
user_id,
department_id,
amount,
rate_type ENUM('HOURLY', 'MONTHLY'),
start_period DATE,
end_period DATE, -- nullable; NULL = текущая
created_by,
created_at
)

У сотрудника с одной MONTHLY-ставкой $5,000 от 1 января 2026 одна строка с start_period = 2026-01-01, end_period = NULL. Когда HR заводит новую ставку с 1 марта на $5,400, PanDev атомарно делает две вещи:

  1. Закрывает существующую строку — end_period = 2026-02-28.
  2. Вставляет новую строку с start_period = 2026-03-01, end_period = NULL, amount = 5400.

Если новая строка датирована в прошлом относительно сегодняшнего дня, система помечает её как retroactive insert и ставит в очередь OverheadCoefficientFullRecalcCronJob для затронутых (department_id, year, month).

Каскад пересчёта, шаг за шагом

Когда cron подхватывает задачу пересчёта, цепочка отрабатывает механически и исчерпывающе.

Таблица 1 — Что и где меняется при retroactive rate

ШагСущность пересчитываетсяЗачем
1История UserRate закрыта/вставленаНовая строка отражает ретро-повышение
2OverheadCoefficient per (dept, year, month)taskTotal и nontaskTotal растут, потому что direct dev cost вырос
3mv_cost_per_task_daily materialized viewLoaded cost каждой задачи переигрывается через новую ставку × новый K
4mv_cost_per_feature_monthly materialized viewFeature-rollups обновляются из обновлённых cost-задач
5Department / project cost-отчётыАгрегации обновляются из нового состояния MV
6Plan-vs-Actual variance line RETRO_RATE_DELTAVariance декомпозируется, чтобы ретро-изменение не загрязняло scope-creep
7Запись в audit logUser, timestamp, before/after rate, dollar delta по каждому затронутому месяцу

Пересчёт идемпотентен: повторный запуск на том же диапазоне даёт те же числа. Это non-negotiable свойство для SOX, где аудиторы должны воспроизвести цифры из raw-событий.

UI: где HR это реально делает

Поток /dashboard/employees/salary-rate/:employeeId. Панель истории показывает все предыдущие строки ставок. Диалог «New Rate» спрашивает четыре вещи: эффективная дата, сумма, тогл HOURLY/MONTHLY и обязательное текстовое обоснование. Перед confirm диалог рендерит preview impact: сколько уже финализированных месяцев будет затронуто, dollar delta на taskTotal и были ли какие-то из этих месяцев «locked» finance-закрытием.

Последний пункт — ключевой контроль. Месяцы в статусе LOCKED_PERIOD (выставленном Finance после month-end close) требуют дополнительного аппрува: финансовая роль должна co-sign ретро-изменение. Дефолтный конфиг требует двух подписей на любое ретро, пересекающее границу locked-месяца. AICPA Statement on Auditing Standards 122 считает это правило двух человек базовым ожиданием для любого restatement workflow.

Worked example: ретро-повышение на 8%

Возвращаемся к VP of Engineering. 12 backend-инженеров, средний direct rate $42/час, 8% повышение с 1 марта, обработано 18 мая. K департамента: 0.41 в марте, 0.43 в апреле, 0.45 в мае (сезонность мы разбирали в per-month K).

HR заводит 12 новых строк UserRate с start_period = 2026-03-01. Cron ставит в очередь пересчёт (dept_id=engineering, 2026, 3..5). За ~90 секунд каскад завершается.

Таблица 2 — Cost-отчёт за 31 марта: оригинал vs replayed

Cost lineЗапуск 1 апреля (до ретро)Запуск 19 мая (после ретро)Delta
Direct dev cost (12 инженеров, март)$80,640$87,091+$6,451
taskTotal (март)$58,866$63,576+$4,710
nontaskTotal (март)$21,774$23,515+$1,741
Overhead K (март)0.4100.405-0.005
Loaded cost per task hour$59.22$63.74+$4.52
Total department cost (март)$113,702$122,562+$8,860
Total cost (март+апрель+май)$345,815$373,732+$27,917

Два момента. Первое: K немного упал, потому что adminSalary (CTOs, EMs, DevOps) не изменился, а taskTotal + nontaskTotal вырос — overhead стал меньшей долей увеличившегося знаменателя. Второе: total delta $27,917 — не сюрприз бюджету: это ровно 8% от direct cost тех 12 инженеров за три месяца плюс K-blended overhead reweighting. Цифра сходится.

CFO, запускающий cost-отчёт за 31 марта сегодня, получит $122,562. Тот же отчёт, запущенный 1 апреля, давал $113,702. Оба корректны. Audit log объясняет разницу одним предложением: «Retroactive rate increase 8% applied to 12 engineers (employee_ids 401–412) on 2026-05-18 by user a.pan@pandev.io

Как это связано с остальным cost-стеком

Каскад пересчёта работает только потому, что весь cost-движок битемпоральный. Если вы читали loaded hourly rate, знаете, что loaded rate = direct × (1 + K), а сам K зависит от тех же taskTotal/nontaskTotal, что только что пересчитались. Если читали overhead coefficient, знаете, что K — месячный per department, никаких усреднений.

Ретро-пересчёт уважает оба правила: каждый месяц получает свой K replay, каждый loaded rate перестраивается из нового direct rate. Поэтому это не могло быть простым шорткатом «умножить затронутые отчёты на 1.08». K сдвигается, когда сдвигается direct cost, поэтому распространение реально переигрывает event-данные, а не патчит итоги.

Где живёт audit trail

PanDev держит три слоя evidence для любого ретро-изменения ставки:

  1. Версионирование UserRate — старая строка остаётся со своим закрытым end_period; новая существует рядом. Можно спросить «какая ставка была у Марии 15 марта?» против любого исторического timestamp и получить ответ, который был корректен на тот момент.
  2. AuditLogEntry — append-only таблица с entity = 'UserRate', action = 'RETRO_INSERT', actor, before_state, after_state, dollar_delta_per_month, approver (если сработал dual-control).
  3. Variance attribution — Plan-vs-Actual отчёт добавляет отдельную строку RETRO_RATE_DELTA, чтобы ретро-изменение не смешивалось со scope creep или сезонными K-spike. Зачем эта строка нужна, разбирали в budget variance analysis.

Аудиторы при SOX 404 attestation обычно задают три вопроса: кто поменял число, когда и с чего на что. Три слоя выше отвечают на все три из одного и того же event log.

Чем это не является

Retroactive rate change — мощный инструмент, который можно использовать неправильно. Backdating ставок ради манипуляции квартальными цифрами — ровно тот сценарий, для отлова которого SOX и был написан. См. enforcement actions SEC по revenue restatement. Audit trail здесь ваш друг, не щит. Каждое ретро-изменение timestamp-нуто, attribut-но и видно любому с audit-ролью. Не используйте эту фичу, чтобы «причесать» плохие числа. Используйте, чтобы держать хорошие числа точными.

Несколько паттернов, которых не должно быть и которые система флажит:

  • Ретро-снижение ставки прямо перед расчётом контрактного бонуса.
  • Ретро-изменение, пересекающее границу финансового года без finance-аппрува.
  • Ретро-изменение на собственном аккаунте сотрудника от его же менеджера (self-approval заблокирован).

HR Technology Conference 2024 проводила панель отдельно по compensation transparency tooling, и консенсус панели — перефразированный из опубликованного recap — был, что видимость ретро-изменения важнее, чем его предотвращение. Людям иногда придётся датировать задним числом. Плохая версия — это невидимое backdating.

Honest limit: что это не чинит

Каскад пересчёта держит cost-отчёты внутренне консистентными. Он не чинит ретроактивно решения, принятые на старых числах. Если CFO презентовал Q1-результаты борду 15 апреля по pre-retro ставкам, эти числа были корректны на 15 апреля. Сейчас они устарели. Материалы для борда не перегенерируются. Это отдельная workflow — обычно addendum к следующему квартальному ревью с пометкой «Q1 dev cost restated upward by $19K following May salary review; restatement does not affect operating margin classification.»

PanDev даёт данные, чтобы написать этот addendum. Написать его за вас не может. И не может откатить board commitment, сделанный на числах, которые с тех пор были restated.

Финальная позиция, которую готов защищать

Если ваш engineering finance инструмент не поддерживает retroactive rate changes с полным audit propagation — у вас на самом деле нет engineering finance. У вас engineering bookkeeping с проблемой квартальных корректировок. Интересный вопрос не в том, разрешать ли ретро-изменения — реальность payroll их форсирует. Вопрос в том, производит ли система, которая их абсорбирует, аудит-готовую историю или тихо перезаписывает её. Выбирайте ту, что не делает вид, что прошлое неизменяемо.

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

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

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