From 0482acc51f5dfeec7577145c0cc5d1717b51a7cd Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Fri, 6 Nov 2026 18:41:20 +0400 Subject: [PATCH 1/4] =?UTF-8?q?=D0=93=D1=83=D0=B4=D0=B2=D0=B8=D0=BB=D0=BB:?= =?UTF-8?q?=20=D0=BA=D0=BE=D0=BD=D1=82=D1=80=D0=B0=D0=BA=D1=82=20API=20?= =?UTF-8?q?=D0=B8=20=C2=A78=20=D0=BF=D0=BE=D0=B4=20DOC-259025=20=D0=B8=20?= =?UTF-8?q?=D1=80=D0=B5=D0=B2=D1=8C=D1=8E=20core=20#222=20(#204)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Контракт dev/api-contract-goodwill.md/.openapi.yaml — по факту arm-core #222 после доводки: пары по вложениям 58.01, «Нет вложений», nullable суммы, warnings, groups и УК в итогах, пакетный PUT …/goodwill, доля до двух знаков, sign в экспорте; OpenAPI выгружен из app.openapi(). §8 «Гудвилл» — выжимка DOC-259025 (03.11); OQ-10/OQ-37 — что принято в реализации. Co-Authored-By: Claude Opus 5.5 --- context/05-domain-rules.md | 30 +- context/07-open-questions.md | 4 +- dev/api-contract-goodwill.md | 308 ++++++++----- dev/api-contract-goodwill.openapi.yaml | 609 +++++++++++++++---------- 4 files changed, 595 insertions(+), 356 deletions(-) diff --git a/context/05-domain-rules.md b/context/05-domain-rules.md index a483672..6059607 100644 --- a/context/05-domain-rules.md +++ b/context/05-domain-rules.md @@ -951,11 +951,31 @@ OQ-145); тем же правилом сервисы разрешают ничь 409 `CORRECTION_VERSION_CONFLICT`, кто-то сохранил раньше, фронт перечитывает таблицу. - История — `audit_event(CORRECTION_CREATE`/`CORRECTION_EDIT)`, `before`/`after`, `version_no++` при каждом сохранении. -**Гудвилл** (DOC-259025, ТЗ 6.7): по каждой паре ГО→ДО периметра: -`FV = Σ closing_debit (58.01, контрагент ДО)`; `UK = 1310 ББ ДО на отчётную дату` (FNR03: Кт 80 + Кт 83, при -отсутствии — 1310); `share` — ручной ввод; `participation = (1310 [+ 1350?]) × share / 100`; -`goodwill = FV − participation` (может быть < 0); `include_in_kfo` + комментарий обязателен. Положительный итог — -строка `GOODWILL` в разделе I КФО. +**Гудвилл** (DOC-259025 «Гудвилл», версия 03.11.2026; ТЗ 6.7; AURORA-1543/1544, реализовано — `arm-core#222/#220`, +контракт `dev/api-contract-goodwill.md`). Единица — направленная пара ГО → ДО версии: + +- **Пары.** ГО — владелец строк ОСВ счёта `58.01` и его потомков (`chart_of_account.parent_id`) в сводной ОСВ версии + (общие правила §2–§3: выбор источников, периоды, дорезолв), ДО — сопоставленный контрагент этих строк из периметра. + `is_head_entity` не используется; встречные направления — разные пары; пустой контрагент, вложение в себя и + контрагент вне периметра пары не дают. `ФВ = Σ closing_debit` заполненных строк пары, пара есть при `ФВ ≠ 0`. +- **«Нет вложений».** Сохранённая пара, которая больше не находится (замена ОСВ, нулевое сальдо, выход из периметра), + остаётся строкой таблицы с ручным вводом, но без сумм, вне итогов и КФО; вернулась — расчёт по текущим данным. +- **УК** — стр. `1310` подтверждённого ББ ДО, привязанного к версии, по колонке конца отчётного квартала (не по + `is_current`), повторы кода суммируются, единицы формы — словарём единиц `arm_statements`, в рублях. Нет + однозначного ББ/строки/колонки — УК не рассчитан (`null`, предупреждение), а не 0. **1350 в базу не входит** + (ASSUMPTION OQ-10 по DOC-259025 §6), хранится справочно. +- **Ввод.** Доля владения `d` — 0–100 %, не больше **двух** знаков (лишние — ошибка, не округление); галочка «Учёт в + отчёте» (`include_in_kfo`); у отмеченной действующей строки обязательны доля и комментарий. Снятие галочки ввод не + стирает. Сохранение — набором строк в одной транзакции; строки без изменений не перезаписываются. +- **Формулы** (только у действующей отмеченной строки с долей и УК, иначе не рассчитано): `P = round(УК × d / 100, 2)` + (`ROUND_HALF_UP`), `G = ФВ − P` (может быть < 0 и = 0). +- **Итоги** блока ГО и листа: ФВ и УК — все действующие строки независимо от галочки; P и G — отмеченные (отрицательные + тоже); в КФО — `Σ G > 0` строк, где `use_goodwill` = действующая ∧ отмечена ∧ доля и комментарий заполнены ∧ `G > 0`, и + только при `consolidation_version.goodwill_enabled` (переключатель на вкладке, OQ-27). Доли в итогах не складываются; + сумма заполненных долей разных ГО в одной ДО > 100 % — предупреждение, не ошибка. +- **КФО** (AURORA-1546, ещё не реализовано): для пары с `use_goodwill` — ГО `1170 − ФВ`, ДО `1310 − P`, ГО строка + `GOODWILL` `+ G` (служебный код, ASSUMPTION OQ-37), обычный ББ-2 по этой паре не применяется; при `G ≤ 0` — обычный + ББ-2, разница в 1370 не переносится. Изменение ввода, переключателя или источников делает результат КФО устаревшим. **КФО** (ТЗ 6.4, Прил. 10): по каждому коду строки ББ/ОФР: `sum_amount = Σ по компаниям` (значения отчётной колонки ББ / ОФР за период, в тыс. руб.) → diff --git a/context/07-open-questions.md b/context/07-open-questions.md index 7ca6555..b8cdb4c 100644 --- a/context/07-open-questions.md +++ b/context/07-open-questions.md @@ -15,7 +15,7 @@ | OQ-07 | Матчинг ВГО: одна строка = одна позиция (DOC-223044) или агрегировать по (компания, контрагент, счёт) (FNR03)? | DOC-223044 vs FNR03 | срез 5 | **закрыт решением владельца 19.10.2026 (docs #136):** внутри тройки «правило × компания A × компания B» строки сопоставляются группами **1:1, 1:N, N:1** (правило охватывает несколько счетов), N:M нет; выбирается **минимальная упаковка** — меньше строк (закрывается и 1:2, и 1:3 → 1:2); закрытие — точно или ≤ 5 % к меньшей; N ≤ 5; ничья → ручное. Упаковка — детализация; в КФО — агрегат min(ΣA, ΣB) по тройке, на него способ упаковки не влияет. Алгоритм — `05-domain-rules.md` §5 | | OQ-08 | Знак порога 5 % в правиле пары (≤ или <), и к чему считать % — к меньшей сумме (Методика сноска 14) | FNR03 (знак потерян) | срез 5 | допущение: `≤ 5 %` к меньшей | | OQ-09 | Правила элиминации ОФР (2110/2120/2310/2320/2330): точный состав субсчетов, источник средневзвешенной ставки НДС, выбор субсчетов «работником ПА самостоятельно» | FNR03 | срез 6 | не реализовывать авто-элиминацию ОФР до решения; только ручные `correction` | -| OQ-10 | Гудвилл: входит ли стр. 1350 (добавочный капитал) в базу участия; УК из ББ (1310) или из ОСВ 80/83 | DBML, DOC-259025, FNR03 | срез 6 | допущение: 1310 из ББ; 1350 — настраиваемый флаг, по умолчанию выключен | +| OQ-10 | Гудвилл: входит ли стр. 1350 (добавочный капитал) в базу участия; УК из ББ (1310) или из ОСВ 80/83 | DBML, DOC-259025, FNR03 | срез 6 | допущение: 1310 из ББ; 1350 — настраиваемый флаг, по умолчанию выключен. **06.11:** DOC-259025 (Лысенко, 03.11) — «для УК используется 1310», 1350 в формулу не входит; так реализовано (arm-core #222), флага нет; подтверждение методологов (AURORA-1369) не получено | | OQ-11 | Строка «ППА» в КФО (правило № 1, когда у А нет ОСВ 03) — что это и куда | FNR03 | срез 6 | не реализовывать | | OQ-12 | Формулы ОФР: знаки 2410/2420/2430 по ФСБУ 4/2023 vs 66н; состав 1100 (1120–1140) | DOC-221281 | срез 3 | допущение: формулы §7, строки без значения = 0; **расширение 07.10.2026:** формулы и справочники по редакциям форм (`66n` / `fsbu4`) — OQ-146 | | OQ-13 | `confidence` от ИС Сканирование — доли 0..1 или проценты 0..100; имя поля организации (`full_name` vs `form_name`) | FNR02 vs `aep_*.json` | срез 3 (scan-gateway) | **закрыт для scan-gateway (04.10.2026, по `aep_3.json`, `aep_6.json`):** `confidence` — числа 0–100 (не доли); название организации — `data_ocr.organization.form_name` (шумный OCR-текст), ИНН — `inn.form_name`, единицы — `units.form_name`, дата — `date.form_name` (в `aep_6.json` мусор); `full_name`/`inn.inn`/`units.measurement` из ТЗ в реальных файлах нет. Нормализация — Срез 3 | @@ -42,7 +42,7 @@ | OQ-34 | «Удалить» ГСК в меню строки (`77-01`): физическое удаление или закрытие с историей? В DBML у `related_group` нет флага/истории удаления | макет [77-01] | срез 1 | **закрыт реализацией Среза 1 (ASSUMPTION подтверждено отсутствием возражений):** удаляется только пустая ГСК/ГСЗ (409 `GROUP_NOT_EMPTY`), состав сохраняется в `before_value` аудита; группа с FK из консолидаций — follow-up `GROUP_IN_USE` (arm-core#8, T8.1) | | OQ-35 | База процента отклонения: в таблице корректировки % считается к сумме стороны ЮЛ-1 (−79,77 % для 1 000 ↔ 202,32, `31-02-02`), в «Подтверждении КФО клиента» — к версии банка (ст. 2, `31-06`); DBML и Методика (сноска 14) — к **меньшей** сумме. Что считать правильным для `correction.deviation_percent` и `client_kfo_line.deviation_percent`? | макет [31-02] [31-06] vs DBML, Методика | срезы 5–6 | допущение: к меньшей (Методика); макет считать неточным | | OQ-36 | Экран проверки показывает строки формы **без кода** («в том числе: Основные средства, пригодные к использованию…», `41`); DBML хранит только строки с кодом (`financial_statement_line`), контракт требует `line_code`. Хранить ли расшифровочные строки без кода (и как их показывать)? | макет [41] vs DBML, контракт | срез 3, контракт v1.x | допущение: строки без кода не хранить и не показывать в первой очереди; **Срез 3b: допущение реализовано** — `financial_statement_line` пишется только для строк с кодом (строк без кода в результате нет), экран их не получает. **Закрыт решением владельца 17.10.2026 (Р-8, change `unified-statement-normalization`, контракт v2.0):** строки без кода отдаются сервисами со статусом `detail` (расшифровка под строкой с кодом), `missing` или `unreadable`, хранятся ядром (`financial_statement_line.line_status`, `line_code` nullable, DBML v5.19); в проверки §7, агрегаты и `done` `detail` не входит; экран по умолчанию прячет расшифровки под переключателем «Показать расшифровки (N)». Правила — `05-domain-rules.md` §11.4. Действует с переходом сервисов на v2.0 | -| OQ-37 | Строка «Гудвилл 1105» (ФСБУ 4/2023) в распознанной форме (`41`) vs строка `kfo_result_line.line_code = 'GOODWILL'` в разделе I КФО: использовать код 1105 или служебный GOODWILL? Влияет на шаблон строк на фронте и на формулы §7 (1100 = … + 1105?) | макет [41], ФСБУ 4/2023 | срез 3, 6 | допущение: хранить код 1105 как пришёл; строка КФО «GOODWILL» — отдельная, уточнить у методологов | +| OQ-37 | Строка «Гудвилл 1105» (ФСБУ 4/2023) в распознанной форме (`41`) vs строка `kfo_result_line.line_code = 'GOODWILL'` в разделе I КФО: использовать код 1105 или служебный GOODWILL? Влияет на шаблон строк на фронте и на формулы §7 (1100 = … + 1105?) | макет [41], ФСБУ 4/2023 | срез 3, 6 | допущение: хранить код 1105 как пришёл; строка КФО «GOODWILL» — отдельная, уточнить у методологов. **06.11:** DOC-259025 §7 — технический `line_code = GOODWILL` отдельной строкой раздела I; применение — AURORA-1546 | | OQ-38 | ЕХ в макете показывает и **неверифицированные** консолидации («Консолидация 2025 К4», без даты верификации, `62` [83-01]); ЕХ ТЗ 6.2 — срез пишется только после «Верифицирован». Показывать ли в ЛК ЕХ незавершённые консолидации (как «в работе») или только `ex_snapshot`? | макет [62] [83-01] vs ЕХ ТЗ 6.2 | срез 7 | допущение: только срезы `ex_snapshot`; незавершённые — в разделе «Отчётность» | | OQ-39 | Состав «сформированной отчётности» в ЕХ (`83-01`): КФО .xlsx, Шахматка .xlsx и «Консолидированная отчетность .pdf» с датой «Сформирован» и подписями Утвердил/Верифицировал. Нужен ли PDF-экспорт КФО и какие ещё артефакты (сводная ОСВ, корректировки, гудвилл — ЕХ ТЗ 5.2) показывать? | макет [83-01] vs ЕХ ТЗ 5.2 | срез 7 | допущение: xlsx из `payload` на лету для всех `item_type`; PDF — не делать до решения | | OQ-40 | «Квартальная дата» в журнале для консолидации 2024 К1 показана как 01.01.2024 (`43-02`); по правилам банка 1 квартал 2024 = отчётная дата 01.04.2024 (`consolidation.reporting_date`). Что выводить? | макет [43-02] | срез 4, 7 | **допущение принято и реализовано в Срезе 4 (06.10.2026):** `consolidation.reporting_date` по правилу банка (Q1 → 01.04.Y, Q2 → 01.07.Y, Q3 → 01.10.Y, Q4 → 01.01.Y+1), `domain/consolidation_periods.reporting_date`; макет неточен | diff --git a/dev/api-contract-goodwill.md b/dev/api-contract-goodwill.md index db86997..a300789 100644 --- a/dev/api-contract-goodwill.md +++ b/dev/api-contract-goodwill.md @@ -1,160 +1,183 @@ # Контракт API таблицы гудвилла (AURORA-1543 / AURORA-1544) -Черновик контракта для бэкенда (AURORA-1543, Цыганков) и фронта (AURORA-1544, Голов) — по аналогии с -`dev/api-contract-corrections.md`: фронт строит экран по контракту на фейковых данных, не дожидаясь бэкенда, подключение -к реальным ручкам — потом. Машиночитаемая копия — `dev/api-contract-goodwill.openapi.yaml`; при расхождении двух файлов -правится тот, что ошибся, и оба держатся одинаковыми. +Контракт таблицы гудвилла версии консолидации — по факту реализации arm-core (#222, OpenSpec change `goodwill-table`). +Машиночитаемая копия — `dev/api-contract-goodwill.openapi.yaml`: она выгружена из `app.openapi()` и сверяется с +приложением тестом `arm-core/api/tests/test_goodwill_contract.py`. Если файлы расходятся, прав код, а оба файла +правятся вслед за ним. Источники, в порядке приоритета: -1. **AURORA-1369** (решение методологов, срок 02.11.2026) — формула ещё не подтверждена, см. §5 «Открытые вопросы». Пока - не закрыта, бэкенд не считает по-настоящему — но контракт (форма ответа, поля, эндпоинты) этим не затронут: методологи - могут поменять *формулу* (входит ли 1350, источник УК), а не *набор полей* (поля `charter_capital_amount` / - `additional_capital_amount` под обе ветки уже есть в DBML). -2. `05-domain-rules.md` §8 «Гудвилл» — текущий рабочий алгоритм (допущения OQ-10, OQ-37). -3. DBML v5.24, `Table goodwill`, `Table kfo_result_line` (`line_code = 'GOODWILL'`), `consolidation_version.goodwill_enabled`. -4. Макет: `[31-03]` «Гудвилл» (таблица, режим «Редактировать»), `[31-04]` «КФО» (строка «В т.ч. Гудвилл» под 1170). -5. ТЗ 6.7, Прил. 3 п. 2, Прил. 8 — только там, где остальное молчит. +1. **DOC-259025 «Гудвилл»** (вики аналитики, версия 03.11.2026, Лысенко) — пары, жизненный цикл строк, формулы, итоги, + проверки ввода. +2. `05-domain-rules.md` §8 «Гудвилл» — выжимка того же с решениями по допущениям. +3. DBML v5.24: `Table goodwill`, `consolidation_version.goodwill_enabled`. +4. Макет `[31-03]` «Гудвилл» (таблица, режим «Редактировать»). ## 1. Общее Всё как в `dev/api-contract-slice-4.md` §1 и `dev/api-contract-corrections.md` §1: - префикс `/api/v1`, `Authorization: Bearer `; -- чтение — право `CONSOLIDATION_VIEW`, правки — `CONSOLIDATION_EDIT`; +- чтение и экспорт — право `CONSOLIDATION_VIEW`, правки — `CONSOLIDATION_EDIT`; - версия вне видимости — 404 `NOT_FOUND`, как несуществующая; - доменные ошибки — `{"detail": {"code", "message", ...}}`, 422 валидации тела — массив pydantic; -- **деньги — строки с двумя знаками** (`"1000000.00"`), всегда в **рублях**; перевод в тыс. руб. — на фронте; -- порядок проверок правок: право (403) → видимость (404) → версия редактируема (409) → операция; коды — те же, что везде: - 409 `VERSION_NOT_EDITABLE`, `LOCK_REQUIRED`, `LOCKED_BY_OTHER`. Фронт читает `editable` / `read_only_reason` из ответа - таблицы, правило не повторяет. +- **деньги — строки с двумя знаками в рублях** (`"1000000.00"`) или `null`; `null` — «не рассчитано», а не ноль. + Перевод в тыс./млн руб. — на фронте, после суммирования; +- **доля владения — строка с двумя знаками** (`"80.00"`) или `null`; +- порядок проверок правок: право (403) → видимость (404) → версия редактируема (409 `VERSION_NOT_EDITABLE`, + `LOCK_REQUIRED`, `LOCKED_BY_OTHER`) → операция. Фронт берёт `editable` и `read_only_reason` из ответа таблицы и правило + сам не повторяет; у пользователя без `CONSOLIDATION_EDIT` — `read_only_reason = "VERSION_NOT_EDITABLE"`. ## 2. Понятия -- **Головная организация периметра (ГО)** — член версии консолидации с `is_head_entity = true` (как в `VersionCard.members[]`, - `dev/api-contract-slice-4.md` §3). **Дочерняя организация (ДО)** — любой другой член периметра. Таблица гудвилла — по - одной строке на каждую пару (ГО, ДО); строки не создаются и не удаляются пользователем, их состав определяется - составом периметра (`PUT …/members`) на момент запроса. **Если в периметре несколько `is_head_entity`** — строки - считаются для каждой ГО отдельно (как для независимых голов); на реальных данных такого пока не встречалось — - ASSUMPTION, уточнить при первом расхождении. -- **Фондовые вложения (`financial_investment_amount`, FV)** — `Σ closing_debit` счёта `58.01` ГО по контрагенту ДО - (сводная ОСВ версии). Может быть `0.00` — строка всё равно показывается (полный список ДО периметра под каждой ГО, - как в макете `[31-03]`). -- **Уставный капитал ДО (`charter_capital_amount`, УК)** — стр. `1310` ББ ДО на отчётную дату; при наличии ОСВ ДО — - `Кт 80 + Кт 83`, иначе `1310` из формы (§8 domain-rules, уточняет первую часть OQ-10). **Добавочный капитал - (`additional_capital_amount`)** — стр. `1350` ББ ДО, считается и хранится всегда, независимо от того, участвует ли - в формуле (см. OQ-10 ниже). -- **Доля владения (`ownership_share`)** — ручной ввод аналитика, % (`numeric(7,4)`); единственное обязательное поле - правки наряду с `comment`. -- **Доля участия (`participation_amount`)** = `(charter_capital_amount [+ additional_capital_amount, если решение - методологов это подтвердит]) × ownership_share / 100`. -- **Гудвилл (`goodwill_amount`)** = `financial_investment_amount − participation_amount`; может быть отрицательным. -- **Учёт в КФО (`include_in_kfo`)** — чекбокс «Учёт в отчёте» строки (макет `[31-03]`, с «Выделить все» по ГО на - фронте — пакетное действие фронта, не отдельная ручка). Комментарий обязателен **только** когда `include_in_kfo = true` - (ТЗ 6.7). Положительная сумма отмеченных строк уходит в `kfo_result_line` с `line_code = 'GOODWILL'` - (раздел I КФО, допущение OQ-37) — расчёт и запись при построении результата КФО, не в этом контракте. -- **Признак расчёта гудвилла (`goodwill_enabled`)** — переключатель версии (`consolidation_version.goodwill_enabled`, - решено OQ-27: живёт на вкладке «Гудвилл», не в модалке создания). Сейчас в контракте нет ручки, которая бы его - выставляла (только чтение через `VersionCard`, `dev/api-contract-slice-4.md`) — добавляется здесь (§3.2). - Выключен — таблица показывается как есть (доли и комментарии не теряются), но ничего не уходит в КФО. +- **Пара ГО → ДО.** ГО — компания периметра, у которой в сводной ОСВ версии есть строки счёта `58.01` или его потомков + (по `chart_of_account.parent_id`) с сопоставленным контрагентом из периметра; этот контрагент — ДО. Признак + `is_head_entity` **не используется**: одна компания может быть ГО в одном блоке и ДО в другом, встречные направления — + разные пары. Пустой контрагент, вложение в себя и контрагент вне периметра пару не дают. Ручного добавления пар нет. +- **Финансовые вложения (`financial_investment_amount`, ФВ)** — сумма заполненных `closing_debit` подходящих строк + пары; ФВ = 0 — пары нет. +- **«Нет вложений» (`has_investments = false`).** Сохранённая пара, которая больше не находится (замена ОСВ, нулевое + сальдо, компания вышла из периметра). Строка остаётся с сохранёнными долей, комментарием и галочкой, все денежные поля + — `null`, в итоги и КФО не входит. Повторное появление пары восстанавливает расчёт по текущим данным. +- **Уставный капитал ДО (`charter_capital_amount`, УК)** — стр. `1310` подтверждённого ББ ДО, привязанного к версии, + по колонке **конца отчётного квартала** (не по `is_current`), в рублях: единицы формы читаются словарём единиц + `arm_statements` («тыс.руб.», «в тысячах рублей» → тыс. руб.). Повторы кода 1310 суммируются. Ноль — допустимое значение. + Нет ББ, строки 1310 или колонки даты, единицы не распознаны — `null` и предупреждение `CAPITAL_SOURCE_MISSING`; + у ДО несколько подходящих ББ — `null` и `CAPITAL_SOURCE_AMBIGUOUS`. +- **Добавочный капитал (`additional_capital_amount`)** — стр. `1350` того же ББ, справочно; в формулу не входит + (ASSUMPTION OQ-10). +- **Доля владения (`ownership_share`)** — ручной ввод, % от `0` до `100`, не больше двух знаков после запятой. +- **Доля участия (`participation_amount`, P)** = `round(УК × доля / 100, 2)` (`ROUND_HALF_UP`), **гудвилл + (`goodwill_amount`, G)** = `ФВ − P`, может быть отрицательным и нулевым. P и G считаются **только у действующей + отмеченной строки** (`include_in_kfo = true`) с заполненными долей и УК, иначе `null`. +- **Учёт в отчёте (`include_in_kfo`)** — галочка строки. У отмеченной действующей строки обязательны доля и непустой + комментарий. Снятие галочки не стирает долю и комментарий. +- **Признак учёта гудвилла (`goodwill_enabled`)** — переключатель версии (решение OQ-27: на вкладке «Гудвилл», не в + модалке создания). Выключен — таблица и ручной ввод не меняются, но в КФО ничего не уходит + (`goodwill_in_kfo_amount = "0.00"`). +- **Предупреждения (`warnings[]`)** — `{code, message}`, не блокируют сохранение: + `CAPITAL_SOURCE_MISSING`, `CAPITAL_SOURCE_AMBIGUOUS` (см. УК), `OWNERSHIP_TOTAL_EXCEEDED` — сумма заполненных долей + действующих пар разных ГО в одной ДО больше 100 % (показывается у всех строк этой ДО). ## 3. Эндпоинты | Метод и путь | Право | Запрос | Ответ | Ошибки | |---|---|---|---|---| | `GET /consolidations/versions/{version_id}/goodwill` | VIEW | — | 200 `GoodwillTableOut` | 404 `NOT_FOUND` | +| `PUT /consolidations/versions/{version_id}/goodwill` | EDIT | `GoodwillBatchIn` | 200 `GoodwillTableOut` | 404 `NOT_FOUND` (версия или пара), 409 правок версии, 422 `INVALID_SHARE`, `COMMENT_REQUIRED`, `MONEY_OUT_OF_RANGE`, `DUPLICATE_ROW`, 422 валидации тела | | `PUT /consolidations/versions/{version_id}/goodwill-settings` | EDIT | `GoodwillSettingsIn` | 200 `GoodwillTableOut` | 404, 409 правок версии | -| `PUT /consolidations/versions/{version_id}/goodwill/{head_legal_entity_id}/{subsidiary_legal_entity_id}` | EDIT | `GoodwillRowIn` | 200 `GoodwillRowOut` | 404 `NOT_FOUND` (версия или пара не из периметра), 409 правок версии, 422 `COMMENT_REQUIRED`, 422 `INVALID_SHARE` | -| `GET /consolidations/versions/{version_id}/goodwill/export` | VIEW | — | файл xlsx | 404 `NOT_FOUND` | +| `GET /consolidations/versions/{version_id}/goodwill/export?sign=all\|positive\|negative\|zero` | VIEW | — | файл xlsx | 404 `NOT_FOUND`, 422 неизвестный `sign` | ### 3.1 `GET …/goodwill` — таблица -Все строки одним ответом (пар — единицы, не десятки; без страниц). Порядок: по имени ГО, внутри — по имени ДО. -Группировку по ГО и подсчёт «Выделить все» делает фронт. Пустой периметр (только ГО, без ДО) — `rows = []`, это не ошибка. +Все строки одним ответом, без страниц. Порядок строк — имя ГО, имя ДО, затем id; `groups` — в том же порядке ГО. +Чтение ничего не пишет (записи `goodwill` создаются только сохранением). Нет пар и сохранённых строк — `rows = []`, +`groups = []`, итоги `"0.00"`. -### 3.2 `PUT …/goodwill-settings` — переключатель версии +### 3.2 `PUT …/goodwill` — сохранить набор строк -Меняет `consolidation_version.goodwill_enabled`. Аудит — новое действие в `audit_action` (например, -`GOODWILL_ENABLED_CHANGE`), результат КФО помечается устаревшим при любом изменении значения (как в AURORA-1417). -Выключение не трогает сохранённые строки (`ownership_share`, `comment`, `include_in_kfo` остаются). +Тело — изменённые аналитиком строки целиком (`ownership_share`, `include_in_kfo`, `comment` каждой строки приходят +каждый раз). **Пакет сохраняется в одной транзакции**: сначала проверяются все строки, ошибка любой — ответ с её ключом +(`detail.head_legal_entity_id`, `detail.subsidiary_legal_entity_id`) и ничего не сохраняется. -### 3.3 `PUT …/goodwill/{head_legal_entity_id}/{subsidiary_legal_entity_id}` — сохранить строку +- Пара должна быть в текущей таблице (действующая или «Нет вложений»), иначе 404 `NOT_FOUND` с ключом пары. +- Одна пара дважды в пакете — 422 `DUPLICATE_ROW`. +- `ownership_share` — `null` или строка `0`…`100`, не больше двух знаков после запятой, запятая или точка + (`"80,5"` = `"80.5"`). Лишние знаки, отрицательные, экспонента — 422 `INVALID_SHARE` (без округления). У отмеченной + строки доля обязательна — `null` даёт `INVALID_SHARE`. +- `comment` — до 1000 символов; у отмеченной строки пустой после обрезки — 422 `COMMENT_REQUIRED`. +- У строки «Нет вложений» `null` в доле или комментарии не стирает сохранённое значение. +- Строка, ввод которой совпадает с сохранённым (или для пары без записи — с умолчанием «не отмечена, доли и комментария + нет»), не перезаписывается: `updated_by/updated_at` не меняются, аудита нет. Поэтому фронт может отправлять весь набор + отмеченных строк, но лучше — только изменённые. +- У изменённой действующей строки вместе с вводом сохраняется снимок сумм (ФВ, УК, 1350, P, G); сумма вне + `numeric(18,2)` — 422 `MONEY_OUT_OF_RANGE`. +- Аудит — `GOODWILL_EDIT` на каждую изменённую строку (`object_type = goodwill`, `before`/`after` — строка ответа). -Сохраняет **все поля строки целиком** (как в `PUT …/corrections/aggregates/…` — текущие `ownership_share`, -`include_in_kfo` и `comment` приходят каждый раз, частичного `PATCH` нет). +Ответ — пересчитанная таблица целиком (строки, итоги по ГО и общие). -- `head_legal_entity_id` должен быть членом периметра с `is_head_entity = true`, `subsidiary_legal_entity_id` — любым - другим членом периметра; иначе 404 `NOT_FOUND` (пары не существует). -- `ownership_share` — строка `0`…`100`, до 4 знаков после запятой (`numeric(7,4)`); вне диапазона или больше 4 знаков — - 422 `INVALID_SHARE`. -- `comment` обязателен, если `include_in_kfo = true`; пустой после обрезки в этом случае — 422 `COMMENT_REQUIRED` - (как `COMMENT_REQUIRED` в контракте корректировок); не длиннее 1000 символов. При `include_in_kfo = false` комментарий - необязателен и может быть `null`. -- `financial_investment_amount`, `charter_capital_amount`, `additional_capital_amount`, `participation_amount`, - `goodwill_amount` в тело **не входят** — это вычисляемые поля, пересчитываются сервером из текущей ОСВ версии при - каждом ответе, в запросе их нет. -- Аудит `GOODWILL_ROW_EDIT` (поле нужно добавить в `audit_action`, как `CORRECTION_*` для корректировок) с `before`/`after`; - результат КФО помечается устаревшим. +### 3.3 `PUT …/goodwill-settings` — переключатель версии -Ответ — пересчитанная строка (а не вся таблица — в отличие от корректировок, строки гудвилла друг на друга не влияют). +Меняет `consolidation_version.goodwill_enabled`. Повтор прежнего значения ничего не пишет. Изменение — аудит +`GOODWILL_EDIT` (`object_type = consolidation_version`, `changed_field = goodwill_enabled`). Ручной ввод строк не +трогается. -### 3.4 `GET …/export` — xlsx +### 3.4 `GET …/goodwill/export` — xlsx -Таблица гудвилла одним листом, группировка по ГО, итоговая строка «Всего» (макет `[31-03]`). Имя файла — -`Гудвилл_<название ГК>_<год>_<квартал>.xlsx` в `Content-Disposition` (как в экспорте корректировок). Аудит `EXPORT`. +Лист «Гудвилл»: восемь колонок экранной таблицы (Учёт в отчёте, Наименование дочерней организации, Доля владения, +Фондовые вложения головной организации, Доля участия ГО в УК, Уставный капитал, Гудвилл (+/−), Комментарий), блок на +каждую ГО со строкой «Всего», в конце — «Итого по листу». Деньги — в рублях, `null` — пустая ячейка. `sign` оставляет +строки с гудвиллом нужного знака (нерассчитанные в фильтры по знаку не попадают), итоги остаются по полному составу. +Выгружается последнее сохранённое состояние. Имя — `Гудвилл_<ГК>_<год>_<квартал>.xlsx` в `Content-Disposition` +(`filename*`). Аудит `EXPORT` с `{report: "goodwill", rows: <число строк в файле>}`. ## 4. Схемы -Поля без `?` есть всегда; `null` указан явно. +Поля без `?` есть всегда; `null` указан явно. `money` — строка с двумя знаками, `share` — строка с двумя знаками. **`GoodwillTableOut`** ``` -version_id uuid -goodwill_enabled bool -editable bool -read_only_reason null | "VERSION_NOT_EDITABLE" | "LOCK_REQUIRED" | "LOCKED_BY_OTHER" -rows[] GoodwillRowOut -totals GoodwillTotalsOut +version_id uuid +goodwill_enabled bool +editable bool +read_only_reason null | "VERSION_NOT_EDITABLE" | "LOCK_REQUIRED" | "LOCKED_BY_OTHER" +rows[] GoodwillRowOut +groups[] GoodwillGroupOut итог «Всего» по каждой ГО +totals GoodwillTotalsOut «Итого по листу» ``` **`GoodwillRowOut`** ``` -head_legal_entity Ref {id, name} +head_legal_entity Ref {id, name} subsidiary_legal_entity Ref -ownership_share share | null % 4 знака; null — ещё не введена -financial_investment_amount money -charter_capital_amount money -additional_capital_amount money -participation_amount money -goodwill_amount money может быть отрицательным -include_in_kfo bool -comment string | null -updated_by Ref | null -updated_at datetime | null +ownership_share share | null +financial_investment_amount money | null null у «Нет вложений» +charter_capital_amount money | null null — нет однозначного 1310 или «Нет вложений» +additional_capital_amount money | null справочно, в формулу не входит +participation_amount money | null только у действующей отмеченной строки +goodwill_amount money | null может быть < 0 и = 0 +include_in_kfo bool +comment string | null +has_investments bool +warnings[] {code, message} +updated_by Ref | null +updated_at datetime | null ``` -**`GoodwillTotalsOut`** — сумма по всем строкам ответа: `{financial_investment_amount, participation_amount, -goodwill_amount}`, плюс `goodwill_in_kfo_amount` — сумма `goodwill_amount` только по строкам с `include_in_kfo = true` -и положительным `goodwill_amount` (то, что реально уйдёт в `kfo_result_line.line_code = 'GOODWILL'`). +**`GoodwillGroupOut`** `{head_legal_entity: Ref, totals: GoodwillTotalsOut}`. -**`GoodwillRowIn`** `{ownership_share (share-строка 0…100), include_in_kfo (bool), comment (string | null, обязателен -при include_in_kfo = true, ≤ 1000 символов)}`. +**`GoodwillTotalsOut`** — по действующим строкам (без «Нет вложений»): +``` +financial_investment_amount money все действующие строки, независимо от галочки +charter_capital_amount money все действующие строки (УК одной ДО у нескольких ГО входит несколько раз) +participation_amount money только отмеченные +goodwill_amount money только отмеченные, отрицательные и нулевые тоже +goodwill_in_kfo_amount money положительные G отмеченных строк с долей и комментарием; 0.00 при goodwill_enabled = false +``` -**`GoodwillSettingsIn`** `{goodwill_enabled (bool)}`. +**`GoodwillBatchIn`** `{rows: GoodwillRowKeyIn[]}` (минимум одна строка). -## 5. Открытые вопросы (AURORA-1369, срок 02.11.2026) +**`GoodwillRowKeyIn`** `{head_legal_entity_id: uuid, subsidiary_legal_entity_id: uuid, ownership_share: string | null, +include_in_kfo: bool, comment: string | null (≤ 1000)}`. -- **OQ-10** — входит ли стр. 1350 (добавочный капитал) в базу участия. Допущение сейчас: **нет**, `participation_amount` - считается только от `charter_capital_amount`; `additional_capital_amount` хранится и показывается в таблице (колонка - есть и в DBML, и на фронте — макет её не показывает явно, но поле в `goodwill` есть с пометкой ADD), но не входит в - формулу. Если методологи подтвердят, что 1350 нужен — меняется только формула на бэкенде, контракт не меняется. -- **OQ-37** — код строки «Гудвилл» в результате КФО: служебный `GOODWILL` (как в DBML, принято этим контрактом) или - `1105` как в распознанной форме ББ (ФСБУ 4/2023). Контракта это не касается напрямую (он про таблицу `goodwill`, не - про `kfo_result_line`), но влияет на отдельную задачу построения КФО — не блокирует 1543/1544. -- Несколько `is_head_entity` в одном периметре — не встречалось на фикстурах, поведение не проверено отдельно. +**`GoodwillSettingsIn`** `{goodwill_enabled: bool}`. -**Фронт (AURORA-1544) ничего из этого не ждёт** — строит экран по схемам §4 на фейковых данных; поля не меняются от -исхода открытых вопросов, меняются только числа внутри них. +## 5. Граница этапа и открытые вопросы -## 6. Пример +- **КФО.** Применение гудвилла в КФО (1170 ГО − ФВ, 1310 ДО − P, строка `GOODWILL` + G), исключение обычного ББ-2 по + паре, пометка результата КФО устаревшим и фиксация при VERIFIED — задача AURORA-1546, этим API не делается. +- **OQ-10** — 1350 в базу участия не входит (DOC-259025 §6: «Для УК используется 1310»), колонка — справочно. +- **OQ-37** — служебный код `GOODWILL` для строки КФО (DOC-259025 §7). + +## 6. Что изменилось против черновика 30.10 + +- ГО — по фактическим вложениям 58.01, а не по `is_head_entity`; строк «все ДО под каждой ГО» больше нет. +- Новые поля строки: `has_investments`, `warnings`; УК, 1350, P, G и ФВ — nullable. +- Новые поля ответа: `groups[]`, `totals.charter_capital_amount`. +- P и G — только у отмеченных строк; итоги P и G — по отмеченным. +- Доля — два знака, а не четыре. +- Построчный `PUT …/goodwill/{head}/{sub}` заменён пакетным `PUT …/goodwill` (одна транзакция, ответ — вся таблица). +- У экспорта — параметр `sign`. + +## 7. Пример `GET /api/v1/consolidations/versions/7b…/goodwill` (сокращено): @@ -166,25 +189,72 @@ goodwill_amount}`, плюс `goodwill_in_kfo_amount` — сумма `goodwill_am "read_only_reason": null, "rows": [ { - "head_legal_entity": {"id": "a…", "name": "АО «Русский бисквит»"}, - "subsidiary_legal_entity": {"id": "b…", "name": "ООО «Пекарня Счастливочная»"}, - "ownership_share": "100.0000", - "financial_investment_amount": "5000000.00", - "charter_capital_amount": "4500000.00", + "head_legal_entity": {"id": "a…", "name": "Альфа"}, + "subsidiary_legal_entity": {"id": "b…", "name": "Бета"}, + "ownership_share": "80.00", + "financial_investment_amount": "180000.00", + "charter_capital_amount": "100000.00", "additional_capital_amount": "0.00", - "participation_amount": "4500000.00", - "goodwill_amount": "500000.00", + "participation_amount": "80000.00", + "goodwill_amount": "100000.00", "include_in_kfo": true, - "comment": "Доля подтверждена уставом от 01.01.2024", + "comment": "Доля подтверждена уставом", + "has_investments": true, + "warnings": [], "updated_by": {"id": "u…", "name": "Иванов И. И."}, - "updated_at": "2026-10-30T12:00:00+03:00" + "updated_at": "2026-11-06T12:00:00Z" + }, + { + "head_legal_entity": {"id": "a…", "name": "Альфа"}, + "subsidiary_legal_entity": {"id": "c…", "name": "Гамма"}, + "ownership_share": "80.00", + "financial_investment_amount": "50000.00", + "charter_capital_amount": "100000.00", + "additional_capital_amount": null, + "participation_amount": "80000.00", + "goodwill_amount": "-30000.00", + "include_in_kfo": true, + "comment": "Доля подтверждена уставом", + "has_investments": true, + "warnings": [], + "updated_by": null, + "updated_at": null + } + ], + "groups": [ + { + "head_legal_entity": {"id": "a…", "name": "Альфа"}, + "totals": { + "financial_investment_amount": "230000.00", + "charter_capital_amount": "200000.00", + "participation_amount": "160000.00", + "goodwill_amount": "70000.00", + "goodwill_in_kfo_amount": "100000.00" + } } ], "totals": { - "financial_investment_amount": "5000000.00", - "participation_amount": "4500000.00", - "goodwill_amount": "500000.00", - "goodwill_in_kfo_amount": "500000.00" + "financial_investment_amount": "230000.00", + "charter_capital_amount": "200000.00", + "participation_amount": "160000.00", + "goodwill_amount": "70000.00", + "goodwill_in_kfo_amount": "100000.00" } } ``` + +`PUT /api/v1/consolidations/versions/7b…/goodwill`: + +```json +{"rows": [ + {"head_legal_entity_id": "a…", "subsidiary_legal_entity_id": "b…", "ownership_share": "80", "include_in_kfo": true, "comment": "Доля подтверждена уставом"}, + {"head_legal_entity_id": "a…", "subsidiary_legal_entity_id": "d…", "ownership_share": null, "include_in_kfo": false, "comment": null} +]} +``` + +Ошибка строки: + +```json +{"detail": {"code": "COMMENT_REQUIRED", "message": "Укажите комментарий", + "head_legal_entity_id": "a…", "subsidiary_legal_entity_id": "b…"}} +``` diff --git a/dev/api-contract-goodwill.openapi.yaml b/dev/api-contract-goodwill.openapi.yaml index 892176f..4f28cfa 100644 --- a/dev/api-contract-goodwill.openapi.yaml +++ b/dev/api-contract-goodwill.openapi.yaml @@ -1,278 +1,427 @@ openapi: 3.1.0 info: - title: АРМ КА — таблица гудвилла - version: 0.1.0-draft - description: > - Черновик контракта AURORA-1543 (бэкенд) / AURORA-1544 (фронт). Текстовое описание, источники и открытые вопросы — - dev/api-contract-goodwill.md; оба файла держатся одинаковыми. Деньги — строки с двумя знаками в рублях, доля - владения — строка с четырьмя знаками. Чтение — CONSOLIDATION_VIEW, правки — CONSOLIDATION_EDIT. -servers: - - url: /api/v1 -security: - - bearerAuth: [] - + title: АРМ КА — таблица гудвилла (AURORA-1543/1544) + version: '1.0' paths: - /consolidations/versions/{version_id}/goodwill: + /api/v1/consolidations/versions/{version_id}/goodwill: get: - operationId: getGoodwillTable - summary: Таблица гудвилла версии - x-permission: CONSOLIDATION_VIEW + tags: + - consolidations + summary: Table + operationId: table_api_v1_consolidations_versions__version_id__goodwill_get + security: + - bearerAuth: [] parameters: - - $ref: '#/components/parameters/VersionId' + - name: version_id + in: path + required: true + schema: + type: string + format: uuid + title: Version Id responses: '200': - description: Строки пар (ГО, ДО) периметра, итоги + description: Successful Response content: application/json: - schema: { $ref: '#/components/schemas/GoodwillTableOut' } - '401': { $ref: '#/components/responses/Unauthorized' } - '403': { $ref: '#/components/responses/Forbidden' } - '404': { $ref: '#/components/responses/DomainError' } - - /consolidations/versions/{version_id}/goodwill-settings: + schema: + $ref: '#/components/schemas/GoodwillTableOut' + '422': + description: Validation Error + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPValidationError' put: - operationId: saveGoodwillSettings - summary: Переключатель расчёта гудвилла версии (consolidation_version.goodwill_enabled) - description: > - Аудит GOODWILL_ENABLED_CHANGE; результат КФО устаревает при любом изменении значения. Выключение не стирает - сохранённые строки (ownership_share, comment, include_in_kfo). - x-permission: CONSOLIDATION_EDIT + tags: + - consolidations + summary: Save + description: Пакет строк одной транзакцией; ошибка любой строки — 404/422 с её ключом, ничего не сохраняется. + operationId: save_api_v1_consolidations_versions__version_id__goodwill_put + security: + - bearerAuth: [] parameters: - - $ref: '#/components/parameters/VersionId' + - name: version_id + in: path + required: true + schema: + type: string + format: uuid + title: Version Id requestBody: required: true content: application/json: - schema: { $ref: '#/components/schemas/GoodwillSettingsIn' } + schema: + $ref: '#/components/schemas/GoodwillBatchIn' responses: '200': - description: Таблица с обновлённым флагом + description: Successful Response content: application/json: - schema: { $ref: '#/components/schemas/GoodwillTableOut' } - '401': { $ref: '#/components/responses/Unauthorized' } - '403': { $ref: '#/components/responses/Forbidden' } - '404': { $ref: '#/components/responses/DomainError' } - '409': { $ref: '#/components/responses/DomainError' } - - /consolidations/versions/{version_id}/goodwill/{head_legal_entity_id}/{subsidiary_legal_entity_id}: + schema: + $ref: '#/components/schemas/GoodwillTableOut' + '422': + description: Validation Error + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPValidationError' + /api/v1/consolidations/versions/{version_id}/goodwill-settings: put: - operationId: saveGoodwillRow - summary: Сохранить строку гудвилла (создать или заменить целиком) - description: > - Аудит GOODWILL_ROW_EDIT; результат КФО устаревает. Вычисляемые поля (financial_investment_amount, - charter_capital_amount, additional_capital_amount, participation_amount, goodwill_amount) в тело запроса не - входят — пересчитываются сервером. 404 — версия или пара (head/subsidiary) не из периметра; 409 — правок - версии; 422 — COMMENT_REQUIRED (include_in_kfo = true без комментария), INVALID_SHARE (вне 0…100 или больше - 4 знаков после запятой). - x-permission: CONSOLIDATION_EDIT + tags: + - consolidations + summary: Settings + operationId: settings_api_v1_consolidations_versions__version_id__goodwill_settings_put + security: + - bearerAuth: [] parameters: - - $ref: '#/components/parameters/VersionId' - - $ref: '#/components/parameters/HeadLegalEntityId' - - $ref: '#/components/parameters/SubsidiaryLegalEntityId' + - name: version_id + in: path + required: true + schema: + type: string + format: uuid + title: Version Id requestBody: required: true content: application/json: - schema: { $ref: '#/components/schemas/GoodwillRowIn' } + schema: + $ref: '#/components/schemas/GoodwillSettingsIn' responses: '200': - description: Пересчитанная строка + description: Successful Response content: application/json: - schema: { $ref: '#/components/schemas/GoodwillRowOut' } - '401': { $ref: '#/components/responses/Unauthorized' } - '403': { $ref: '#/components/responses/Forbidden' } - '404': { $ref: '#/components/responses/DomainError' } - '409': { $ref: '#/components/responses/DomainError' } - '422': { $ref: '#/components/responses/DomainOrValidationError' } - - /consolidations/versions/{version_id}/goodwill/export: + schema: + $ref: '#/components/schemas/GoodwillTableOut' + '422': + description: Validation Error + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPValidationError' + /api/v1/consolidations/versions/{version_id}/goodwill/export: get: - operationId: exportGoodwillTable - summary: Выгрузка таблицы гудвилла в xlsx - description: Аудит EXPORT. - x-permission: CONSOLIDATION_VIEW + tags: + - consolidations + summary: Export + operationId: export_api_v1_consolidations_versions__version_id__goodwill_export_get + security: + - bearerAuth: [] parameters: - - $ref: '#/components/parameters/VersionId' + - name: version_id + in: path + required: true + schema: + type: string + format: uuid + title: Version Id + - name: sign + in: query + required: false + schema: + enum: + - all + - positive + - negative + - zero + type: string + default: all + title: Sign responses: '200': - description: Файл xlsx + description: Successful Response content: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet: - schema: { type: string, format: binary } - '401': { $ref: '#/components/responses/Unauthorized' } - '403': { $ref: '#/components/responses/Forbidden' } - '404': { $ref: '#/components/responses/DomainError' } - + schema: + type: string + format: binary + '422': + description: Validation Error + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPValidationError' components: - securitySchemes: - bearerAuth: { type: http, scheme: bearer, bearerFormat: JWT } - - parameters: - VersionId: - name: version_id - in: path - required: true - schema: { type: string, format: uuid } - HeadLegalEntityId: - name: head_legal_entity_id - in: path - required: true - description: Член периметра с is_head_entity = true - schema: { type: string, format: uuid } - SubsidiaryLegalEntityId: - name: subsidiary_legal_entity_id - in: path - required: true - description: Любой другой член периметра - schema: { type: string, format: uuid } - - responses: - Unauthorized: - description: Нет или плохой токен - Forbidden: - description: Нет права - content: - application/json: - schema: - type: object - properties: { detail: { type: string } } - DomainError: - description: Доменная ошибка - content: - application/json: - schema: { $ref: '#/components/schemas/DomainErrorOut' } - DomainOrValidationError: - description: Доменная ошибка или массив ошибок валидации pydantic - content: - application/json: - schema: - oneOf: - - $ref: '#/components/schemas/DomainErrorOut' - - type: object - properties: - detail: - type: array - items: - type: object - properties: - loc: { type: array, items: {} } - msg: { type: string } - type: { type: string } - schemas: - Money: - type: string - pattern: '^-?\d+\.\d{2}$' - description: Сумма в рублях, два знака; может быть отрицательной (goodwill_amount) - examples: ['500000.00'] - Share: - type: [string, 'null'] - pattern: '^\d{1,3}\.\d{4}$' - description: Доля владения, % (numeric(7,4)); null — ещё не введена - ShareIn: - type: string - pattern: '^\d{1,3}(\.\d{1,4})?$' - description: 0…100, до 4 знаков после запятой; вне диапазона — 422 INVALID_SHARE - Ref: - type: object - required: [id, name] + GoodwillBatchIn: properties: - id: { type: string, format: uuid } - name: { type: string } - - DomainErrorOut: - type: object - required: [detail] - properties: - detail: - type: object - required: [code, message] - properties: - code: - type: string - enum: - - NOT_FOUND - - VERSION_NOT_EDITABLE - - LOCK_REQUIRED - - LOCKED_BY_OTHER - - COMMENT_REQUIRED - - INVALID_SHARE - message: { type: string, description: Текст по-русски } - additionalProperties: true - - GoodwillTableOut: - type: object - required: [version_id, goodwill_enabled, editable, read_only_reason, rows, totals] - properties: - version_id: { type: string, format: uuid } - goodwill_enabled: { type: boolean } - editable: { type: boolean } - read_only_reason: - type: [string, 'null'] - enum: [null, VERSION_NOT_EDITABLE, LOCK_REQUIRED, LOCKED_BY_OTHER] rows: + items: + $ref: '#/components/schemas/GoodwillRowKeyIn' type: array - items: { $ref: '#/components/schemas/GoodwillRowOut' } - totals: { $ref: '#/components/schemas/GoodwillTotalsOut' } - - GoodwillRowOut: + minItems: 1 + title: Rows + additionalProperties: false type: object required: - - head_legal_entity - - subsidiary_legal_entity - - ownership_share - - financial_investment_amount - - charter_capital_amount - - additional_capital_amount - - participation_amount - - goodwill_amount - - include_in_kfo - - comment - - updated_by - - updated_at + - rows + title: GoodwillBatchIn + description: Пакет строк таблицы гудвилла; сохраняется целиком или не сохраняется. + GoodwillGroupOut: properties: - head_legal_entity: { $ref: '#/components/schemas/Ref' } - subsidiary_legal_entity: { $ref: '#/components/schemas/Ref' } - ownership_share: { $ref: '#/components/schemas/Share' } - financial_investment_amount: { $ref: '#/components/schemas/Money' } - charter_capital_amount: { $ref: '#/components/schemas/Money' } - additional_capital_amount: { $ref: '#/components/schemas/Money' } - participation_amount: { $ref: '#/components/schemas/Money' } - goodwill_amount: { $ref: '#/components/schemas/Money' } - include_in_kfo: { type: boolean } - comment: { type: [string, 'null'] } - updated_by: - oneOf: - - $ref: '#/components/schemas/Ref' - - type: 'null' - updated_at: { type: [string, 'null'], format: date-time } - - GoodwillTotalsOut: + head_legal_entity: + $ref: '#/components/schemas/RefOut' + totals: + $ref: '#/components/schemas/GoodwillTotalsOut' type: object - required: [financial_investment_amount, participation_amount, goodwill_amount, goodwill_in_kfo_amount] + required: + - head_legal_entity + - totals + title: GoodwillGroupOut + description: Итог «Всего» блока одной ГО. + GoodwillRowKeyIn: properties: - financial_investment_amount: { $ref: '#/components/schemas/Money' } - participation_amount: { $ref: '#/components/schemas/Money' } - goodwill_amount: { $ref: '#/components/schemas/Money' } - goodwill_in_kfo_amount: - allOf: [{ $ref: '#/components/schemas/Money' }] - description: Сумма goodwill_amount только по строкам include_in_kfo = true с положительным goodwill_amount - - GoodwillRowIn: - type: object - required: [ownership_share, include_in_kfo, comment] - properties: - ownership_share: { $ref: '#/components/schemas/ShareIn' } - include_in_kfo: { type: boolean } + ownership_share: + anyOf: + - type: string + - type: 'null' + title: Ownership Share + include_in_kfo: + type: boolean + title: Include In Kfo comment: - type: [string, 'null'] - maxLength: 1000 - description: Обязателен (непустой после обрезки), если include_in_kfo = true - - GoodwillSettingsIn: + anyOf: + - type: string + maxLength: 1000 + - type: 'null' + title: Comment + head_legal_entity_id: + type: string + format: uuid + title: Head Legal Entity Id + subsidiary_legal_entity_id: + type: string + format: uuid + title: Subsidiary Legal Entity Id + additionalProperties: false type: object - required: [goodwill_enabled] + required: + - ownership_share + - include_in_kfo + - head_legal_entity_id + - subsidiary_legal_entity_id + title: GoodwillRowKeyIn + description: 'Строка пакетного сохранения: пара ГО → ДО и ручной ввод.' + GoodwillRowOut: properties: - goodwill_enabled: { type: boolean } + head_legal_entity: + $ref: '#/components/schemas/RefOut' + subsidiary_legal_entity: + $ref: '#/components/schemas/RefOut' + ownership_share: + anyOf: + - type: string + - type: 'null' + title: Ownership Share + financial_investment_amount: + anyOf: + - type: string + - type: 'null' + title: Financial Investment Amount + charter_capital_amount: + anyOf: + - type: string + - type: 'null' + title: Charter Capital Amount + additional_capital_amount: + anyOf: + - type: string + - type: 'null' + title: Additional Capital Amount + participation_amount: + anyOf: + - type: string + - type: 'null' + title: Participation Amount + goodwill_amount: + anyOf: + - type: string + - type: 'null' + title: Goodwill Amount + include_in_kfo: + type: boolean + title: Include In Kfo + comment: + anyOf: + - type: string + - type: 'null' + title: Comment + has_investments: + type: boolean + title: Has Investments + warnings: + items: + $ref: '#/components/schemas/WarningOut' + type: array + title: Warnings + updated_by: + anyOf: + - $ref: '#/components/schemas/RefOut' + - type: 'null' + updated_at: + anyOf: + - type: string + format: date-time + - type: 'null' + title: Updated At + type: object + required: + - head_legal_entity + - subsidiary_legal_entity + - ownership_share + - financial_investment_amount + - charter_capital_amount + - additional_capital_amount + - participation_amount + - goodwill_amount + - include_in_kfo + - comment + - has_investments + - warnings + - updated_by + - updated_at + title: GoodwillRowOut + GoodwillSettingsIn: + properties: + goodwill_enabled: + type: boolean + title: Goodwill Enabled + additionalProperties: false + type: object + required: + - goodwill_enabled + title: GoodwillSettingsIn + GoodwillTableOut: + properties: + version_id: + type: string + format: uuid + title: Version Id + goodwill_enabled: + type: boolean + title: Goodwill Enabled + editable: + type: boolean + title: Editable + read_only_reason: + anyOf: + - type: string + enum: + - VERSION_NOT_EDITABLE + - LOCK_REQUIRED + - LOCKED_BY_OTHER + - type: 'null' + title: Read Only Reason + rows: + items: + $ref: '#/components/schemas/GoodwillRowOut' + type: array + title: Rows + groups: + items: + $ref: '#/components/schemas/GoodwillGroupOut' + type: array + title: Groups + totals: + $ref: '#/components/schemas/GoodwillTotalsOut' + type: object + required: + - version_id + - goodwill_enabled + - editable + - read_only_reason + - rows + - groups + - totals + title: GoodwillTableOut + GoodwillTotalsOut: + properties: + financial_investment_amount: + type: string + title: Financial Investment Amount + charter_capital_amount: + type: string + title: Charter Capital Amount + participation_amount: + type: string + title: Participation Amount + goodwill_amount: + type: string + title: Goodwill Amount + goodwill_in_kfo_amount: + type: string + title: Goodwill In Kfo Amount + type: object + required: + - financial_investment_amount + - charter_capital_amount + - participation_amount + - goodwill_amount + - goodwill_in_kfo_amount + title: GoodwillTotalsOut + HTTPValidationError: + properties: + detail: + items: + $ref: '#/components/schemas/ValidationError' + type: array + title: Detail + type: object + title: HTTPValidationError + RefOut: + properties: + id: + type: string + format: uuid + title: Id + name: + type: string + title: Name + type: object + required: + - id + - name + title: RefOut + description: 'Ссылка на объект справочника: идентификатор и название (ГК, филиал, ГСК/ГСЗ).' + ValidationError: + properties: + loc: + items: + anyOf: + - type: string + - type: integer + type: array + title: Location + msg: + type: string + title: Message + type: + type: string + title: Error Type + input: + title: Input + ctx: + type: object + title: Context + type: object + required: + - loc + - msg + - type + title: ValidationError + WarningOut: + properties: + code: + type: string + title: Code + message: + type: string + title: Message + type: object + required: + - code + - message + title: WarningOut From 6c7408a9aa048db691a95a580749134e3a115288 Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Fri, 6 Nov 2026 18:52:59 +0400 Subject: [PATCH 2/4] =?UTF-8?q?=D0=93=D1=83=D0=B4=D0=B2=D0=B8=D0=BB=D0=BB:?= =?UTF-8?q?=20=D0=B2=20=D1=8D=D0=BA=D1=81=D0=BF=D0=BE=D1=80=D1=82=D0=B5=20?= =?UTF-8?q?=D1=81=D1=82=D1=80=D0=BE=D0=BA=D0=B0=20=C2=AB=D0=9D=D0=B5=D1=82?= =?UTF-8?q?=20=D0=B2=D0=BB=D0=BE=D0=B6=D0=B5=D0=BD=D0=B8=D0=B9=C2=BB=20?= =?UTF-8?q?=D0=BF=D0=BE=D0=BC=D0=B5=D1=87=D0=B5=D0=BD=D0=B0=20(#204)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- dev/api-contract-goodwill.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dev/api-contract-goodwill.md b/dev/api-contract-goodwill.md index a300789..5c1fb1d 100644 --- a/dev/api-contract-goodwill.md +++ b/dev/api-contract-goodwill.md @@ -104,7 +104,8 @@ Лист «Гудвилл»: восемь колонок экранной таблицы (Учёт в отчёте, Наименование дочерней организации, Доля владения, Фондовые вложения головной организации, Доля участия ГО в УК, Уставный капитал, Гудвилл (+/−), Комментарий), блок на -каждую ГО со строкой «Всего», в конце — «Итого по листу». Деньги — в рублях, `null` — пустая ячейка. `sign` оставляет +каждую ГО со строкой «Всего», в конце — «Итого по листу». Деньги — в рублях, `null` — пустая ячейка; у строки «Нет +вложений» в колонке ФВ — текст «Нет вложений». `sign` оставляет строки с гудвиллом нужного знака (нерассчитанные в фильтры по знаку не попадают), итоги остаются по полному составу. Выгружается последнее сохранённое состояние. Имя — `Гудвилл_<ГК>_<год>_<квартал>.xlsx` в `Content-Disposition` (`filename*`). Аудит `EXPORT` с `{report: "goodwill", rows: <число строк в файле>}`. From dc374a54a4fb5fbd063bfa63d7130b901ba80818 Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Fri, 6 Nov 2026 22:57:45 +0400 Subject: [PATCH 3/4] =?UTF-8?q?AURORA-1864:=20=D0=B3=D1=80=D1=83=D0=BF?= =?UTF-8?q?=D0=BF=D0=B0=20=D0=B0=D0=BB=D1=8C=D1=82=D0=B5=D1=80=D0=BD=D0=B0?= =?UTF-8?q?=D1=82=D0=B8=D0=B2=20=D1=81=D0=BE=D0=BE=D1=82=D0=BD=D0=BE=D1=88?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B9=202400,=20=D0=B7=D0=BD=D0=B0=D0=BA=202?= =?UTF-8?q?410=20=D0=BD=D0=B5=20=D0=BE=D0=BF=D1=80=D0=B5=D0=B4=D0=B5=D0=BB?= =?UTF-8?q?=D1=8F=D0=B5=D0=BC=20(RSHB/pylib=5Fai=5Farm=5Fstatements#3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit §7: правило group в statement_checks.yaml, соотношения 2400 полного и упрощённого наборов — альтернативы; OQ-12 закрыт в части 2410. Change statement-checks-alternatives: требование «Шаг 7» спеки statement-normalization. Co-Authored-By: Claude Opus 5.5 --- context/05-domain-rules.md | 22 ++++-- context/07-open-questions.md | 2 +- .../.openspec.yaml | 2 + .../statement-checks-alternatives/design.md | 30 ++++++++ .../statement-checks-alternatives/proposal.md | 34 +++++++++ .../specs/statement-normalization/spec.md | 74 +++++++++++++++++++ 6 files changed, 157 insertions(+), 7 deletions(-) create mode 100644 openspec/changes/statement-checks-alternatives/.openspec.yaml create mode 100644 openspec/changes/statement-checks-alternatives/design.md create mode 100644 openspec/changes/statement-checks-alternatives/proposal.md create mode 100644 openspec/changes/statement-checks-alternatives/specs/statement-normalization/spec.md diff --git a/context/05-domain-rules.md b/context/05-domain-rules.md index 6059607..18f02f7 100644 --- a/context/05-domain-rules.md +++ b/context/05-domain-rules.md @@ -828,7 +828,8 @@ one_sided = ровно одна из amount_a, amount_b равна 0 (знак `1200 = 1210 + 1220 + 1230 + 1240 + 1250 + 1260`; `1300 = 1310 + 1320 + 1340 + 1350 + 1360 + 1370`; `1400 = 1410 + 1420 + 1430 + 1450`; `1500 = 1510 + 1520 + 1540 + 1550`; `1600 = 1100 + 1200`; `1700 = 1300 + 1400 + 1500`. ОФР: `2100 = 2110 − 2120`; `2200 = 2100 − 2210 − 2220`; `2300 = 2200 + 2310 + 2320 − 2330 + 2340 − 2350`; -`2400 = 2300 − 2410 + 2420` (знаки 2410/2420/2430 — по ФСБУ 4/2023 уточнить [открыто]). +`2400 = 2300 − 2410 + 2420` или `2400 = 2300 + 2410 + 2460` (знак 2410 не определяется — см. группу альтернатив ниже; +знаки 2420/2430 по ФСБУ 4/2023 — OQ-12 [открыто]). Межформенная: `1370(отчётная дата) − 1370(начало года) = 2400(ОФР за период)` — при расхождении КА может акцептовать с комментарием (`financial_statement_meta.checks_accepted_*`). @@ -839,9 +840,9 @@ one_sided = ровно одна из amount_a, amount_b равна 0 (знак пересчитывает тем же кодом только после правок аналитика; своей копии словаря у ядра нет. Правила счёта: - **Жёсткие** (нарушение → `needs_check`): ББ — `1100`, `1200` (+1215), `1300`, `1400`, `1500`, `1600 = 1100 + 1200`, - `1700 = 1300 + 1400 + 1500`, `1600 = 1700`; ОФР — `2100`, `2200`, `2300`. **Мягкие** (`info`, `ASSUMPTION(OQ-12)`): `2400 = - 2300 − 2410 + 2420` и варианты ФСБУ 4/2023 (`2400 = 2300 + 2410 + 2460`, `2410 = 2411 + 2412`), `2500 = 2400 + 2510 + - 2520 + 2530`, «сумма пятизначных подстрок = родитель» (по колонке — только если хотя бы одна подстрока родителя в ней + `1700 = 1300 + 1400 + 1500`, `1600 = 1700`; ОФР — `2100`, `2200`, `2300`. **Мягкие** (`info`): `2400 = + 2300 − 2410 + 2420` и `2400 = 2300 + 2410 + 2460` (группа альтернатив, ниже), `2410 = 2411 + 2412`, `2500 = 2400 + + 2510 + 2520 + 2530`, «сумма пятизначных подстрок = родитель» (по колонке — только если хотя бы одна подстрока родителя в ней непуста: `amount` не `null`, ноль — значение; AURORA-1527). Мягкое соотношение на статус и подсветку не влияет, но **сошедшееся точно** (расхождение в пределах допуска ±1, без «округления») подтверждает неуверенные значения своих кодов (Р-21, §11.9): так покрываются 2400, 2410, 2411, 2412, 2460, 25xx и пятизначные подстроки. @@ -862,12 +863,21 @@ one_sided = ровно одна из amount_a, amount_b равна 0 (знак с этими членами по модулю (1С выгружает расходы со знаком минус, 66н — в скобках). Такое соотношение сходится при любом знаке члена и потому **подтверждает величину, но не знак**: знак сомнительной ячейки подтверждается конвенцией колонки (§11.9). +- **Группа альтернатив** (`group` в `statement_checks.yaml`; AURORA-1864, решение владельца 27.10.2026). Мягкие + соотношения одной цели одной формы и варианта могут быть альтернативами: в колонке сошлась хоть одна — нарушений + группы нет; не сошлась ни одна — в `soft_relations_failed` одно нарушение, ближайшая к цели альтернатива (при пустой + цели или пустых членах — первая по словарю). `group` только у мягких и с одной целью — иначе словарь не загружается. + Группы — соотношения 2400 обоих наборов: **знак 2410 не определяется**, принимается любой вариант (расход — + `2300 − 2410`, со знаком минус, в скобках или без знака; доход по налогу ФСБУ 4/2023 — `2300 + 2410`). Жёсткие + проверки, подсветка, `needs_check` и подтверждение значений (Р-21) группой не меняются; каждая альтернатива + подтверждает свои коды, как отдельное мягкое соотношение. Формула старого бланка с 2430/2450 не добавляется. - **Упрощённый набор.** Набор выбирается по `form_variant` документа (§11.7): у упрощённой формы нет подытогов 1100 / 1200 (ББ) и 2100 / 2200 / 2300 (ОФР), свои равенства по бланку упрощённой формы (приказ 66н, прил. 5; `ASSUMPTION` до первого образца в fixtures): ББ — `1600 = 1150 + 1170 + 1210 + 1230 + 1250` (жёсткое), `1700 = 1300 + 1350 + 1360 + 1410 + 1450 + 1510 + 1520 + 1550` (жёсткое), `1600 = 1700` (жёсткое); ОФР — `2400 = 2110 − 2120 − 2330 + - 2340 − 2350 − 2410` (**мягкое**, как `2400` полного набора: знак 2410 не определён, OQ-12; `abs_terms` те же; решение - владельца 18.10.2026, Р-22 / S-5). Оба набора — в одном словаре `statement_checks.yaml`. Без упрощённого набора полный + 2340 − 2350 − 2410` и `2400 = 2110 − 2120 − 2330 + 2340 − 2350 + 2410` (**мягкие**, группа альтернатив, как `2400` + полного набора; `abs_terms` — 2120, 2330, 2350, у первого ещё 2410; решение владельца 18.10.2026, Р-22 / S-5; + вторая альтернатива — AURORA-1864). Оба набора — в одном словаре `statement_checks.yaml`. Без упрощённого набора полный дал бы нарушения «пустой цели» на каждой упрощённой форме. `form_variant = null` (документ, оставшийся в БД с времён до v5.19, если база не очищена) — полный набор; у результата 1.x вариант определяется по структуре кодов при приёме. - **Родитель пятизначной подстроки** — ближайшая четырёхзначная строка выше с теми же первыми тремя цифрами, иначе код diff --git a/context/07-open-questions.md b/context/07-open-questions.md index b8cdb4c..0fe24a7 100644 --- a/context/07-open-questions.md +++ b/context/07-open-questions.md @@ -17,7 +17,7 @@ | OQ-09 | Правила элиминации ОФР (2110/2120/2310/2320/2330): точный состав субсчетов, источник средневзвешенной ставки НДС, выбор субсчетов «работником ПА самостоятельно» | FNR03 | срез 6 | не реализовывать авто-элиминацию ОФР до решения; только ручные `correction` | | OQ-10 | Гудвилл: входит ли стр. 1350 (добавочный капитал) в базу участия; УК из ББ (1310) или из ОСВ 80/83 | DBML, DOC-259025, FNR03 | срез 6 | допущение: 1310 из ББ; 1350 — настраиваемый флаг, по умолчанию выключен. **06.11:** DOC-259025 (Лысенко, 03.11) — «для УК используется 1310», 1350 в формулу не входит; так реализовано (arm-core #222), флага нет; подтверждение методологов (AURORA-1369) не получено | | OQ-11 | Строка «ППА» в КФО (правило № 1, когда у А нет ОСВ 03) — что это и куда | FNR03 | срез 6 | не реализовывать | -| OQ-12 | Формулы ОФР: знаки 2410/2420/2430 по ФСБУ 4/2023 vs 66н; состав 1100 (1120–1140) | DOC-221281 | срез 3 | допущение: формулы §7, строки без значения = 0; **расширение 07.10.2026:** формулы и справочники по редакциям форм (`66n` / `fsbu4`) — OQ-146 | +| OQ-12 | Формулы ОФР: знаки 2410/2420/2430 по ФСБУ 4/2023 vs 66н; состав 1100 (1120–1140) | DOC-221281 | срез 3 | допущение: формулы §7, строки без значения = 0; **06.11.2026 закрыт в части 2410** (AURORA-1864, решение владельца 27.10.2026): знак 2410 не определяется, соотношения 2400 — группа альтернатив §7 (`2300 − 2410` и `2300 + 2410`); 2420/2430 и состав 1100 — открыты; **расширение 07.10.2026:** формулы и справочники по редакциям форм (`66n` / `fsbu4`) — OQ-146 | | OQ-13 | `confidence` от ИС Сканирование — доли 0..1 или проценты 0..100; имя поля организации (`full_name` vs `form_name`) | FNR02 vs `aep_*.json` | срез 3 (scan-gateway) | **закрыт для scan-gateway (04.10.2026, по `aep_3.json`, `aep_6.json`):** `confidence` — числа 0–100 (не доли); название организации — `data_ocr.organization.form_name` (шумный OCR-текст), ИНН — `inn.form_name`, единицы — `units.form_name`, дата — `date.form_name` (в `aep_6.json` мусор); `full_name`/`inn.inn`/`units.measurement` из ТЗ в реальных файлах нет. Нормализация — Срез 3 | | OQ-14 | `reporting_date` в JSON ИС Сканирование — «ждём обновлённый JSON» | FNR01 | срез 3 | **уточнено (04.10.2026):** отдельного поля `reporting_date` в ответе нет; `date.form_name` ненадёжен (в `aep_6.json` — «Общество с ограниченной ответственностью», в `aep_3.json` — «2\|0\|2\|4\|»); даты колонок — в строке-шапке `rows[0]` таблицы («На 30 июня 2025 г» в `aep_6.json`), в `aep_3.json` шапки нет. Допущение прежнее: парсить из шапки таблицы, при её отсутствии — `NEEDS_CHECK`; решение — Срез 3 | | OQ-15 | Таймаут OCR 5 мин / 0 повторов (ТЗ 3) vs outbox с повторами (DBML) | DOC-037736 | срез 3 | решено DBML: `max_attempts` OCR = 1, парсер = 3 | diff --git a/openspec/changes/statement-checks-alternatives/.openspec.yaml b/openspec/changes/statement-checks-alternatives/.openspec.yaml new file mode 100644 index 0000000..8a94d76 --- /dev/null +++ b/openspec/changes/statement-checks-alternatives/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-11-06 diff --git a/openspec/changes/statement-checks-alternatives/design.md b/openspec/changes/statement-checks-alternatives/design.md new file mode 100644 index 0000000..b55f1be --- /dev/null +++ b/openspec/changes/statement-checks-alternatives/design.md @@ -0,0 +1,30 @@ +## Context + +Решение согласовано владельцем 27.10.2026 (комментарий в AURORA-1864): мягкие соотношения одной цели объединяются в +группу альтернатив, знак 2410 не определяется. Реализация — `arm_statements/checks.py` (`checks_from`) и загрузчик +словаря (`loader.py`); RSHB/pylib_ai_arm_statements#3. + +## Decisions + +- Группа считается в `checks_from` по паре (колонка, `group`) среди уже посчитанных исходов; `evaluate_relations` и + исходы для шага 8 не меняются — поэтому подсветка и Р-21 остаются прежними (каждая альтернатива подтверждает + значения сама, как раньше). +- «Сошлась» — расхождение в пределах допуска ±1 (`within_tolerance`), как для мягкого соотношения без группы. +- Проверки «`group` только у мягкого» и «у группы одна цель» — в загрузчике: жёсткая группа означала бы, что статус документа зависит от + выбора альтернативы, а этого решение не предусматривает. + +## Decisions made without discussion + +| Дата | Решение | Обоснование | Цена ошибки | +|---|---|---|---| +| 2026-11-06 | Имя группы в словаре — `net_profit` для обоих наборов | Группа — по форме и варианту (`relations_for`), одно имя не смешивает полный и упрощённый наборы; имя описывает цель 2400 | Нулевая: имя только в словаре, наружу не выходит | +| 2026-11-06 | Если у всех несошедшихся альтернатив пуста цель или все члены (сравнить не с чем) — выдаётся первая по словарю | Расхождения нет, «ближайшую» выбрать не по чему; порядок словаря детерминирован | Низкая: текст нарушения другой альтернативы, сам факт нарушения тот же | +| 2026-11-06 | При равном расхождении выдаётся первая по словарю | Детерминированность | Нулевая | +| 2026-11-06 | Тест `test_simplified_income_soft` (2400 = 500 при 1000 − 600 ± 50) теперь ожидает нарушение новой альтернативы `… + 2410` — она ближе к цели (450 против 350) | Прямое следствие правила «ближайшая к цели» | Нулевая: данные теста неверны в обоих вариантах | + +## Risks / Trade-offs + +- Новая альтернатива упрощённого набора (`… + 2410`) при совпадении подтверждает неуверенные значения своих кодов + (Р-21), включая положительный 2410 — это и есть желаемое поведение для дохода по налогу. +- Документы, загруженные до выкладки 0.1.4, сохраняют прежние `checks` до пересчёта (правка аналитика) — ложное + предупреждение у них останется; переразбор не планируется. diff --git a/openspec/changes/statement-checks-alternatives/proposal.md b/openspec/changes/statement-checks-alternatives/proposal.md new file mode 100644 index 0000000..a8ee594 --- /dev/null +++ b/openspec/changes/statement-checks-alternatives/proposal.md @@ -0,0 +1,34 @@ +## Why + +Замечание тестировщика заказчика 23.10.2026 (AURORA-1864, ООО «Фавор плюс», ООО «Фактор»): у ОФР с положительной строкой +2410 «Налог на прибыль» (доход по налогу, ФСБУ 4/2023) появляется мягкое нарушение «2400 = 2300 − 2410 + 2420», хотя +форма верна (2400 = 2300 + 2410). 2410 — член `abs_terms`: правая часть считается и как записано, и с |2410|, но оба +варианта вычитают 2410. Второе соотношение «2400 = 2300 + 2410 + 2460» на таких данных сходится, а первое всё равно +выдаёт предупреждение. Аналитик видит «некорректную проверку». + +## What Changes + +- Словарь `statement_checks.yaml` (библиотека `pylib_ai_arm_statements` 0.1.4, RSHB/pylib_ai_arm_statements#3): + необязательный ключ `group` у мягких соотношений — группа альтернатив одной цели. В колонке сошлась хоть одна — + нарушений группы нет; не сошлась ни одна — одно нарушение, ближайшая к цели. `group` у жёсткого — ошибка загрузки. +- Соотношения 2400 ОФР — одна группа в полном наборе (`2400 = 2300 − 2410 + 2420`, `2400 = 2300 + 2410 + 2460`) и в + упрощённом (существующее + новое `2400 = 2110 − 2120 − 2330 + 2340 − 2350 + 2410`). +- Документы контекста: `05-domain-rules.md` §7 (правило группы, знак 2410 не определяется), `07-open-questions.md` + OQ-12 — закрыт в части знака 2410. +- Контракт `recognition-result` не меняется: формат `soft_relations_failed` прежний, меняется только состав нарушений. + +Подъём версии библиотеки до 0.1.4 в arm-core, tabular-parser, scan-gateway — отдельными PR после merge AURORA-1677 +(переезд на библиотеку; merge придержан владельцем до выкатки гудвилла). + +## Non-goals + +- Жёсткие проверки, подсветка, `needs_check`, подтверждение значений шагом 8 (Р-21) — без изменений. +- Формула старого бланка с 2430/2450 не добавляется. +- Словари по редакциям 66н / ФСБУ 4/2023 (OQ-146, OQ-150, AURORA-1554) — отдельная задача. +- Переразбор уже загруженных документов: новые проверки применяются к новым загрузкам и к пересчёту после правок. + +## Impact + +- Спека `statement-normalization` — требование «Шаг 7 — самопроверки §7». +- Репозитории: pylib_ai_arm_statements (код и словарь), arm-ka-docs (этот change); подъём пина в arm-core, + arm-tabular-parser, arm-scan-gateway — после AURORA-1677. diff --git a/openspec/changes/statement-checks-alternatives/specs/statement-normalization/spec.md b/openspec/changes/statement-checks-alternatives/specs/statement-normalization/spec.md new file mode 100644 index 0000000..69eb11d --- /dev/null +++ b/openspec/changes/statement-checks-alternatives/specs/statement-normalization/spec.md @@ -0,0 +1,74 @@ +## MODIFIED Requirements + +### Requirement: Шаг 7 — самопроверки §7 +Шаг 7 SHALL считаться у обоих источников одним словарём `statement_checks.yaml` и выдавать `StatementChecks +{relations_failed[], soft_relations_failed[]}`; каждое нарушение — `{column_index, relation, codes, expected, +actual}`, где `codes` — коды цели и членов соотношения. Правила: +- значение кода в колонке — сумма по всем строкам `ok` с этим кодом; строки `detail`, `unreadable`, `missing` в + соотношения не входят; нарушение относится ко всем строкам его кодов; +- соотношение **посчитано**, если непуста цель или хотя бы один член (есть строка `ok` с кодом и `amount` не `null`); + пусто всё — соотношение пропускается; пустая цель при непустых членах и непустая цель при пустых членах — нарушение; +- жёсткое соотношение не сошлось, если расхождение больше допуска ±1 в единицах формы **и** больше 0,001 % наибольшего + по модулю числа соотношения — тогда оно в `relations_failed`; расхождение в пределах округления и мягкие + соотношения — в `soft_relations_failed`; +- члены `abs_terms` (2120, 2210, 2220, 2330, 2350, 2410) сравниваются и как записаны, и по модулю; такое соотношение + подтверждает величину, но не знак (шаг 8); +- набор соотношений — по `form_variant` (полный или упрощённый); `form_variant = null` — полный. В упрощённом наборе + соотношения ББ жёсткие, а `2400 = 2110 − 2120 − 2330 + 2340 − 2350 − 2410` и `2400 = 2110 − 2120 − 2330 + 2340 − + 2350 + 2410` ОФР — мягкие, как `2400` полного набора; +- **группа альтернатив** (`group` в словаре, только у мягких соотношений одной формы и варианта): в колонке сошлась + хоть одна альтернатива — ни одна не попадает в `soft_relations_failed`; не сошлась ни одна — в + `soft_relations_failed` одно нарушение, ближайшая к цели альтернатива (меньшее расхождение; при пустой цели или пустых + членах — первая по словарю). `group` у жёсткого соотношения и разные цели в одной группе — ошибка загрузки словаря. Соотношения 2400 каждого + набора — одна группа: полный — `2400 = 2300 − 2410 + 2420` и `2400 = 2300 + 2410 + 2460`, упрощённый — два варианта + выше. Знак 2410 (расход или доход по налогу, ФСБУ 4/2023) не определяется — принимается любая альтернатива (OQ-12). + Группа не меняет жёстких проверок, подсветки, `needs_check` и подтверждения значений (шаг 8, Р-21); +- родитель пятизначной подстроки — ближайшая четырёхзначная строка выше с теми же первыми тремя цифрами, иначе код из + первых четырёх цифр; «сумма подстрок = родитель» (мягкое) — только если хотя бы одна подстрока в колонке непуста. + +`checks` SHALL быть у каждого `BALANCE` / `INCOME` со строками и отсутствовать у документа без строк. +`duplicate_line_codes` MUST NOT выдаваться. + +#### Scenario: «БАЛАНС» без кода +- **WHEN** строка «БАЛАНС» ушла в `detail` (код потерян), в колонке есть 1100 и 1200 +- **THEN** соотношение `1600 = 1100 + 1200` посчитано и нарушено (пустая цель при непустых членах), в + `relations_failed` + +#### Scenario: Лист только с активом +- **WHEN** в ББ заполнены 1100, 1200, 1600, а 1300–1700 отсутствуют +- **THEN** `1600 = 1700` нарушено (непустая цель при пустых членах), документ `needs_check` + +#### Scenario: Повтор кода в соотношении +- **WHEN** две строки 2410 с суммами 100 и 649 +- **THEN** в соотношениях код 2410 равен 749 + +#### Scenario: Упрощённый ОФР с доходом по налогу +- **WHEN** у упрощённого ОФР 2410 записан со знаком дохода, 2400 с ним сходится только по `2400 = 2110 − 2120 − 2330 + + 2340 − 2350 + 2410` +- **THEN** нарушений по 2400 в `soft_relations_failed` нет + +#### Scenario: Доход по налогу в полной форме (AURORA-1864) +- **WHEN** в колонке ОФР 2300 = 100, 2410 = 20 (без минуса и скобок), 2400 = 120 +- **THEN** `2400 = 2300 − 2410 + 2420` не сошлось, но сошлось `2400 = 2300 + 2410 + 2460` из той же группы — нарушений + по 2400 в `soft_relations_failed` нет + +#### Scenario: Расход по налогу в полной форме +- **WHEN** 2300 = 100, 2410 = −20 или (20) или 20, 2400 = 80 +- **THEN** сходится `2400 = 2300 − 2410 + 2420` — нарушений по 2400 в `soft_relations_failed` нет + +#### Scenario: Неверная чистая прибыль — одно нарушение +- **WHEN** 2300 = 100, 2410 = 20, 2400 = 150 — не сходится ни одна альтернатива группы +- **THEN** в `soft_relations_failed` одно нарушение по 2400 — `2400 = 2300 + 2410 + 2460` (`actual` 120 ближе к 150, + чем 80); документ из-за него не `needs_check` + +#### Scenario: Группа считается по колонке +- **WHEN** в первой колонке 2400 сходится с одной из альтернатив, во второй — ни с одной +- **THEN** нарушение по 2400 одно и только у второй колонки + +#### Scenario: Группа у жёсткого соотношения +- **WHEN** в `statement_checks.yaml` у соотношения со `strict: true` указан `group` +- **THEN** словарь не загружается (ошибка загрузки `statement_checks.yaml`) + +#### Scenario: Округление итогов +- **WHEN** `1600 = 1700` расходится на 9,02 при итогах ~17 млрд +- **THEN** нарушение в `soft_relations_failed`, на статус и подсветку не влияет From a9aa6f60b0adfcd0143e095b1d300f7c500aa9bf Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Fri, 6 Nov 2026 23:00:11 +0400 Subject: [PATCH 4/4] =?UTF-8?q?=D0=90=D1=80=D1=85=D0=B8=D0=B2=20change=20s?= =?UTF-8?q?tatement-checks-alternatives=20(AURORA-1864,=20#209)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- .../.openspec.yaml | 0 .../design.md | 0 .../proposal.md | 0 .../specs/statement-normalization/spec.md | 0 .../specs/statement-normalization/spec.md | 39 ++++++++++++++++--- 5 files changed, 34 insertions(+), 5 deletions(-) rename openspec/changes/{statement-checks-alternatives => archive/2026-11-06-statement-checks-alternatives}/.openspec.yaml (100%) rename openspec/changes/{statement-checks-alternatives => archive/2026-11-06-statement-checks-alternatives}/design.md (100%) rename openspec/changes/{statement-checks-alternatives => archive/2026-11-06-statement-checks-alternatives}/proposal.md (100%) rename openspec/changes/{statement-checks-alternatives => archive/2026-11-06-statement-checks-alternatives}/specs/statement-normalization/spec.md (100%) diff --git a/openspec/changes/statement-checks-alternatives/.openspec.yaml b/openspec/changes/archive/2026-11-06-statement-checks-alternatives/.openspec.yaml similarity index 100% rename from openspec/changes/statement-checks-alternatives/.openspec.yaml rename to openspec/changes/archive/2026-11-06-statement-checks-alternatives/.openspec.yaml diff --git a/openspec/changes/statement-checks-alternatives/design.md b/openspec/changes/archive/2026-11-06-statement-checks-alternatives/design.md similarity index 100% rename from openspec/changes/statement-checks-alternatives/design.md rename to openspec/changes/archive/2026-11-06-statement-checks-alternatives/design.md diff --git a/openspec/changes/statement-checks-alternatives/proposal.md b/openspec/changes/archive/2026-11-06-statement-checks-alternatives/proposal.md similarity index 100% rename from openspec/changes/statement-checks-alternatives/proposal.md rename to openspec/changes/archive/2026-11-06-statement-checks-alternatives/proposal.md diff --git a/openspec/changes/statement-checks-alternatives/specs/statement-normalization/spec.md b/openspec/changes/archive/2026-11-06-statement-checks-alternatives/specs/statement-normalization/spec.md similarity index 100% rename from openspec/changes/statement-checks-alternatives/specs/statement-normalization/spec.md rename to openspec/changes/archive/2026-11-06-statement-checks-alternatives/specs/statement-normalization/spec.md diff --git a/openspec/specs/statement-normalization/spec.md b/openspec/specs/statement-normalization/spec.md index 9809455..3f5737e 100644 --- a/openspec/specs/statement-normalization/spec.md +++ b/openspec/specs/statement-normalization/spec.md @@ -612,8 +612,15 @@ actual}`, где `codes` — коды цели и членов соотноше - члены `abs_terms` (2120, 2210, 2220, 2330, 2350, 2410) сравниваются и как записаны, и по модулю; такое соотношение подтверждает величину, но не знак (шаг 8); - набор соотношений — по `form_variant` (полный или упрощённый); `form_variant = null` — полный. В упрощённом наборе - соотношения ББ жёсткие, а `2400 = 2110 − 2120 − 2330 + 2340 − 2350 − 2410` ОФР — мягкое, как `2400` полного набора - (знак 2410, OQ-12); + соотношения ББ жёсткие, а `2400 = 2110 − 2120 − 2330 + 2340 − 2350 − 2410` и `2400 = 2110 − 2120 − 2330 + 2340 − + 2350 + 2410` ОФР — мягкие, как `2400` полного набора; +- **группа альтернатив** (`group` в словаре, только у мягких соотношений одной формы и варианта): в колонке сошлась + хоть одна альтернатива — ни одна не попадает в `soft_relations_failed`; не сошлась ни одна — в + `soft_relations_failed` одно нарушение, ближайшая к цели альтернатива (меньшее расхождение; при пустой цели или пустых + членах — первая по словарю). `group` у жёсткого соотношения и разные цели в одной группе — ошибка загрузки словаря. Соотношения 2400 каждого + набора — одна группа: полный — `2400 = 2300 − 2410 + 2420` и `2400 = 2300 + 2410 + 2460`, упрощённый — два варианта + выше. Знак 2410 (расход или доход по налогу, ФСБУ 4/2023) не определяется — принимается любая альтернатива (OQ-12). + Группа не меняет жёстких проверок, подсветки, `needs_check` и подтверждения значений (шаг 8, Р-21); - родитель пятизначной подстроки — ближайшая четырёхзначная строка выше с теми же первыми тремя цифрами, иначе код из первых четырёх цифр; «сумма подстрок = родитель» (мягкое) — только если хотя бы одна подстрока в колонке непуста. @@ -634,9 +641,31 @@ actual}`, где `codes` — коды цели и членов соотноше - **THEN** в соотношениях код 2410 равен 749 #### Scenario: Упрощённый ОФР с доходом по налогу -- **WHEN** у упрощённого ОФР 2410 записан со знаком дохода, и `2400 = 2110 − 2120 − 2330 + 2340 − 2350 − 2410` по модулю - не сходится -- **THEN** нарушение в `soft_relations_failed`, документ из-за него не `needs_check` +- **WHEN** у упрощённого ОФР 2410 записан со знаком дохода, 2400 с ним сходится только по `2400 = 2110 − 2120 − 2330 + + 2340 − 2350 + 2410` +- **THEN** нарушений по 2400 в `soft_relations_failed` нет + +#### Scenario: Доход по налогу в полной форме (AURORA-1864) +- **WHEN** в колонке ОФР 2300 = 100, 2410 = 20 (без минуса и скобок), 2400 = 120 +- **THEN** `2400 = 2300 − 2410 + 2420` не сошлось, но сошлось `2400 = 2300 + 2410 + 2460` из той же группы — нарушений + по 2400 в `soft_relations_failed` нет + +#### Scenario: Расход по налогу в полной форме +- **WHEN** 2300 = 100, 2410 = −20 или (20) или 20, 2400 = 80 +- **THEN** сходится `2400 = 2300 − 2410 + 2420` — нарушений по 2400 в `soft_relations_failed` нет + +#### Scenario: Неверная чистая прибыль — одно нарушение +- **WHEN** 2300 = 100, 2410 = 20, 2400 = 150 — не сходится ни одна альтернатива группы +- **THEN** в `soft_relations_failed` одно нарушение по 2400 — `2400 = 2300 + 2410 + 2460` (`actual` 120 ближе к 150, + чем 80); документ из-за него не `needs_check` + +#### Scenario: Группа считается по колонке +- **WHEN** в первой колонке 2400 сходится с одной из альтернатив, во второй — ни с одной +- **THEN** нарушение по 2400 одно и только у второй колонки + +#### Scenario: Группа у жёсткого соотношения +- **WHEN** в `statement_checks.yaml` у соотношения со `strict: true` указан `group` +- **THEN** словарь не загружается (ошибка загрузки `statement_checks.yaml`) #### Scenario: Округление итогов - **WHEN** `1600 = 1700` расходится на 9,02 при итогах ~17 млрд