КФО: дизайн change kfo-calculation-contract (#206)

Proposal и design: §8 «КФО», DBML v5.26, контракт API КФО под AURORA-1546/1547; ответы на вопросы проверки исполнителем.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Andrey Epifancev 2026-11-06 20:16:41 +04:00
parent 53b0a65ad2
commit 228af4e4d9
3 changed files with 338 additions and 0 deletions

View File

@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-11-06

View File

@ -0,0 +1,288 @@
# Design: расчёт КФО — правила §8, модель v5.26, контракт API
Change `kfo-calculation-contract`, issue RSHB/arm-ka-docs#206, ветка `feat/206-kfo-contract`.
Читатели результата — Цыганков (AURORA-1546, расчёт), Степанов (AURORA-1547, экран на фейке). Читают как постановку,
без созвона: документ должен отвечать на «что строить» сам.
## 1. Зачем
С 10.11.2026 в работу идут расчёт и экран КФО (Гант: разработка 10–16.11, тест заказчика 17–25.11). Сейчас у них нет
опоры: §8 описывает КФО по прежней редакции, в модели не хватает колонок по компании, контракта API нет. Цель —
к утру 09.11 дать три документа, по которым бэк и фронт стартуют параллельно и не расходятся.
Успех: Цыганков пишет расчёт по §8 и DBML, не спрашивая «а как тут»; Степанов строит экран по контракту на фейковых
данных, и экран без правок подключается к настоящему бэку.
## 2. Цели, не цели, анти-цели
**Цели**
- §8 «КФО» — полный алгоритм от форм и агрегатов до строки КФО, расхождений и порогов, с примерами.
- DBML v5.26 — всё, что нужно хранить, чтобы отдать экран из `kfo_result` без пересчёта.
- Контракт API КФО — чтение результата и сохранение ручных полей, md + OpenAPI 3.1.
**Не цели** — см. `proposal.md` «Non-goals» (код, ОФР-правила 1999, механизм пересчёта 1417, КФО Общий, подтверждение
клиента, экспорт, проверки переходов, новые поля версии).
**Анти-цели** (провал, даже если документы формально готовы)
- Решения заказчика выданы за принятые. Каждая развилка из «Что решить» DOC-274636 помечается: «на старте — вариант N,
решает заказчик».
- Сумма исключается дважды (автосумма + корректировка, обычный ББ-2 + гудвилл).
- Контракт, который экран не может отрисовать без своих расчётов: фронт не считает ни долей, ни итогов, ни порогов.
- Код роли или номер правила в бизнес-логике: поведение правила задаётся данными (`elimination_mode`), не `rule_no`.
## 3. Ограничения
- Источник истины по полям — DBML; полей, которых нет в v5.26, в §8 и контракте нет.
- Деньги — `numeric(18,2)`, в API — строки с двумя знаками; float нет.
- Производные консолидации не хранятся (DECISION-5); `kfo_result(_line, _line_company)` — хранимый результат версии,
это разрешено моделью; агрегаты ВГО по-прежнему считаются на запрос.
- Соглашения API — как в `dev/api-contract-corrections.md` §1 и `dev/api-contract-goodwill.md` §1 (префикс, права,
404 вне видимости, формат ошибок, порядок проверок правок, `editable` / `read_only_reason`).
- **Нумерация DBML.** В docs открыт PR #203 (v5.25, метрики OCR, AURORA-1759). Наша версия — v5.26. Если #203 слит
раньше нашего PR — строим v5.26 поверх v5.25. Если позже — v5.26 строим от v5.24 и в шапке пишем «v5.25 — PR #203»;
второй слитый PR перед merge вливает изменения первого в свой файл (номера не переписываются задним числом).
- Язык — русский, идентификаторы — английские.
## 4. Что решено
### 4.1 §8 «КФО» — содержание
Блок «КФО» §8 заменяется целиком; блоки «Корректировка», «Гудвилл», «Пороги», «Сравнение с КФО клиента» правятся
только там, где противоречат новому блоку.
**Единицы.** Результат КФО хранится и отдаётся в **тыс. руб.** с двумя знаками. Суммы ОСВ и агрегатов — в рублях,
переводятся в тысячи один раз на ячейке «в т. ч. взаимные» (компания × строка, см. «Округление»).
**1. Строки.** Бланк ББ и ОФР — полный вариант (`full`) той редакции, которую ядро выбирает по **отчётной дате версии**
(как §11.9 и словарь имён ядра: ≤ 31.12.2024 — `66n`, позже — `fsbu4`; `contracts/statement-code-orders.yaml`),
наименования — словарь имён ядра. Для «Русского бисквита» на 01.10.2024 это 66н (1120, 1231, 2930 есть, 1105, 1215,
2420 нет). Плюс служебная строка `GOODWILL` (п. 8). Все строки бланка выводятся, даже нулевые.
Строки форм компаний вне бланка в КФО не входят (предупреждение `LINE_OUTSIDE_BLANK`), строки без кода — тоже
(решение 17.10.2026). На старте — вариант черновика (DOC-274636 вопрос 1): только бланк + гудвилл; решает заказчик.
**2. «По данным клиента» (`client_amount`).** По каждой компании периметра (`consolidation_member`) — подтверждённый
(`CONFIRMED`) документ ББ / ОФР, привязанный к версии (`consolidation_document`). Колонка: ББ — на конец отчётного
квартала, ОФР — за период с начала года по конец квартала (по дате колонки, не по `is_current`; так же, как УК
гудвилла). Строки со статусом `ok`, повторы кода суммируются. Единицы формы — словарём единиц `arm_statements` →
тыс. руб.: «тыс. руб.» как есть, «руб.» / 1000, «млн руб.» × 1000, до двух знаков `ROUND_HALF_UP`.
**Знак строк расходов.** Формы разных компаний могут хранить расходы по-разному (§7, §11.9: скобки, минус или «без
знака»). КФО приводит строки расходов бланка — 2120, 2210, 2220, 2330, 2350 — к одной конвенции: `client_amount =
−|значение|`. Остальные строки — как в форме (1370, 2300, 2400 и прочие могут быть любого знака по существу).
- нет формы → значения компании 0, предупреждение `FORM_MISSING` (вариант 1 вопроса 3 черновика; решает заказчик);
- две и более подтверждённые формы одного типа → 0, `FORM_AMBIGUOUS`;
- единицы не распознаны → 0, `FORM_UNITS_UNKNOWN`.
КФО считается и при неполном комплекте (вопрос 17 черновика: вариант «считать, предупредить»).
**3. Агрегаты.** Источник взаимных сумм — агрегаты таблицы корректировок (§5 п. 2a, `VgoService`): тройка (правило,
компания 1, компания 2) с суммами сторон `amount_1`, `amount_2` (рубли) после ручных решений `vgo_assignment`.
Нераспределённые строки ни в один агрегат не входят и в КФО не исключаются (предупреждение `UNALLOCATED_ROWS` с числом
строк без отметки «не учитывать в КФО»). Тип отчётности агрегата — из правила. Отдельный обход ОСВ в КФО не делается:
то, что видит аналитик в таблице корректировок, и есть вход КФО.
**4. Исключения агрегата.** Каждый агрегат даёт список исключений `(компания, код строки, сумма в рублях ≥ 0)`.
Если у агрегата есть применённая корректировка (`correction.status = APPLIED`) — п. 5, иначе автосумма по режиму
правила (`elimination_rule.elimination_mode`):
| Режим | Автосумма | Куда |
|---|---|---|
| `min` (ББ 3–8) | `min(amount_1, amount_2)`, если обе стороны > 0 и отклонение в допуске (§8 «Корректировка», порог как в коде, OQ-178); иначе 0 | `article_1_line` у компании 1, `article_2_line` у компании 2 |
| `min_or_full` (ББ 2, новое) | обе стороны > 0 → `min(amount_1, amount_2)`; сторона 2 = 0 → `amount_1`; сторона 1 = 0 → 0. Порога нет (DOC-274230 правило 2) | компания 1 — `article_1_line`; компания 2 — каскад по кодам `article_2_line` |
| `manual` (ББ 1) | 0 — только корректировка | — |
| режимы ОФР | по правилам AURORA-1999 (§5); до неё агрегатов ОФР нет | по правилу |
**Каскад** (составная статья «a/b/c»): сумма снимается со строки `a` компании в пределах её доступного остатка
(«по данным клиента» строки в рублях минус уже снятое с неё другими агрегатами, но не ниже 0), остаток — со строки
`b`, затем `c`; что не уместилось — целиком на последнюю строку (DOC-274230: «если остаётся положительный остаток, он
исключается из строки 1370»). Агрегаты обходятся в порядке `rule_no`, затем ИНН компании 1, затем ИНН компании 2 —
порядок детерминирован. Каскад применяется и к составной статье в корректировке, если в ней код с «/» (в коде
корректировки код один, поэтому на практике — только автосумма правила 2).
Изменения seed правил ББ (в AURORA-1546, `arm-core/api/seed/core/elimination_rules_v1.yaml`): правило 2 —
`elimination_mode: min_or_full`, `article_2_line: "1310/1350/1370"`; правка версии 1 на месте, без новой версии
правил (п. 6 «Decisions»).
**Автосумма — одна функция для таблицы корректировок и КФО.** Сейчас `correction_source.py` знает только `manual` и
«остальное как `min`»; с `min_or_full` таблица корректировок показала бы правило 2 с порогом 5 % и односторонний
агрегат нулём, а КФО исключил бы иначе. Поэтому этот PR правит и §8 «Корректировка», и `dev/api-contract-corrections.md`:
у агрегата `min_or_full` `eliminated_amount` = автосумма по таблице выше, `exceeds_threshold = false` всегда, статус —
`AUTO` при обеих сторонах, `ONE_SIDED` с `eliminated_amount = amount_1` при пустой стороне 2. Код — в AURORA-1546:
автосумма агрегата выносится в одну функцию домена, её вызывают и `correction_source`, и расчёт КФО. Правило 1 остаётся `manual`: по DOC-274230 обе его суммы — у компании 2 (Кт 76.7.1 − Дт
76.7.9), а сторона агрегата устроена «компания 1 — дебет, компания 2 — кредит»; автосумма правила 1 — после
переработки seed (follow-up), сейчас только корректировкой.
**5. Корректировка агрегата заменяет автосумму.** Применённая корректировка агрегата отменяет его автосумму целиком
и не прибавляется к ней (DOC-274636 допущение 4; DOC-272613: сохранённые корректировки не меняют суммы сторон).
Исключения: компания 1 — статьи `debit[]` (основная + дочерние), компания 2 — `credit[]`, суммы как введены. Если
заполнены обе стороны и итоги сторон разные, каждая сторона исключает **меньший** итог (ответ Лысенко п. 8, §8
«Корректировка»): снимается со статей стороны по порядку — основная, затем дочерние в порядке ввода — до исчерпания.
Пустая сторона ничего не исключает, а другая — как введена (одностороннее правило 1, ОФР 1/2/3).
**6. «В т. ч. взаимные» по компании (`vgo_amount`).** Σ исключений компании по строке в рублях → тыс. руб., до целых
тысяч, половина — от нуля (`ROUND_HALF_UP` по модулю), со **знаком строки**: строки расходов (2120, 2210, 2220, 2330,
2350; после приведения п. 2 они ≤ 0) получают отрицательное `vgo_amount`. Так на всех строках
одно равенство: `cleaned_amount = client_amount − vgo_amount`, исключение всегда приближает значение к нулю (при
превышении — проходит через ноль). На старте — вариант 1 вопроса 8 черновика (округление ячейки); решает заказчик.
**7. Строка КФО.** По компании: `client_amount`, `vgo_amount`, `cleaned_amount`, `share_percent = cleaned /
bank_amount × 100` (хранится 4 знака, отдаётся 2; `bank_amount = 0` → `null`). По строке: `sum_amount = Σ client`,
`vgo_amount = Σ vgo по компаниям` (= `eliminated_auto + eliminated_manual`, где auto — от агрегатов без корректировки,
manual — от агрегатов с корректировкой; гудвилл-пары — в auto), `goodwill_adjust` (у `GOODWILL` и у итогов, куда он
входит), `bank_amount = sum_amount − vgo_amount + goodwill_adjust`.
**Итоговые строки** (1100, 1200, 1300, 1400, 1500, 1600, 1700, 2100, 2200, 2300, 2400, 2500): «по данным клиента» — итог
из формы компании, «в т. ч. взаимные» — Σ `vgo_amount` слагаемых по формуле бланка с их знаками (1100 ← 1110…1190;
1600 ← 1100 + 1200; 1700 ← 1300 + 1400 + 1500; 2100 ← 2110 + 2120; 2200 ← 2100 + 2210 + 2220; дальше по бланку);
очищенные — разность. **Гудвилл в итогах:** `goodwill_adjust` итоговой строки = Σ `goodwill_adjust` её слагаемых, то
есть `G` у 1100 и 1600; разреза по компаниям у этой части нет, поэтому `bank_amount` итога = Σ очищенных компаний +
`goodwill_adjust`, а доли компаний у 1100/1600 считаются от `bank_amount` и в сумме дают меньше 100 % на долю гудвилла.
Проверка сходимости: `bank(1600) = bank(1700)`, если сходятся формы компаний (у ГО −ФВ, у ДО −P, гудвилл +G, ФВ − P = G). Итог КФО совпадает с подписанным клиентом минус взаимные, а не с суммой своих строк
(вариант 2 вопроса 7 черновика; решает заказчик). Пример: 10 млн исключены из 2110 «Альфы» и 2120 «Беты» — у 2100
каждой компании `vgo` ±10 000, у строки 2100 КФО Σ `vgo` = 0, валовая прибыль группы не меняется.
**8. Гудвилл** (DOC-259025, §8 «Гудвилл»). Ветка гудвилла идёт **по паре ГО → ДО таблицы гудвилла, независимо от
агрегатов**: для пары с `use_goodwill` (действующая, отмечена, доля и комментарий, `G > 0`) при `goodwill_enabled` у
версии — исключения ГО `1170` на ФВ (как в таблице гудвилла — все строки 58.01 и потомков пары), ДО `1310` на P (без
каскада), `G` — в строку `GOODWILL`; агрегат правила 2 той же пары (компания 1 = ГО, компания 2 = ДО), если он есть,
**не применяется** — ни автосумма, ни корректировка. Если строку 58.01 этой пары аналитик перенёс в другое правило
или отметил «не учитывать», она всё равно входит в ФВ — предупреждение `GOODWILL_ROW_REASSIGNED`: ВГО по ней могут
исключиться дважды, решает аналитик. При `G ≤ 0` или выключенном гудвилле — обычный агрегат (п. 4–5). Строка `GOODWILL` —
раздел I, сразу после 1170, без разреза по компаниям, `goodwill_adjust = bank_amount = Σ G` применимых пар в тыс. руб.
(до целых тысяч); входит в итоги 1100 и 1600; `kfo_result.goodwill_total` — то же значение. Место строки: по DOC-259025
(R-1) — отдельная строка раздела I; макет `[31-04]` показывает «В т.ч. Гудвилл» под 1170 — расхождение в открытых
вопросах, решают методологи.
**9. Расхождения по статьям.** По агрегатам режима `min` (ББ и ОФР): `|amount_1 − amount_2|` — на статью стороны с
большей суммой (компания 1 — `article_1_line`, компания 2 — `article_2_line`), независимо от корректировок (вариант 1
вопроса 10 черновика; решает заказчик). Агрегаты `manual`, `min_or_full` и гудвилл-пары в расхождения не входят. По
строке — Σ в рублях → тыс. руб. до целых. `total_deviation_bb` / `total_deviation_ofr` — Σ по строкам своего отчёта.
`has_deviations` — хотя бы одна строка ≠ 0.
**10. Пороги** (ТЗ 6.8, DOC-274636 правило 8; величины — КФО банка, тыс. руб.):
- ББ: ЧА = строка 1300 (вопрос 11, вариант FNR03), ВБ = 1600. `ЧА ≥ 10 % ВБ` → база ЧА, предел 10 % ЧА; иначе (в том
числе ЧА ≤ 0) → база ВБ, предел 1 % ВБ.
- ОФР: `2200 ≥ 0` и `2200 / 2110 ≥ 10 %` → база 2200, предел 10 % 2200; иначе → база 2110, предел 1 % 2110. `2110 = 0`
→ база 2110, предел 0.
- Превышение — `total_deviation > предел` (равно — норма). Процент расхождения от базы — для сводки; база = 0 → `null`.
- Превышение хотя бы по одному отчёту → нужно обоснование (`threshold_justification`). Обязательность перед отправкой
на верификацию (вариант 2 вопроса 13) проверяет переход статуса — AURORA-2002 / 1414, не КФО.
**11. Реквизиты формы** (ТЗ 6.4, DOC-274636 правило 9): ГК, ГСК, отчётная дата, номер и статус версии, сценарий,
подход (`approach` — пакет документов), гудвилл учтён да/нет и сумма, расхождения ББ/ОФР и превышения, обоснование,
корректировки (число агрегатов с применённой корректировкой и Σ их исключений по типу отчёта), причины отклонений от
Методики (`consolidation_version.methodology_deviations`, ввод на вкладке), решение УОБ (номер, дата — только чтение).
«Подход по Методике» и «согласовано клиентом» — не делаем до ответа заказчика (вопросы 14, 15).
**12. Пересчёт и заморозка.** Результат хранится у версии (`kfo_result`, одна запись). Пока версия не заморожена,
чтение отдаёт результат по текущим данным версии: если входы изменились после `computed_at`, сервер пересчитывает и
сохраняет до ответа (как обнаруживать изменения — AURORA-1417). Кнопки «Рассчитать» нет (вариант 1 вопроса 16).
Комментарии строк и обоснование переносятся при пересчёте по (тип отчёта, код строки). При «Верифицирован»
`is_frozen = true` (ставит AURORA-2002) — дальше результат только читается.
**Предупреждения** — `{code, message, legal_entity_id?, statement_type?, count?}`, не блокируют расчёт: `FORM_MISSING`,
`FORM_AMBIGUOUS`, `FORM_UNITS_UNKNOWN`, `LINE_OUTSIDE_BLANK`, `UNALLOCATED_ROWS`, `INCOME_RULES_MISSING` (до 1999 —
агрегатов ОФР нет, взаимные обороты = 0), `GOODWILL_NOT_APPLIED` (гудвилл включён, а применимых пар нет),
`GOODWILL_ROW_REASSIGNED` (п. 8). Предупреждения и сводка корректировок (п. 11) — часть результата: **хранятся в
`kfo_result`** и пишутся при расчёте, чтобы замороженная версия отдавала ровно то, что видели при верификации.
§8 содержит сквозной пример: две компании, правила 2 (с гудвиллом и без), 6 (в допуске и с корректировкой), ОФР-пара
2110/2120, итоги 1100/1600/2100, пороги — числа сходятся до тысячи. Плюс прогон на «Русском бисквите» 01.10.2024
(эталон `Консолидация_Группа РБ.xlsx`) — как критерий 1546, с известными расхождениями: алгоритм по ТЗ (min, порог 5 %),
а эталон исключает каждую сторону полностью (OQ-155).
### 4.2 DBML v5.26
- `kfo_result_line_company`: **+ `client_amount numeric(18,2)`** («по данным клиента», тыс. руб.),
**+ `vgo_amount numeric(18,2)`** («в т. ч. взаимные», со знаком строки); `amount` — пояснение «очищенные показатели»;
`share_percent numeric(7,4)` — «4 знака хранения, в API 2». Строка `GOODWILL` разреза не имеет.
- `kfo_result_line`: пояснения по §8 — единицы тыс. руб.; `sum_amount` = Σ client; `eliminated_auto` / `eliminated_manual`
— от агрегатов без / с корректировкой, **не складываются по одному агрегату**; `vgo_amount = auto + manual`;
`goodwill_adjust` — только у `GOODWILL`; `consolidated_amount = sum − vgo + goodwill_adjust`; `deviation_amount` —
п. 9; `comment` ≤ 2000, переживает пересчёт; `source_kind` — всегда `balance` (ОСВ как источник — вопрос 3).
- `kfo_result`: пояснения порогов по п. 10, `goodwill_total` по п. 8, `is_frozen` — ставит переход «Верифицирован».
- `kfo_result`: **+ `warnings jsonb`** (массив предупреждений расчёта, п. 12), **+ `corrections_count_bb int`,
`corrections_count_ofr int`, `corrections_amount_bb numeric(18,2)`, `corrections_amount_ofr numeric(18,2)`** (число
агрегатов с применённой корректировкой и Σ их исключений в тыс. руб., п. 11).
- `audit_action`: **+ `KFO_EDIT`** — сохранение ручных полей КФО (комментарии строк, обоснование порога, причины
отклонений от Методики) одним событием на запрос, `object_type = kfo_result`, `before` / `after` по изменённым
полям; раздел — тот же, что у корректировок и гудвилла (`CONSOLIDATION`). Миграция enum — в 1546.
- `elimination_rule.elimination_mode`: в пояснение — значение `min_or_full` (varchar, миграция схемы не нужна).
- Шапка — changelog v5.26; `entities-v5.26.md` — копия с теми же правками; ссылки «v5.24» в `CLAUDE.md` workspace и
`openspec/config.yaml` — на v5.26.
- Миграция `arm-core` (две колонки, nullable) — в AURORA-1546.
Разреза «расхождение по компании» не добавляем: на экране «Расхождения» — общая колонка строки.
### 4.3 Контракт API (`dev/api-contract-kfo.md` + `.openapi.yaml`)
Префикс `/api/v1/consolidations/versions/{version_id}`.
| Метод и путь | Право | Ответ | Ошибки |
|---|---|---|---|
| `GET …/kfo` | `KFO_VIEW` | 200 `KfoResultOut` | 404 `NOT_FOUND` |
| `PUT …/kfo/annotations` | `CONSOLIDATION_EDIT` | 200 `KfoResultOut` | 404, 409 `VERSION_NOT_EDITABLE` / `LOCK_REQUIRED` / `LOCKED_BY_OTHER`, 422 `UNKNOWN_LINE`, 422 pydantic |
Чтение — по `KFO_VIEW` (право есть в seed и означает ровно это), не по `CONSOLIDATION_VIEW`, как корректировки и
гудвилл; правка — `CONSOLIDATION_EDIT` (`KFO_EDIT` в seed нет).
`KfoResultOut`:
- `version_id`, `computed_at`, `is_frozen`, `editable`, `read_only_reason`, `units = "thousand_rub"`;
- `companies[]` — `{legal_entity_id, name, inn}` в порядке колонок (по имени);
- `requisites` — п. 11;
- `summary.balance` / `summary.income` — `{deviation_total, base_kind (NET_ASSETS | BALANCE_TOTAL | SALES_PROFIT |
REVENUE), base_amount, limit_percent, limit_amount, deviation_percent, exceeded}`;
- `justification_required`, `threshold_justification`;
- `goodwill` — `{enabled, total}`;
- `balance.lines[]`, `income.lines[]` — `KfoLineOut`: `line_code`, `line_name`, `ordinal`, `is_total`,
`is_threshold_base` (подсветка строки базы при превышении), `companies[]` `{legal_entity_id, client_amount,
vgo_amount, cleaned_amount, share_percent}` (у `GOODWILL` — пусто), `sum_amount`, `vgo_amount`, `eliminated_auto`,
`eliminated_manual`, `goodwill_adjust`, `bank_amount`, `deviation_amount`, `comment`;
- `warnings[]`.
Деньги — строки с двумя знаками **в тыс. руб.** (в отличие от корректировок и гудвилла, где рубли: результат КФО по
определению в тысячах; поле `units` явно). Переключатели «Единицы» (тыс. / млн) и «Знаков после запятой» — только показ
на фронте.
`PUT …/kfo/annotations` — тело `{comments?: [{statement_type, line_code, comment | null}], threshold_justification?,
methodology_deviations?}`; отсутствующее поле не меняется, `null` — очищает; комментарий ≤ 2000, обоснование и причины
≤ 4000; строки нет в бланке — 422 `UNKNOWN_LINE`. Сохраняет набором в одной транзакции, аудит — `KFO_EDIT` (4.2).
Ответ — полный `KfoResultOut`.
Состояния экрана из DOC-274647 выражаются полями ответа: нет компаний / нет форм — `warnings`; заморожено — `is_frozen`
+ `editable = false`. Примеры ответа (обычный, с превышением и гудвиллом, замороженный) — в md и в `examples`
OpenAPI, на числах сквозного примера §8; Степанов берёт их как фейк.
### 4.4 Вложения в Jira
После открытия PR (не дожидаясь merge): AURORA-1546 — `05-domain-rules.md` (§8) и `data-model-v5.26.dbml`;
AURORA-1547 — `api-contract-kfo.md` и `api-contract-kfo.openapi.yaml`; в обеих — комментарий со ссылкой на PR. После
правок по ревью — новые версии вложений.
## 5. Что остаётся исполнителю плана
Точный перечень и порядок строк бланка (из словаря ядра и `statement-code-orders.yaml`), тексты предупреждений,
формулировки §8, разметка OpenAPI (имена схем, `examples`), числа сквозного примера, расстановка открытых вопросов в
`07-open-questions.md` (новые OQ с номерами по порядку).
## 6. Decisions made without discussion
| # | Дата | Решение | Обоснование | Цена ошибки |
|---|---|---|---|---|
| 1 | 06.11.2026 | Развилки «решает заказчик» из DOC-274636 закрываются вариантом черновика как стартовым, с пометкой | Раскладка спринта 4, R-2; иначе 1546 не стартует | Доработка по ответу заказчика |
| 2 | 06.11.2026 | Вход КФО — агрегаты таблицы корректировок (§5 п. 2a), не отдельный обход ОСВ | Одна истина для аналитика и КФО; двойной учёт уже снят разнесением | Если заказчик захочет КФО «мимо» разнесения — переделка источника |
| 3 | 06.11.2026 | Корректировка агрегата заменяет автосумму; при разных итогах сторон каждая исключает меньший | DOC-274636 доп. 4, ответ Лысенко п. 8 | Неверная сумма ВГО на агрегатах с корректировкой |
| 4 | 06.11.2026 | Правило 2 считается автоматически по DOC-274230 (режим `min_or_full`, каскад 1310 → 1350 → 1370) правкой seed версии 1 на месте | Описание 1546 (владелец, 06.11) требует каскада; верифицированных версий нет — объяснимость старых результатов не страдает | Версии, созданные до правки, начнут считать правило 2 по-новому; если где-то это недопустимо — нужна версия правил 2 |
| 5 | 06.11.2026 | Правило 1 остаётся ручным (только корректировка) | Обе суммы DOC-274230 — у компании 2, сторона агрегата устроена иначе; переделка seed не влезает | ВГО по 76.7.x не исключаются без корректировки аналитика |
| 6 | 06.11.2026 | Результат КФО в API — в тыс. руб. (поле `units`), не в рублях, как в других контрактах | Округление до тысяч — часть расчёта; рубли из результата не восстановить | Фронт, привыкший к рублям, неверно масштабирует — снимается полем `units` |
| 7 | 06.11.2026 | `vgo_amount` со знаком строки; `cleaned = client − vgo` на всех строках | Расходы в формах с минусом; одно равенство без исключений для экрана и итогов | Если формы хранят расходы положительными — знак у 2120/2210/… перевернётся (проверить на фикстурах в 1546) |
| 8 | 06.11.2026 | Строка `GOODWILL` — отдельная строка раздела I после 1170, без разреза по компаниям, входит в 1100/1600 | DOC-259025 важнее макета (R-1); разрез по компаниям гудвиллу не определён | Если методологи выберут «в т. ч. под 1170» — перенос строки и пересчёт итогов |
| 9 | 06.11.2026 | Чтение пересчитывает устаревший результат сам; кнопки «Рассчитать» нет | Вариант 1 вопроса 16; фронту не нужно знать про устаревание | Долгий GET на больших версиях — оптимизация в 1417 |
| 10 | 06.11.2026 | Разреза «расхождение по компании» в модели нет (в чате обещал добавить) | На экране «Расхождения» — общая колонка строки | Понадобится расшифровка расхождения по компании — новая колонка |
| 11 | 06.11.2026 | Порядок обхода агрегатов для каскада — `rule_no`, ИНН компании 1, ИНН компании 2 | Детерминированный результат при нескольких агрегатах на одну строку | Иное распределение между 1310/1350/1370 при нехватке остатка |
| 12 | 06.11.2026 | Гудвилл в итогах — `goodwill_adjust` у 1100/1600 = Σ слагаемых (вопрос ревью 1, вариант «а») | Одна формула `bank = sum − vgo + goodwill_adjust` на всех строках; 1600 сходится с 1700 | Доли компаний у 1100/1600 в сумме < 100 % — если смутит заказчика, показывать долю гудвилла отдельно |
| 13 | 06.11.2026 | Строки расходов 2120, 2210, 2220, 2330, 2350 приводятся к `−|x|` независимо от знака в форме | Формы хранят знак по-разному (§7, §11.9); без единой конвенции Σ по компаниям и итоги неверны | Законно положительное значение строки расходов (сторно) станет отрицательным |
| 14 | 06.11.2026 | Автосумма агрегата — одна функция для таблицы корректировок и КФО; PR правит §8 «Корректировка» и контракт корректировок под `min_or_full` | Иначе аналитик видит одно, а КФО исключает другое | Шире PR и 1546 (правка `correction_source.py`) |
| 15 | 06.11.2026 | Предупреждения и сводка корректировок хранятся в `kfo_result` (`warnings jsonb`, 4 колонки) | Замороженная версия должна отдавать то, что видели при верификации | Лишние колонки, если ЕХ всё равно заморозит ответ целиком в срез |
| 16 | 06.11.2026 | Аудит ручных полей КФО — новое действие `KFO_EDIT` | В `audit_action` подходящего нет; инвариант «append-only аудит» | Если заказчику нужен отдельный раздел журнала — новое значение раздела |
| 17 | 06.11.2026 | Бланк — по редакции на отчётную дату версии (66н ≤ 2024, ФСБУ 4 позже) | Иначе КФО за 2024 теряет статьи 66н; так же выбирает редакцию ядро | Версия на стыке редакций с формами разных редакций — строки вне бланка в предупреждения |
| 18 | 06.11.2026 | Гудвилл-ветка — по паре таблицы гудвилла, независимо от агрегатов; агрегат правила 2 пары подавляется; перенос строки 58.01 — предупреждение | ФВ гудвилла уже определён по всем строкам 58.01 пары (контракт гудвилла) | Двойное исключение строки, перенесённой аналитиком в другое правило, — видно по предупреждению |
| 19 | 06.11.2026 | Чтение КФО — по `KFO_VIEW`, правка — по `CONSOLIDATION_EDIT` | `KFO_VIEW` в seed есть и означает ровно это; `KFO_EDIT` нет | Роль с `CONSOLIDATION_VIEW` без `KFO_VIEW` не увидит вкладку — так и задумано матрицей |

View File

@ -0,0 +1,48 @@
# Proposal: расчёт КФО — правила §8, модель v5.26, контракт API (docs #206)
## Why
Разработка КФО стартует 10.11.2026: AURORA-1546 (расчёт, Цыганков) и AURORA-1547 (экран, Степанов, на фейковых данных
по контракту). Обе задачи в «Нужно до старта» ссылаются на три документа, которых нет или которые устарели:
- §8 «КФО» в `context/05-domain-rules.md` написан по прежней редакции: корректировки складываются с автоэлиминацией,
нет распределения по строкам правил 1 и 2, нет ветки гудвилла в расчёте, нет единиц и округления;
- в модели `kfo_result_line_company` нет «по данным клиента» и «в т. ч. взаимные» по компании — колонки экрана из
сохранённого результата не восстановить;
- контракта API КФО нет совсем.
Источники: страницы Лысенко — DOC-274230 «Правила пар счетов» (27.10), DOC-259025 «Гудвилл» (03.11), DOC-272613
«Корректировка данных агрегатом» (04.11); черновики команды — DOC-274636 «Расчёт КФО», DOC-274647 «Экран: КФО»;
макет `[31-04]`. При расхождении страница Лысенко важнее черновика (решение владельца 28.10.2026, распространено на КФО
в раскладке спринта 4, R-1).
## What changes
- `context/05-domain-rules.md` §8, блок «КФО» — переписан: источники значений, порядок расчёта, автосумма агрегата по
каждому правилу ББ, распределение по строкам, корректировка агрегата вместо автосуммы, гудвилл, ОФР, итоговые строки,
единицы и округление, расхождения и пороги, реквизиты, пересчёт и заморозка, предупреждения.
- `model/data-model-v5.26.dbml` + `model/entities-v5.26.md` — `kfo_result_line_company` получает `client_amount`
и `vgo_amount`; пояснения `kfo_result_line` (`vgo_amount`, `eliminated_manual`, `GOODWILL`, единицы) и `kfo_result`
приведены к §8; changelog в шапке.
- `dev/api-contract-kfo.md` + `dev/api-contract-kfo.openapi.yaml` — новый контракт: чтение результата КФО версии,
сохранение ручных полей (комментарии строк, обоснование порога, причины отклонений от Методики).
- §8 «Корректировка» и `dev/api-contract-corrections.md` — автосумма агрегата нового режима `min_or_full` (правило 2):
таблица корректировок и КФО считают её одной функцией.
- DBML v5.26 также: `kfo_result.warnings` и сводка корректировок, `audit_action.KFO_EDIT`, пояснение `elimination_mode`.
- `context/07-open-questions.md` — открытые вопросы КФО с вариантом, принятым на старте.
Срез плана: Срез 6 (`09-delivery-plan.md`), Гант 2.5 / 2.8. Дочерние change'и — в `arm-core` (AURORA-1546, 1547,
1417), заводят исполнители.
Схема среза ЕХ `ex-payload-v1` — отдельный change и PR (под AURORA-1560), строится на форме ответа этого контракта.
## Non-goals
- Код расчёта и экрана — AURORA-1546, 1547 в `arm-core`.
- Правила пар ОФР как данные и их расчёт — AURORA-1999 (§5); здесь только как КФО читает агрегаты ОФР.
- Механизм пересчёта и инвалидации (события, триггеры) — AURORA-1417; здесь только наблюдаемое поведение для экрана.
- «КФО Общий» (динамика), «Подтверждение КФО клиента», выгрузка формы 2.03.01.01с — AURORA-2003, 1549/1550, 1565.
- Проверки перед переходами статусов (обязательность обоснования при отправке на верификацию) — AURORA-2002 / 1414;
здесь только признак «нужно обоснование».
- Новые поля версии под «подход по Методике» и «согласовано клиентом» — ждут ответа заказчика (DOC-274636, вопросы 14, 15).
- Схема `ex-payload-v1` — следующий change.