ФИНАНСОВАЯ ОБЛАСТЬP1L3

Распределение затрат: пул, драйвер и сохранение суммы

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

Справочный материал. Контракты данных, архитектурные решения и ограничения применения. Источники приведены в материалах, где используются внешние основания.

Задача и границы#

Нужно распределить общий пул затрат между центрами затрат в управленческой витрине, сохранив исходную сумму и объяснив долю каждого получателя. Распределение создаёт производный факт, а не меняет исходные проводки. Рассматривается один этап с неотрицательной базой драйвера; взаимные услуги, циклические распределения, валютная конвертация и формирование бухгалтерских проводок исключены.

Это предлагаемый проектный контракт со статусом knowledge. Выбор драйвера и допустимость распределения утверждаются владельцем финансовой методологии. Алгоритм не является требованием IFRS, не описывает опыт автора и не реализован в лаборатории. Денежная точность объясняет масштаб и округление, а отображение счетов - классификацию без весов; здесь главное - состав пула, база получателей и воспроизводимость распределения.

Зерно и контракт входов#

Зерно пула - одна сумма по юридическому лицу, регистру, периоду, категории затрат и валюте в выбранной версии входных данных. pool_id идентифицирует эту группу, а pool_version_id - её неизменяемый снимок. Каждая выбранная исходная строка должна иметь определённую принадлежность пулу; случайное включение одной строки дважды запрещено. Сохраняются ссылки на версии исходных строк и правило отбора.

Сумма P знаковая: в этом контракте затраты положительны, их уменьшение отрицательно. Единица - целая копейка RUB с масштабом 2 только для синтетического примера. Другие валюты и масштабы образуют отдельные пулы. Смешивать валюты или распределять сумму после неявной конвертации нельзя.

Зерно драйвера - один получатель внутри pool_version_id и driver_snapshot_id. Ключ получателя включает юридическое лицо и стабильный идентификатор центра затрат. Драйвером могут быть часы обслуживания, площадь или количество операций, но все веса одного пула измеряются в одной закреплённой единице. Десятичные веса переводят точно в целые единицы общего масштаба, например часы в сотые часа; отрицательные, отсутствующие и нечисловые веса отклоняют. Нулевой вес допустим и даёт нулевую долю.

business_date исходных строк определяет период затрат; observed_at и cutoff определяют доступные версии. Отдельно фиксируют период измерения драйвера, cutoff его снимка, версию методики и состав допустимых получателей. Время запуска не заменяет эти параметры. Новая версия драйвера создаёт новый расчёт, а не переписывает предыдущий выпуск.

Подготовка пула и получателей#

Сначала отбирают версии исходных строк и агрегируют сумму до зерна пула. Драйвер независимо приводят к одной строке на получателя. Затем проверяют покрытие: каждый ожидаемый получатель присутствует ровно один раз; лишние и отсутствующие ключи требуют решения до расчёта. Соединение детальных проводок с детальными измерениями драйвера до агрегации способно размножить обе меры.

Идентичность расчёта задаёт набор (pool_version_id, driver_snapshot_id, policy_version_id). Выходное зерно - этот набор плюс ключ получателя. run_id обозначает попытку исполнения, но не новую экономическую версию результата. В витрину входит только один выбранный успешный выпуск для заданного контекста отчёта.

При сумме весов W = 0 распределение не определено, даже если P = 0. В предлагаемом контракте расчёт блокируется и сохраняется диагностический статус; его нельзя выдавать за успешный результат из нулей. Альтернативы - оставить сумму в отдельном нераспределённом пуле или применить резервный драйвер - требуют явной версии политики и отдельного контроля. Отсутствующий вес нельзя автоматически подменять нулём.

Алгоритм целых долей#

При W > 0 распределяют модуль A = abs(P). Для каждого целого веса w_i вычисляют q_i = floor(A * w_i / W) и остаток r_i = A * w_i - q_i * W. Затем R = A - sum(q_i) единиц добавляют первым R получателям по убыванию r_i. При равенстве используют возрастающий стабильный ключ с закреплённым порядком сравнения, независимым от локали. Знак P возвращают после распределения модуля.

Псевдокод, не исполняемый SQL или Python:

Validate immutable inputs, unique recipient keys and nonnegative integer weights.
W := sum(weights); if W = 0: stop with a diagnostic status.
A := abs(P)
For each recipient i: (q_i, r_i) := integer quotient and remainder of A*w_i by W.
R := A - sum(q_i)
Sort recipients by remainder descending, then stable key ascending.
Add one unit to q_i for each of the first R recipients.
Restore the sign of P; retain zero shares and the allocation audit fields.
Check all invariants; publish the complete result as one selected release.

Так как 0 <= r_i < W, получаем 0 <= R < n, где n - число получателей. Если остаток есть, положительных остатков достаточно для его распределения; нулевой вес не получает добавочную единицу. Каждая доля отличается от точной пропорции менее чем на одну минимальную единицу, а сумма долей точно равна P. Это свойства данного алгоритма, а не допуск финансового стандарта.

Операции частного и остатка описаны в документации Python 3.14. В реализации нужно исключить переполнение произведения A*w_i. В PostgreSQL 18 точный numeric отличается от неточных floating point типов; запись в numeric(p,s) может округлить значение к масштабу столбца. Документация PostgreSQL 18. Она подтверждает свойства типов, но не проверку этого алгоритма на PostgreSQL. Здесь приведён только псевдокод; исполнительных тестов распределения не заявлено.

Синтетический пример и альтернативы#

Пул равен 100 копейкам RUB, получатели A, B, C имеют веса 1, 1, 1. Ключи сравниваются в порядке A < B < C.

Получатель Целая доля q Остаток r при W = 3 Добавка Итог, копейки
A 33 1 1 34
B 33 1 0 33
C 33 1 0 33

При P = -100 те же веса дают -34, -33, -33; при P = 0 и положительном W все доли нулевые. Если веса меняются на 1, 2, 0, доли становятся 33, 67, 0: большую дробную часть получает B. Это заданные учебные числа, а не реальные данные.

Независимое округление долей может нарушить сумму пула. Отнесение всего остатка на заранее выбранного получателя проще, но способно сильнее отклонить его долю от пропорции. Метод наибольших остатков требует сортировки и стабильного правила равенства; один ключ может регулярно получать лишнюю единицу. Ротация приоритетов возможна только как другая версионируемая политика. При изменении суммы пула на единицу доля отдельного получателя не обязана возрастать, поэтому алгоритм не обещает монотонности.

Контроли и повторный расчёт#

Для каждой версии пула сохраняют исходную сумму, список получателей, веса, W, q_i, r_i, добавку, итог и версии правил. Контроль включает:

  • точное равенство sum(allocated_minor) = P отдельно по пулу, валюте и выпуску;
  • совпадение набора выходных ключей с закреплённым набором получателей, включая нулевые доли;
  • уникальность выходного зерна и отсутствие отрицательного веса;
  • нулевую долю при нулевом весе и соответствие знака ненулевой доли знаку пула;
  • тот же результат при перестановке входных строк и повторе с теми же версиями;
  • отказ при пустом наборе, нулевом знаменателе, дубликате ключа, неизвестной валюте или выходе за диапазон реализации.

Одного равенства сумм недостаточно: потерянный получатель может компенсироваться завышенной долей другого. Исходный пул и производные доли нельзя складывать в одном показателе затрат. При аналитическом переносе между центрами отдельно определяют снятие суммы с исходного центра; эта статья не задаёт проводки такого переноса.

Поздняя корректировка источника или драйвера создаёт новый снимок зависимых пулов и новый выпуск. Старый выпуск остаётся воспроизводимым. Частичный результат после сбоя не выбирают для отчёта; повтор той же идентичности расчёта не должен создавать двойные доли. Для закрытого периода возможность замены выпуска определяется политикой закрытия, а не алгоритмом распределения.

Связанные темы и вопросы#

Для происхождения долей нужны lineage показателя и версии входов; для выбора выпуска после cutoff - закрытие периода. Эта модель дополняет денежную точность и классификацию счетов, но не заменяет их контракты.

  1. Какие поля нужны, чтобы повторить распределение после изменения драйвера?
  2. Почему сохранение суммы не доказывает правильность состава получателей?
  3. Как отличить повтор запуска от нового экономического выпуска расчёта?

Источники и документация

  1. Python 3.14 - divmod ↗
  2. PostgreSQL 18 - Numeric Types ↗

Источники нужны для самостоятельной проверки. Примеры и интерпретации на сайте могут быть учебными упрощениями.