108 lines
11 KiB
Markdown
108 lines
11 KiB
Markdown
# statement-checks Specification
|
||
|
||
## Purpose
|
||
Логические проверки форм ББ/ОФР §7 в ядре: чистые функции над текущими суммами документа, без хранения результата;
|
||
акцепт непройденных проверок аналитиком.
|
||
|
||
## Requirements
|
||
|
||
### Requirement: Соотношения §7 по колонкам
|
||
Проверки SHALL считаться чистой функцией по текущим `financial_statement_value.amount` документа для каждой колонки
|
||
отдельно по словарю §7 копии пакета `arm_statements` (`statement-normalization`; своей копии словаря ядро не держит):
|
||
соотношение **посчитано**, если непуста цель (левая часть) или хотя бы один член (есть строка `ok` с кодом и непустой
|
||
суммой); если пусто всё — соотношение пропускается. В посчитанном соотношении отсутствующий код и пустая сумма в
|
||
арифметике — 0, но пустая цель при непустых членах и непустая цель при пустых членах — нарушение (пустая цель даёт
|
||
`expected = null`, P1-21 пакета). Выполнено, если `|левая − правая| ≤ tolerance`
|
||
(1, в единицах формы) хотя бы в одном варианте правой части — как записано или с кодами `abs_terms` по модулю. Нарушение
|
||
жёсткого (`strict`) соотношения, не превышающее `rounding_tolerance_percent` (0,001 %) от наибольшего по модулю числа
|
||
соотношения, SHALL считаться мягким («в пределах округления»). Мягкие: соотношения `strict: false` и «сумма пятизначных
|
||
подстрок = четырёхзначный родитель». Результат — `{failed: [...], soft_failed: [...]}`, элемент — `{column_index,
|
||
relation, expected, actual}`, где `expected` и `actual` могут быть `null` (`expected = null` — пустая цель); не хранится как есть: нарушения при приёме и пересчёте записываются в `document_issue`.
|
||
|
||
Присутствующими SHALL считаться суммы **агрегата формы по коду** (требование «Агрегат формы по коду»): строки `ok` формы из БД, кроме строк, созданных правкой аналитика (`row_index IS NULL`), у которых все суммы пустые; строки `detail`, `unreadable`, `missing` и строки, достроенные в ответе по справочнику (`documents-api`, `id = null`), в проверках MUST NOT участвовать. Созданная правкой строка с хотя бы одной непустой суммой проверяется по всем колонкам; в колонке, где её сумма пуста, она — пустая цель (или член с суммой 0). Повтор кода в форме суммируется. Результат нарушения несёт `codes` (коды цели и членов соотношения) и `line_ids` (все строки `ok` этих кодов). Соотношения считает точка входа пакета для пересчёта по агрегату формы; запись в БД — только в виде `document_issue` при приёме и пересчёте, `api/rules/statement_checks.yaml` и код его загрузки в ядре MUST NOT существовать.
|
||
|
||
#### Scenario: Сходящийся баланс
|
||
- **WHEN** проверяется форма `mezheninovskaya/2025-Q3/1_Форма1_бух_09`
|
||
- **THEN** `failed` пуст
|
||
|
||
#### Scenario: Нарушение жёсткого соотношения
|
||
- **WHEN** у баланса 1600 = 1000, 1100 = 300, 1200 = 600 в колонке 0
|
||
- **THEN** `failed` содержит `{column_index: 0, relation: "1600 = 1100 + 1200", expected: 1000, actual: 900}`
|
||
|
||
#### Scenario: Расходы со знаком минус
|
||
- **WHEN** ОФР 2110 = 100, 2120 = −60, 2100 = 40
|
||
- **THEN** `2100 = 2110 − 2120` выполнено через вариант с `abs_terms`
|
||
|
||
#### Scenario: Правка исправляет нарушение
|
||
- **WHEN** аналитик правит 1200 с 600 на 700 в форме из предыдущего сценария
|
||
- **THEN** проверка по текущим суммам: `failed` пуст
|
||
|
||
#### Scenario: Пустая шаблонная строка не проверяется
|
||
- **WHEN** в форме нет строки 1700 (в ответе она достроена пустой и как значение в проверках не участвует), 1300 +
|
||
1400 + 1500 = 2 371 333 в колонке 0
|
||
- **THEN** сама строка 1700 не проверяется, но соотношение `1700 = 1300 + 1400 + 1500` посчитано и нарушено: `expected = null`, `actual = 2371333`;
|
||
`1600 = 1700` (цель 1600 есть в форме, 1700 пуста) тоже нарушено; потерявшая итоговую строку форма блокирует
|
||
подтверждение (`CHECKS_NOT_PASSED`) до правки строки или акцепта (P3-30)
|
||
|
||
#### Scenario: Все стороны пусты
|
||
- **WHEN** в колонке нет ни цели, ни членов соотношения
|
||
- **THEN** соотношение пропускается, нарушения нет
|
||
|
||
#### Scenario: Заполненная шаблонная строка проверяется
|
||
- **WHEN** аналитик заполнил 1700 = 2 371 333 только в колонке 0, а в колонке 1 1300 + 1400 + 1500 = 2 000 000
|
||
- **THEN** в колонке 0 соотношение выполнено; в колонке 1 `failed` содержит `1700 = 1300 + 1400 + 1500` с
|
||
`expected = null` (1700 в колонке 1 пуста), `actual = 2000000`
|
||
|
||
#### Scenario: Очищенная шаблонная строка
|
||
- **WHEN** затем аналитик очищает 1700 в колонке 0 (`amount = null`)
|
||
- **THEN** строка 1700 снова не участвует в проверках как значение: пустая цель при непустых членах — нарушение
|
||
`expected = null` в каждой колонке (прежнее «очищенная строка не цель» не действует, P3-30)
|
||
|
||
#### Scenario: Повтор кода суммируется
|
||
- **WHEN** в ББ две строки 1230 со значениями 100 и 50 в колонке 0
|
||
- **THEN** соотношения берут 1230 = 150
|
||
|
||
#### Scenario: Копии словаря §7 в ядре нет
|
||
- **WHEN** проверяется репозиторий ядра после перехода на пакет
|
||
- **THEN** файла `api/rules/statement_checks.yaml` нет, `load_check_rules` / `run_checks` в `api/src` не встречаются;
|
||
допуск `tolerance` берётся из правил пакета
|
||
|
||
### Requirement: Межформенная проверка
|
||
Для документа `BALANCE` или `INCOME` SHALL считаться мягкая проверка `1370(колонка отчётной даты ББ) − 1370(колонка
|
||
31 декабря прошлого года ББ) = 2400(колонка отчётного периода ОФР)` с допуском `tolerance`, если найдена форма-пара:
|
||
документ другого типа формы того же ЮЛ, `period_year` и `period_quarter`, файла не `SUPERSEDED` (подтверждённый, затем
|
||
самый поздний по `created_at`), и все три колонки. Значение `1370` / `2400` берётся по агрегату формы по коду (сумма строк `ok` с этим кодом). Нарушение SHALL попадать в `cross_form` и MUST NOT блокировать
|
||
подтверждение.
|
||
|
||
#### Scenario: Пара найдена
|
||
- **WHEN** у ЮЛ ББ за 2025 Q3 (1370 = 500 на 30.09.2025, 300 на 31.12.2024) и ОФР за 2025 Q3 (2400 = 150)
|
||
- **THEN** `cross_form` — нарушение `expected 200, actual 150`; подтверждение ББ разрешено
|
||
|
||
#### Scenario: Пары нет
|
||
- **WHEN** у ЮЛ есть только ББ за период
|
||
- **THEN** `cross_form` — `null`
|
||
|
||
#### Scenario: Межформенная проверка по агрегату
|
||
- **WHEN** в ББ две строки 1370 (сумма 500 и 300 на отчётную дату)
|
||
- **THEN** межформенная проверка берёт 1370 = 800
|
||
|
||
### Requirement: Акцепт непройденных проверок
|
||
Акцепт SHALL записывать `financial_statement_meta.checks_accepted_by`, `checks_accepted_at`,
|
||
`checks_accepted_comment`; документ с акцептом SHALL считаться прошедшим жёсткие проверки для статуса и подтверждения.
|
||
Любая правка значений или шапки формы SHALL сбрасывать акцепт (три поля — `NULL`).
|
||
|
||
#### Scenario: Акцепт и правка
|
||
- **WHEN** аналитик акцептовал нарушение 1600 и затем поправил любое значение
|
||
- **THEN** поля акцепта пусты, при непройденной проверке документ `NEEDS_CHECK`
|
||
|
||
### Requirement: Агрегат формы по коду
|
||
Все чтения формы «по коду» SHALL идти через одну чистую функцию «агрегат формы по коду» без хранения (DECISION-5):
|
||
для кода и колонки — сумма `amount` строк `ok` с этим кодом (`null`, если ни одной непустой), список `line_id`,
|
||
справочное имя. На неё переводятся межформенная проверка `1370 / 2400`, суммы формы для консолидации и выгрузок,
|
||
`parent_code` подстрок, раскладка строк на экране, правка ячеек по `line_code`, метрики `fields_*`. Строки `detail`,
|
||
`unreadable`, `missing` в агрегат, КФО и снимок ЕХ MUST NOT попадать.
|
||
|
||
#### Scenario: Межформенная проверка по агрегату
|
||
- **WHEN** в ББ две строки 1370 (сумма 500 и 300 на отчётную дату)
|
||
- **THEN** межформенная проверка берёт 1370 = 800
|