docs(#116): §11 — общая цепочка нормализации ББ/ОФР, §7 и §9 по v2.0, открытые вопросы
- 05-domain-rules: §11 объединяет прежние §11, §11.1 и §12 (источники OCR и Excel, шаги 2–8, таблица уровней предупреждений, что отменено); §12 — заголовок-ссылка; §7 — общий словарь у сервисов, «посчитанное» соотношение, abs_terms подтверждают величину, упрощённый набор; §9.3 — RECOGNIZED при приёме v2.0; §9.5 — хранение v2.0, агрегат по коду, 409 LINES_UNRESOLVED / DUPLICATE_FORM, PATCH lines/columns/confirmed; §1.6 — статус и предупреждения ОСВ в v2.0 - отменены: OQ-36/OQ-141 (Р-8), решение № 22 среза 3a (Р-11), решение 08.10.2026 по OQ-156 (Р-17); решение № 30 сохранено для OCR-текста - 07-open-questions: закрыты OQ-36, OQ-141, OQ-147, OQ-152; OQ-156 пересмотрен; OQ-148 — допущение спеки; примечания к OQ-33, 145, 146, 149, 150; новые OQ-165 (склейка половин формы, Р-15), OQ-166 (упрощённая форма), OQ-167 (несколько периодов в файле ИС) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BJKMLHdxMwSUeYcdkwqX4W
This commit is contained in:
parent
9b1a4ccbfd
commit
56a0ff4527
@ -1,4 +1,4 @@
|
||||
# 05. Правила предметной области (выжимка из ТЗ, имена — v5.18)
|
||||
# 05. Правила предметной области (выжимка из ТЗ, имена — v5.19)
|
||||
|
||||
Каждый раздел: источник в ТЗ → правило → где в модели. Где ТЗ молчит или противоречит — пометка **[открыто]** и
|
||||
ссылка на `07-open-questions.md`.
|
||||
@ -293,10 +293,15 @@ result-osv-needs-check.json`.
|
||||
|
||||
Формат определяется по сигнатуре байтов (zip → XLSX, либо XLSB при наличии `xl/workbook.bin`; OLE2 → XLS), а не по
|
||||
`file_type` запроса; несовпадение — предупреждение. Листы без признаков ОСВ и форм пропускаются с
|
||||
предупреждением (`sheet=<имя> skipped: нет признаков ОСВ или формы`; листы форм теперь разбираются, §12). Другие предупреждения:
|
||||
предупреждением (`sheet=<имя> skipped: нет признаков ОСВ или формы`; листы форм теперь разбираются, §11.2). Другие предупреждения:
|
||||
`sheet=<имя> hierarchy=<стратегия>` (всегда), свёрнутое сальдо, нераспознанное число (с номером строки), не найденное
|
||||
поле шапки, «нет строк данных». xlsb не содержит outline level — каскад начинается с отступа (предупреждение
|
||||
«xlsb: outline level недоступен»).
|
||||
**С контракта v2.0** (change `unified-statement-normalization`, §11.9) у каждого `osvDocument` есть `status` (`done`, когда
|
||||
контрольные суммы §1.4 листа сошлись, иначе `needs_check` — те же условия, что выше, но по листу) и `warnings[]` —
|
||||
перечисленные предупреждения листа структурами `{code: "OSV_…", severity: "info", message}`; строки `sheet=<имя> …` в
|
||||
`metrics.warnings` больше не пишутся (предупреждения уровня книги — `metrics.warnings` структурами, например
|
||||
`FILE_TYPE_MISMATCH`). Разбор ОСВ не меняется.
|
||||
|
||||
Лимиты: `PARSE_MAX_BYTES` (по умолчанию 50 МиБ) и `PARSE_TIMEOUT_SECONDS` (120 с; кооперативный — проверка между листами,
|
||||
в циклах по листу и между стратегиями; чтение книги библиотекой он не прерывает). Поэтому до библиотеки чтения действуют
|
||||
@ -506,38 +511,60 @@ variance = |amount_a − amount_b|; variance_pct = variance / min(amount_a, amou
|
||||
Межформенная: `1370(отчётная дата) − 1370(начало года) = 2400(ОФР за период)` — при расхождении КА может акцептовать
|
||||
с комментарием (`financial_statement_meta.checks_accepted_*`).
|
||||
|
||||
**Парсер tabular-parser (§12 п. 7)** считает **жёсткими** (нарушение → `needs_check`) соотношения ББ — `1100`, `1200`
|
||||
(+1215), `1300`, `1400`, `1500`, `1600 = 1100 + 1200`, `1700 = 1300 + 1400 + 1500`, `1600 = 1700` — и ОФР — `2100`, `2200`,
|
||||
`2300`; **мягкими** (предупреждение) — `2400 = 2300 − 2410 + 2420` и варианты ФСБУ 4/2023 (`2400 = 2300 + 2410 + 2460`,
|
||||
`2410 = 2411 + 2412`), `2500 = 2400 + 2510 + 2520 + 2530` и «сумма пятизначных подстрок = родитель»
|
||||
(`ASSUMPTION(OQ-12)`), последняя — по колонке и только если хотя бы одна подстрока родителя в ней непуста (`amount` не `null`; ноль — значение): без детализации соотношение не проверяется, ни нарушения, ни предупреждения (AURORA-1527, решение владельца 16.10.2026). Допуск ±1 в единицах формы (расхождение до 0,001 % наибольшего числа соотношения — округление,
|
||||
мягкое, OQ-138); соотношения считаются по отображаемым значениям (п. 5 §12), расходы — двойной расчёт знаков (`abs_terms`). Межформенная проверка
|
||||
`1370 − 1370(начало года) = 2400` парсером не выполняется. Это сигнал о структуре и данных файла, а не замена проверок
|
||||
ядра: ядро (Срез 3) пересчитывает §7 и ведёт акцепт (`financial_statement_meta.checks_accepted_*`).
|
||||
**Самопроверка в сервисах (общий код, §11.8; change `unified-statement-normalization`, контракт v2.0).** Оба сервиса
|
||||
(tabular-parser и scan-gateway) считают §7 одним кодом `arm_statements` по одному словарю `statement_checks.yaml` и кладут
|
||||
результат в `documents[].checks` (`relations_failed`, `soft_relations_failed`; у нарушения — `column_index`, текст
|
||||
соотношения, `codes` — коды цели и членов, `expected`, `actual`). Ядро хранит `checks` как пришли (`document_issue`) и
|
||||
пересчитывает тем же кодом только после правок аналитика; своей копии словаря у ядра нет. Правила счёта:
|
||||
|
||||
**Ядро (Срез 3b, реализовано).** Ядро считает §7 само, для обоих источников (Excel и OCR), по **текущим** суммам формы
|
||||
(`financial_statement_value.amount` после правок аналитика); результат не хранится, считается на лету (`GET
|
||||
/documents/{id}/statement`, подтверждение). Правила — словарь `arm-core/api/rules/statement_checks.yaml`: **копия**
|
||||
словаря tabular-parser (те же жёсткие и мягкие соотношения, допуск ±1 в единицах формы, округление 0,001 %, `abs_terms`,
|
||||
мягкая «сумма пятизначных подстрок = родитель», по колонке — только если хотя бы одна подстрока родителя в ней непуста: `amount` не `null`, ноль — значение; пустая детализация не проверяется, AURORA-1527); тест идентичности копий между репозиториями не ставится, расхождение —
|
||||
известный риск (`03-architecture.md`). Соотношение считается по каждой колонке и только если строка-цель (левая часть) есть в форме;
|
||||
отсутствующий код и пустая сумма — 0 (ненулевые слагаемые при пустой цели дают жёсткое нарушение). Результат в ответе API: `failed` (жёсткие), `soft_failed` (мягкие, в
|
||||
том числе «в пределах округления»), `cross_form`, `accepted`.
|
||||
- **Жёсткие** (нарушение → `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`, «сумма пятизначных подстрок = родитель» (по колонке — только если хотя бы одна подстрока родителя в ней
|
||||
непуста: `amount` не `null`, ноль — значение; AURORA-1527).
|
||||
- **Значение кода** в колонке — сумма по всем строкам `ok` с этим кодом (повтор кода — законный случай, Р-4); строки
|
||||
`detail`, `unreadable`, `missing` в соотношения не входят; нарушение относится ко всем строкам его кодов.
|
||||
`duplicate_line_codes` не выдаётся.
|
||||
- **Посчитанное соотношение.** Соотношение считается, если непуста цель или хотя бы один член (есть строка `ok` с кодом
|
||||
и непустой суммой); пусто всё — пропускается. Пустая цель при непустых членах — нарушение («БАЛАНС» без кода ушёл в
|
||||
`detail` — 1600 пуст, `1600 = 1100 + 1200` подсвечивает родителя); непустая цель при пустых членах — тоже нарушение
|
||||
(лист только с активом: `1600 = 1700` при отсутствующем 1700 — `needs_check`). До v2.0 соотношение с отсутствующей
|
||||
целью пропускалось у обоих сервисов и в ядре — поведение изменено; посчитанное соотношение из одних `null` ничего не
|
||||
подтверждает.
|
||||
- **Допуск и округление:** жёсткое соотношение не сошлось, если расхождение больше ±1 в единицах формы **и** больше
|
||||
0,001 % наибольшего по модулю числа соотношения — только тогда оно в `relations_failed`; расхождение в пределах
|
||||
округления (OQ-138) и мягкие соотношения — в `soft_relations_failed`, это `info`, на статус и подсветку не влияют.
|
||||
Соотношения считаются по отображаемым значениям (§11.2, числовой формат Excel).
|
||||
- **Члены по модулю (`abs_terms`: 2120, 2210, 2220, 2330, 2350, 2410).** Правая часть считается дважды — как записано и
|
||||
с этими членами по модулю (1С выгружает расходы со знаком минус, 66н — в скобках). Такое соотношение сходится при
|
||||
любом знаке члена и потому **подтверждает величину, но не знак**: знак сомнительной ячейки подтверждается конвенцией
|
||||
колонки (§11.9).
|
||||
- **Упрощённый набор.** Набор выбирается по `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` (жёсткое, `abs_terms` те же). Оба набора — в одном словаре `statement_checks.yaml`. Без
|
||||
упрощённого набора полный дал бы нарушения «пустой цели» на каждой упрощённой форме.
|
||||
- **Родитель пятизначной подстроки** — ближайшая четырёхзначная строка выше с теми же первыми тремя цифрами, иначе код
|
||||
из первых четырёх цифр (12310 под 1230); то же правило ядро отдаёт экрану как `parent_code`.
|
||||
- **Межформенная** `1370(отчётная дата) − 1370(начало года) = 2400` сервисами не считается — только ядром (ниже).
|
||||
|
||||
**Ядро.** При загрузке результата v2.0 ядро §7 **не пересчитывает**: `checks`, `needs_review` и статус документа
|
||||
берутся из результата (§9.3, §9.5 п. 2). После правки аналитика (`PATCH …/statement`) ядро пересчитывает §7 копией
|
||||
`arm_statements` по текущим суммам (`financial_statement_value.amount` после правок) — с теми же правилами, что в
|
||||
сервисах; при приёме результата 1.x (окно выкладки) — прежним правилом D-6. Межформенная `1370(отчётная дата) − 1370(начало
|
||||
года) = 2400` — **мягкая**, считается ядром по агрегату формы по коду (§9.5 п. 2) и не блокирует подтверждение: пара —
|
||||
другой документ формы того же ЮЛ с тем же годом и кварталом периода (файл не `SUPERSEDED`; подтверждённый
|
||||
предпочтительнее, далее самый поздний `created_at`); считается, только если найдены пара и обе колонки. Результат в
|
||||
ответе API: `failed` (жёсткие), `soft_failed` (мягкие, в том числе «в пределах округления»), `cross_form`, `accepted`;
|
||||
у нарушения — `codes` и `line_ids`.
|
||||
Показ пятизначных подстрок (AURORA-1527, решение владельца 16.10.2026): ядро отдаёт у каждой строки формы вычисляемое
|
||||
`parent_code` — код четырёхзначного родителя подстроки по тому же правилу, что и проверка «сумма подстрок = родитель»
|
||||
(п. 7 §12; одна функция в ядре, порядок кодов тот же); у остальных строк `null`. Экран показывает подстроки сразу под
|
||||
родителем, пустые подстроки (нет значения ни в одной колонке) свёрнуты за переключателем; правила родителя на фронте нет.
|
||||
Только показ: парсеры и данные не меняются.
|
||||
Межформенная `1370(отчётная дата) − 1370(начало года) = 2400` — **мягкая** и не блокирует подтверждение: пара — другой
|
||||
документ формы того же ЮЛ с тем же годом и кварталом периода (файл не `SUPERSEDED`; подтверждённый предпочтительнее, далее
|
||||
самый поздний `created_at`); считается, только если найдены пара и обе колонки (ОФР часто приходит отдельным файлом позже).
|
||||
`parent_code` (правило выше); экран показывает подстроки под родителем, пустые подстроки свёрнуты за переключателем.
|
||||
**Акцепт** — по жёстким нарушениям, с комментарием (1–2000 символов), `POST /documents/{id}/statement/checks/accept`;
|
||||
без нарушений — 409 `NOTHING_TO_ACCEPT`. Акцепт **сбрасывается любой правкой формы** (значения или шапки): данные
|
||||
изменились, проверку нужно смотреть заново. Аудит акцепта — `RECOGNIZED_DATA_EDIT` (отдельного `audit_action` нет, новое
|
||||
значение enum потребовало бы DBML v5.17). Формулы одни для редакций `66n` и `fsbu4` (`ASSUMPTION(OQ-12, OQ-146)`); редакцию
|
||||
ядро определяет по дате периода документа и отдаёт в ответе (`edition`: `66n` при дате ≤ 2024-12-31, `fsbu4` — позже, `null`
|
||||
без даты; решение владельца 07.10.2026 по OQ-145 — редакция в контракт не передаётся, досрочный ФСБУ 4/2023 — новый OQ при
|
||||
первом случае); раздельные словари и формулы по редакциям — вместе с составом `fsbu4` (OQ-146, OQ-150).
|
||||
без нарушений — 409 `NOTHING_TO_ACCEPT`. Акцепт **сбрасывается любой правкой формы**. Аудит акцепта —
|
||||
`RECOGNIZED_DATA_EDIT`. Формулы одни для редакций `66n` и `fsbu4` (`ASSUMPTION(OQ-12, OQ-146)`); редакцию ядро определяет
|
||||
по дате периода документа и отдаёт в ответе (`edition`: `66n` при дате ≤ 2024-12-31, `fsbu4` — позже, `null` без даты;
|
||||
OQ-145); тем же правилом сервисы выбирают редакцию упорядоченного перечня кодов (§11.6).
|
||||
|
||||
**Полный состав формы (arm-core#80, решение владельца 09.10.2026).** Экран проверки показывает все строки формы
|
||||
редакции документа (`edition`): справочник `arm-core/api/rules/statement_lines.yaml` (66н и ФСБУ 4/2023, ББ и ОФР, из
|
||||
@ -546,11 +573,19 @@ variance = |amount_a − amount_b|; variance_pct = variance / min(amount_a, amou
|
||||
(`line_code_original_value = NULL` — признак «не из источника») и ячейку как правку аналитика (аудит
|
||||
`RECOGNIZED_DATA_EDIT`). Пустая добавленная строка не влияет на статус, проверки §7 и автоподтверждение; созданная
|
||||
правкой строка участвует в проверках, пока у неё есть хоть одна сумма. Без редакции (нет даты периода) строки не
|
||||
достраиваются. Строки «БАЛАНС» (1600, 1700) на экране выделяются цветом.
|
||||
достраиваются. Строки «БАЛАНС» (1600, 1700) на экране выделяются цветом. **С v2.0** (change
|
||||
`unified-statement-normalization`): справочник ядра — только словарь имён (код → наименование по редакциям); порядок и
|
||||
состав редакций ядро берёт из копии упорядоченного перечня кодов `arm_statements` (`sync-statements --check` сверяет, что
|
||||
все коды словаря имён есть в перечне); код, у которого есть хотя бы одна строка `ok` из файла, шаблоном не
|
||||
достраивается; строки `unreadable` / `missing` шаблонной строкой молча не заменяются — показываются отдельно со статусом.
|
||||
Наименование строки из источника хранится как распознано; экран показывает справочное имя по коду, файловое — рядом
|
||||
(Р-3, AURORA-1406 / 1522).
|
||||
|
||||
Когда: после распознавания; после «Сохранить» правок; при «Подтвердить данные». Непройдено → `document.status =
|
||||
NEEDS_CHECK`; пройдено/акцептовано + нажато «Подтвердить» → `CONFIRMED` + `audit_event(DOCUMENT_CONFIRM)`.
|
||||
Отрицательные значения в формах — в скобках `(2100)` → `-2100`. Значения с `confidence < 100` подсвечиваются.
|
||||
Когда: в сервисе при распознавании; в ядре после «Сохранить» правок. Непройдено → `document.status = NEEDS_CHECK`;
|
||||
пройдено/акцептовано + нажато «Подтвердить» → `CONFIRMED` + `audit_event(DOCUMENT_CONFIRM)`. Отрицательные значения в
|
||||
формах — в скобках `(2100)` → `-2100`. Подсветка ячеек — только по `needs_review` (§11.9): уверенность, `is_recognized` и
|
||||
нарушения §7 сведены в него сервисом; «подтверждение проверкой» — сошедшееся посчитанное жёсткое соотношение снимает
|
||||
подсветку с сомнительной ячейки (Р-7).
|
||||
|
||||
---
|
||||
|
||||
@ -650,6 +685,16 @@ INITIATED; в Срезе 2c консолидаций ещё нет, поэтом
|
||||
(`financial_statement_meta` нет), подтверждается без условий п. 9.5.4, а `GET`/`PATCH …/statement` по нему — 409
|
||||
`DOCUMENT_NOT_STATEMENT`.
|
||||
|
||||
**Документ формы при приёме контракта v2.0** (change `unified-statement-normalization`, Р-19; правило выше остаётся для
|
||||
результатов 1.x на окне выкладки). `RECOGNIZED` — только если одновременно: `document.status = done` из результата; в
|
||||
`metrics.warnings` файла нет предупреждений уровня `error` (потерянный json или таблица ИС — решение № 28 среза 3a;
|
||||
`needs_check` соседнего документа и статус конверта не учитываются); `counterparty_check ≠ MISMATCH`; определены
|
||||
`period_year` и `period_quarter` (дата — конец квартала); у каждой строки `ok` с неуверенным кодом (`line_code_confidence
|
||||
< 100`) распознанное наименование совпало со справочным по коду (Р-16, правило совпадения — §11.9); в файле нет второй
|
||||
формы того же типа и периода (Р-18). Иначе `NEEDS_CHECK`. Проверки §7, `needs_review` и предупреждения сервиса при
|
||||
загрузке не пересчитываются. После правки или акцепта статус пересчитывается общим кодом (§11.9) с подтверждениями
|
||||
аналитика и теми же условиями ядра; `RECOGNIZED` после правки — только вручную, как и прежде.
|
||||
|
||||
### 9.4 Задание (`recognition_status`): queued → published → processing → done | needs_check | error | timeout → dlq;
|
||||
replay: dlq → queued (`attempt + 1`). `max_attempts`: OCR 1, парсер 3; `reply_deadline` OCR 20 мин (решение владельца 09.10.2026; note DBML «5 мин» — OQ-159), парсер 5 мин.
|
||||
|
||||
@ -687,6 +732,24 @@ replay: dlq → queued (`attempt + 1`). `max_attempts`: OCR 1, парсер 3; `
|
||||
`NEEDS_CHECK`. Файл только с формами — не ошибка; `ERROR` по результату — только при пустом `documents`. Страницы одной
|
||||
формы из разных файлов не склеиваются (OQ-140), строки без кода не хранятся (OQ-36, OQ-141, OQ-149). Статус документа формы —
|
||||
§9.3.
|
||||
**Формы по контракту v2.0** (change `unified-statement-normalization`; правила выше — для результатов 1.x на окне
|
||||
выкладки, у них поля v2.0 заполняются по умолчанию: `line_status = ok`, `row_index` по порядку, `text =
|
||||
string_value`, `needs_review` значений — по прежнему правилу «правлено или уверенность 100 и `is_recognized`»).
|
||||
Результат пишется как пришёл, без повторной дедупликации: каждая строка — `financial_statement_line` с `row_index`,
|
||||
`source_ref` (`ref`, уникален в форме), `line_status`, `line_code` (`null` у `unreadable` / `missing` / `detail`),
|
||||
`line_code_text`, `parent_row_index` (родитель `detail`), `needs_review`; повтор кода — законный случай, хранятся все
|
||||
строки (Р-4; строки без кода хранятся — Р-8, отменяет OQ-36 / OQ-141). Значения — с `source_text` (`values[].text`) и
|
||||
`needs_review`; колонки — с `period_kind` и `label_confidence`; шапка — с `form_variant` и `needs_review_fields`;
|
||||
`checks` и предупреждения документа — в `document_issue` (источник `service`) вместе с проверками ядра уровня `info`
|
||||
(`CODE_NOT_IN_DICTIONARY`, `CODE_NOT_IN_EDITION`, `NAME_MISMATCH`); версия общего кода —
|
||||
`recognition_job.statements_version`. Каждый `BALANCE` / `INCOME` становится отдельным документом с `file_ordinal`:
|
||||
вторая форма того же типа в файле **хранится** (Р-10, Р-15; ни одна из двух форм одного типа и периода не
|
||||
автоподтверждается — Р-18); ОСВ по счёту, `DISCLOSURE` и `OTHER` — по-прежнему один документ на тип (и счёт) на файл.
|
||||
Все чтения формы «по коду» (межформенная проверка, суммы формы для консолидации и выгрузок, `parent_code`, раскладка
|
||||
экрана, правка по `line_code`, метрики `fields_*`) идут через одну чистую функцию **«агрегат формы по коду»**: сумма
|
||||
`amount` строк `ok` с кодом по колонке, список `line_id`, справочное имя (DECISION-5, не хранится). Строки `detail`,
|
||||
`unreadable`, `missing` в агрегат, КФО и снимок ЕХ не попадают — цена Р-8. Записи `document_issue` удаляются вместе
|
||||
с документом при перезагрузке файла.
|
||||
3. **Автозамена.** После успешного маппинга другие файлы того же ЮЛ (не `SUPERSEDED`), содержащие документ с теми же
|
||||
`document_type`, `osv_account_code`, `period_year`, `period_quarter`, переводятся в `SUPERSEDED`
|
||||
(`superseded_by_file_id` = новый файл); `version_no` нового = максимум заменённых + 1. Новый файл без контекста (`consolidation_id IS NULL`) наследует `consolidation_id` первого (по `id`) заменённого файла, у которого он задан; файл, загруженный в консолидацию, сохраняет свою (v5.18). Заменяется и файл с `CONFIRMED`
|
||||
@ -710,6 +773,11 @@ replay: dlq → queued (`attempt + 1`). `max_attempts`: OCR 1, парсер 3; `
|
||||
`PERIOD_REQUIRED` (поправьте дату отчёта, п. 9); непройденные неакцептованные жёсткие проверки §7 — 409
|
||||
`CHECKS_NOT_PASSED` (список нарушений — в `detail.failures`: `column_index`, `relation`, `expected`, `actual`); порядок
|
||||
409: `FILE_SUPERSEDED` → `ALREADY_CONFIRMED` → `DOCUMENT_NOT_CONFIRMABLE` → `PERIOD_REQUIRED` → `CHECKS_NOT_PASSED`.
|
||||
**С v2.0** (Р-13, Р-18) порядок: `FILE_SUPERSEDED` → `ALREADY_CONFIRMED` → `DOCUMENT_NOT_CONFIRMABLE` →
|
||||
`PERIOD_REQUIRED` → `LINES_UNRESOLVED` (есть строки `unreadable` / `missing` с непустой суммой; `detail.lines[]
|
||||
{line_id, row_index, line_code_text}`; строки без сумм не мешают) → `DUPLICATE_FORM` (в файле уже подтверждена другая
|
||||
форма того же типа и периода; `detail.document_id`; между файлами — автозамена п. 3) → `CHECKS_NOT_PASSED`.
|
||||
Автоподтверждения `LINES_UNRESOLVED` не касается (`done` таких строк не допускает).
|
||||
При подтверждении пишется `financial_statement_meta.reviewed_by/at`. Несовпадение контрагента (`MISMATCH`) и низкая
|
||||
уверенность значений подтверждение **не блокируют** (как у ОСВ): защита — статус `NEEDS_CHECK` и подсветка. Мягкие проверки
|
||||
(в том числе межформенная) не блокируют. `DISCLOSURE`/`OTHER` — без условий. Привязка к версиям и переходы статуса версии —
|
||||
@ -750,6 +818,27 @@ replay: dlq → queued (`attempt + 1`). `max_attempts`: OCR 1, парсер 3; `
|
||||
без изменений — 200 без аудита. Автозамена при правке не запускается (только при маппинге): правка даты, давшая период
|
||||
другого файла ЮЛ, привязывает документ к версии лишь при подтверждении. Порядок блокировок — как у подтверждения (файл →
|
||||
документ).
|
||||
**С v2.0** (Р-6, Р-13; change `unified-statement-normalization`) PATCH дополнительно принимает:
|
||||
`values[] {line_id, column_id, confirmed: true}` — подтвердить ячейку без изменения (`is_confirmed`,
|
||||
`confirmed_by/at`, `needs_review = false`, `is_modified` не ставится); `columns[] {column_id, period_date}` — дата
|
||||
колонке без подписи или с неуверенной подписью (пересчитывает `is_current` и период документа); `lines[] {line_id,
|
||||
line_code}` — назначить или сменить код; `lines[] {line_id, line_status: "detail"}` — отметить расшифровкой; `lines[]
|
||||
{line_id, confirmed: true}` — подтвердить неуверенный код. Переходы: `unreadable` / `missing` / `detail` → `ok`
|
||||
назначением кода; `unreadable` / `missing` / `ok` → `detail` отметкой; `ok` → `ok` сменой кода (прежний — в
|
||||
`line_code_original_value`). Назначенный или подтверждённый код — `line_code_confidence = 100`, `line_code_modified`,
|
||||
`needs_review` строки = `false`; у `detail` родитель пересчитывается как ближайшая строка `ok` выше по `row_index`. Код
|
||||
— 4–5 цифр (422 `INVALID_LINE_CODE`), может совпадать с существующим (Р-4); незнакомый словарю — `info`; шаблонная
|
||||
строка с тем же кодом, созданная правкой с суммами, — 409 `LINE_EXISTS`, без сумм — удаляется; `line_status = ok`
|
||||
без кода — 422 `LINE_CODE_REQUIRED`; элемент без действия или с несколькими — 422 `INVALID_LINE_PATCH`; чужая строка —
|
||||
422 `UNKNOWN_LINE`, чужая колонка — 422 `UNKNOWN_COLUMN`; правка суммы по `line_code` при наличии строки `ok` с этим
|
||||
кодом — 422 `UNKNOWN_CELL` (передайте `line_id`). Удаления строк нет (первичка не мутируется, `detail` — способ вывести
|
||||
строку из сумм). Строки, созданные правкой: `row_index = null`, `source_ref = null`, `line_status = ok`. Правка
|
||||
`header.report_date` пересчитывает `period_end` колонок с `period_kind = relative:*` общим кодом (`null` — `null`).
|
||||
После правки ядро пересчитывает `checks`, `needs_review` и статус копией `arm_statements` (§11.9): правленое или
|
||||
подтверждённое значение и строка не имеют `needs_review`, акцепт снимает жёсткие нарушения; записи `document_issue`
|
||||
видов `relation` / `soft` заменяются целиком, предупреждения сервиса сохраняются, кроме снимаемых правкой (таблица
|
||||
§11.9). Расхождение `statements_version` задания и копии ядра — `info` `STATEMENTS_VERSION_MISMATCH`. Один PATCH —
|
||||
один пересчёт и одна запись `RECOGNIZED_DATA_EDIT` (`before/after` по кодам, статусам, родителям, подтверждениям).
|
||||
10. **Исходный файл и комментарии (Срез 3b).** `GET /files/{id}/content` (`DOCUMENT_VIEW`, policy по ЮЛ) отдаёт исходный
|
||||
объект из S3 потоком через ядро (в контуре MinIO для пользователя может быть недоступен, а через ядро действует
|
||||
видимость); работает и для `SUPERSEDED`; объекта нет в хранилище — 404 `FILE_CONTENT_NOT_FOUND`. Комментарии к документу —
|
||||
@ -912,262 +1001,445 @@ OQ-92 (пустые «Организация»/«Группа компаний»
|
||||
|
||||
---
|
||||
|
||||
## §11. Маппинг JSON ИС Сканирование (FNR01/FNR02) → формы
|
||||
## §11. Нормализация форм ББ/ОФР — общая цепочка (scan-gateway и tabular-parser)
|
||||
|
||||
Формат ответа — **по реальным файлам** `fixtures/ocr/aep_3.json`, `aep_3_ofr.json`, `aep_6.json` (проверено 04.10.2026; ТЗ
|
||||
и первая редакция этого параграфа расходятся с ними — см. OQ-13, OQ-14, OQ-77):
|
||||
**Статус: целевое правило** change `unified-statement-normalization` (RSHB/arm-ka-docs#116; дизайн —
|
||||
`openspec/changes/unified-statement-normalization/design.md`, решения владельца Р-1…Р-19 от 17.10.2026), контракт
|
||||
v2.0. Порядок выкладки — «сначала ядро» (ядро принимает 1.x и 2.0); пока сервис пишет 1.x, он работает по прежним
|
||||
правилам — прежний текст §11, §11.1 и §12 (Срезы 2b, 3a, 3b) — в истории git репозитория docs, коммит `43b4b80`.
|
||||
§11 объединяет прежние §11 (маппинг json ИС), §11.1 (очистка OCR-текста) и §12 (ББ и ОФР из Excel); §12 оставлен
|
||||
заголовком-ссылкой.
|
||||
|
||||
- Каталог ответа `<job_id>/` содержит **несколько json** — по одному на распознанный документ файла (пример: `aep_3.json` —
|
||||
успех, `aep_3_ofr.json` — ошибка по второму документу того же PDF). Шлюз читает все json каталога и склеивает в один ответ
|
||||
(`raw/{job_id}.json`: `{job_id, files[{name, response, parse_error}]}`); ошибка одного файла не отменяет остальные.
|
||||
- Корень файла: `{file_uid, error {code, message}, data_ocr}`. **Успех — `error.code == ""`** (поле `error` не `null`, а
|
||||
`{"code": "", "message": ""}`); ошибка — непустой `code` (`INVALID_DOCUMENT`, `FILE_READ_ERROR`, `INVALID_FORMAT`,
|
||||
`RECOGNITION_FAILED`, `UNKNOWN_ERROR`), `data_ocr` с пустыми полями и `table_structure: []`.
|
||||
- `data_ocr` — **объект** (не список): `label`, `organization`, `inn`, `units`, `date` — каждый `{form_name, confidence}`
|
||||
(в `form_name` шумный OCR-текст; `date.form_name` ненадёжна, ИНН может быть мусором), `table_structure` — **список таблиц**
|
||||
`{label {form_name, confidence}, rows[]}`: `label.form_name` «Бухгалтерский баланс» → BALANCE, «Отчет о финансовых
|
||||
результатах» → INCOME. **Наблюдается:** в одном json может быть **несколько таблиц разных форм** (в `aep_3.json` и
|
||||
`aep_6.json` — баланс и ОФР), каждая со своим `label`; тип документа задаёт `label`. Разбиение одной формы на несколько
|
||||
таблиц (сегменты) в образцах **не встречалось** и не предполагается; если одна форма вдруг придёт двумя таблицами или
|
||||
`label` повторится — это нештатный случай: документ не создаётся молча, а уходит в `NEEDS_CHECK` с причиной (решение —
|
||||
Срез 3, OQ-77). Неизвестный `label` → `OTHER`.
|
||||
- `rows[]`: `row_index`, `cells[] {column_index, value, confidence}`; `column_index` не обязательно подряд (бывают 0, 1, 2, 4, 5),
|
||||
`value` — сырой OCR-текст (мусор в ячейках, переносы строк, скобки для отрицательных, пробелы-разделители тысяч).
|
||||
В `aep_6.json` `rows[0]` — строка-шапка («Наименование показателя», «Код», «На 30 июня 2025 г», …); в `aep_3.json` шапки
|
||||
**нет** — первая строка данных. Бывают строки без кода (расшифровки «в том числе…», OQ-36) и код-строка внутри ячейки
|
||||
наименования («АКТИВ / I. Внеоборотные активы / Гудвил» с кодом 1105).
|
||||
- `confidence` — **числа 0–100** (в ТЗ — доли 0..1; в реальных файлах проценты) → `numeric(5,2)` как есть.
|
||||
Источник требований: ТЗ 6.2 («парсинг ОСВ, бухгалтерского баланса и ОФР»), DOC-037734, DOC-221281, FNR01/FNR02; формат —
|
||||
Приказ 66н / ФСБУ 4/2023, на практике — скан бланка (ИС Сканирование), выгрузка 1С («Регламентированный отчёт»), шаблон
|
||||
банка и вручную набитая таблица.
|
||||
|
||||
Маппинг (Срез 3): таблица `table_structure[i]` с `label` «Бухгалтерский баланс» / «Отчет о финансовых результатах» →
|
||||
`document` (BALANCE/INCOME; `locator = file:<имя json>;table:<i>` — так его формирует scan-gateway для текстового
|
||||
PDF-пути, решение №1 спеки 3a; `pages:…` — только если ИС отдаст номера страниц) + `financial_statement_meta`
|
||||
(ИНН, имя, units, report_date, okud — из `organization/inn/units/date.form_name`, с низкой уверенностью → `NEEDS_CHECK`) +
|
||||
`financial_statement_column` (из шапки `rows[0]`, если она есть; `period_date` распарсить из «На 30 июня 2025 г», `is_current` =
|
||||
первая; без шапки — колонки без дат, документ `NEEDS_CHECK`) + `financial_statement_line` (`cells[1]` код, `cells[0]` имя;
|
||||
только строки с кодом) + `financial_statement_value` (число: убрать пробелы/`-`, скобки → минус; `confidence`; `is_recognized`).
|
||||
Недостающие строки формы ядро достраивает на экране по справочнику редакции (§7, arm-core#80).
|
||||
Сверки: ИНН/имя vs `legal_entity` → `document.counterparty_check`; дата vs период версии → `period_check`.
|
||||
**Принципы.**
|
||||
|
||||
### §11.1 Очистка OCR-текста (перенос из старого ядра, Срез 3)
|
||||
1. **Один выход на шаг.** Код, зависящий от источника, живёт только в шаге 1 (адаптеры «json ИС → кандидат», «лист
|
||||
Excel → кандидат»). Шаги 2–8 — общий код с общими словарями. Единственное место, где общий код смотрит на источник, —
|
||||
разбор запятой и точки в сумме (шаг 4).
|
||||
2. **Ничего не выбрасывать молча.** Строка, колонка или ячейка, которую не удалось истолковать, идёт дальше со
|
||||
статусом, а не превращается в счётчик; положение в источнике (`ref`), порядок строк (`row_index`) и сырой текст
|
||||
ячейки (`text`) доезжают до базы и экрана. Подтвердить документ, не разобрав строки с суммами без кода, нельзя (Р-13).
|
||||
3. **Качество — структурно и с привязкой к месту:** уверенность ячейки (от источника), `is_recognized` (разбор числа),
|
||||
статус строки, нарушения §7 по ячейкам, предупреждения с уровнем и привязкой. Уверенность, `is_recognized` и §7
|
||||
сервис сводит в один признак `needs_review` у значения, строки и поля шапки и из них выводит статус документа. Ядро
|
||||
при загрузке ничего из этого не пересчитывает.
|
||||
4. **Один словарь — одно место:** проверки §7 (полный и упрощённый наборы), периоды, заголовки форм, подписи колонок,
|
||||
упорядоченные перечни кодов форм по редакциям и вариантам (без наименований), диапазоны кодов — в общем коде
|
||||
сервисов; соответствие «код → наименование» — только в ядре (Р-3).
|
||||
5. **Очистка OCR — не отдельный шаг**, а первая стадия типовых разборов текста там, где уже известен смысл ячейки (дата,
|
||||
подпись колонки, код, разлепление, сумма).
|
||||
|
||||
Источник — модуль старого ядра `DKB_ARMKA` `api/src/services/ofr_result_conversion/` (`utils.py`, `date_utils.py`,
|
||||
`consts.py`; в локальном `reference-old-arm` тот же код лежит в `api/src/core/PDFWorker/`). Решение владельца
|
||||
06.10.2026: модуль **проверен на реальном стенде ИС Сканирование, его код можно переносить** в нормализатор `scan-gateway`.
|
||||
Это **единственное исключение** из правила «доменный код из `reference-old-arm` не переносить» (CLAUDE.md workspace).
|
||||
Переносятся чистые функции очистки и разбора; обвязка старого ядра (`json_to_db_rows`, dataclass'ы `Meta/DbRow/DbValue`,
|
||||
прямая запись в `financial_statement_*`) не переносится — результат ложится в `statementDocument` контракта v1.2
|
||||
(`string_value`, `confidence`, `is_recognized`, `period_date`, `period_text`, `line_code_confidence`). Перенос — с тестами
|
||||
на `fixtures/ocr/aep_*.json`; изменения правил ниже — только через этот параграф.
|
||||
**Где код.** Шаги 2–8, общие разборы и словари — пакет `arm_statements/` репозитория `arm-statements` (Р-1); в
|
||||
tabular-parser, scan-gateway и arm-core он лежит копией (раскладывает и проверяет `sync-statements` в docs, прямые правки
|
||||
копии запрещены, маркер версии — `service.statements_version`). Golden-тесты пакета — на синтетических и обезличенных
|
||||
данных внутри пакета; тесты на `fixtures/` остаются в сервисах. Изменения правил ниже — только через этот параграф и
|
||||
словари пакета.
|
||||
|
||||
1. **Буквы-двойники рядом с цифрами** (`date_utils._normalize`, `_LOOKALIKES`): `О/о/O/o → 0`, `З/з → 3`,
|
||||
`l/I/|/і → 1`, `б → 6`, `S/s → 5`, `Ч → 4` — заменяются **только если символ стоит вплотную к цифре**, в несколько
|
||||
проходов («2ОО5» → «2005»); слова («Общество») не портятся. Если первая строка значения состоит только из цифр,
|
||||
пробелов, `|`, точек и двоеточий — `|` считается разделителем клеток формы («2|0|2|4|» → «2 0 2 4»), а не единицей.
|
||||
**Применяется только к датам** — дате отчёта (шапка) и подписям колонок значений; коды строк очищаются п. 6
|
||||
(`clean_value`), суммы — п. 7 (`parse_amount`), ИНН — только цифры (п. 8). Замена двойников внутри кодов и сумм на
|
||||
стенде не проверялась, поэтому не делается (решение №22 спеки 3a): код с латинской `O` вместо нуля не распознаётся
|
||||
как целое и остаётся только в `raw/`.
|
||||
2. **Даты из OCR-текста** (`date_utils._parse_date`): сначала текстовые формы по всей строке в порядке приоритета —
|
||||
диапазон «с 01.01.2025 по 30.09.2025» (берётся конец), «30 сентября 2025 г./r./года» (латинская `r` вместо `г`),
|
||||
«I квартал 2026» / «1-й кв.» / «первый квартал», «1 полугодие» / «1 п/г», «9 месяцев 2025», «январь – сентябрь 2025»
|
||||
(последний месяц), «за 2025 г.» / «2025 год» (31.12); затем числовые — три группы цифр («30.09.2025», «2025 03 31»,
|
||||
«30/09/25»), иначе цифры склеиваются: 8 цифр → ДДММГГГГ / ГГГГММДД / ГГГГДДММ (первый валидный вариант), 4 → год
|
||||
(31.12, только 1990–2100), 6 → ДДММГГ; хвост «0:00:00» отбрасывается. Двузначный год → 20YY. Период квартала/полугодия/
|
||||
N месяцев → последний день последнего месяца. Не дата (название организации) → `None`. Разборщик общий с §12 п. 4
|
||||
(Excel), там на вход приходят чистые строки. **Отличие от источника (решение №31 спеки 3a):** текст длиннее 200
|
||||
символов не разбирается (`None`, двойники к нему не применяются) — регулярки квартала и полугодия дают
|
||||
экспоненциальное/кубическое backtracking на длинной строке; настоящая дата занимает десятки символов.
|
||||
3. **Раскладка колонок** (`_build_layout`, `_infer_layout`): шапка — строка с ячейкой, начинающейся на «код» (после
|
||||
нормализации: нижний регистр, всё не-буквенно-цифровое → пробел); наименование — ячейка с «наименование», иначе ближайшая
|
||||
заполненная слева от кода; колонки значений — правее кода, где в тексте есть четыре цифры года или текст начинается на
|
||||
«на » / «за ». Повторная шапка внутри таблицы **перезаписывает** раскладку. **Без шапки** раскладка выводится по данным:
|
||||
код — столбец с наибольшим числом значений из справочника кодов строк; наименование — столбец левее кода с наибольшим
|
||||
числом текстовых ячеек; значения — столбцы правее кода, где суммы не реже мусора (полоса скана) или столбец пуст
|
||||
(период без данных); шапка синтезируется (`Код` / `Наименование показателя`, тексты периодов пустые). Строки до первой
|
||||
настоящей шапки разбираются по выведенной раскладке. Ни шапки, ни столбца кода — таблица пропускается с warning.
|
||||
**Рулинг 06.10.2026 (вместо прежнего «без шапки → колонки без дат, `NEEDS_CHECK`»):** раскладку выводить, как в старом
|
||||
ядре, но документ без шапки всё равно отдавать `needs_check` с причиной в `metrics.warnings` — колонки без `period_date`
|
||||
аналитик подтверждает руками.
|
||||
4. **Классы строк** (`_classify_row`): пустой код → строка пропускается; нечисловой код → заголовок (не данные);
|
||||
наименование «Баланс» → `BALANCE`, начинается с «итого» → `TOTAL`, иначе обычная строка. Берутся только строки с
|
||||
целым кодом (как в §11 выше); расшифровки без кода — OQ-36. Строки, отброшенные классификацией, **не восстанавливаются**
|
||||
(поведение источника), но о них выдаётся предупреждение уровня таблицы (решение №29 спеки 3a): `строк с нечисловым
|
||||
кодом пропущено: N` — код непустой и не целое число (`1150 :`); `строк без кода с суммами пропущено: N` — код пуст, но в
|
||||
колонках значений есть разобранная сумма (в `aep_3.json` «БАЛАНС» и «Выручка» имеют пустой код, мусор `211(1 |` стоит в
|
||||
колонке значения; как в Excel-пути, OQ-141). Строки с пустым кодом без сумм (заголовки групп, «в том числе:»)
|
||||
предупреждения не дают. Любая такая таблица — `needs_check`; восстановление строк по наименованию — OQ-149.
|
||||
**Склеенные строки** (OQ-156, решение владельца 08.10.2026): ИС может вернуть две соседние строки формы одной строкой —
|
||||
в ячейке кода несколько кодов через перевод строки, суммы тоже через перевод строки (ББ РБ и Счастливочной за 1 кв.
|
||||
2025: код `1500\n1700`, наименование `Итого по разделу V\n\nБАЛАНС`, суммы `917 775\n2 371 333`). До классификации
|
||||
такая строка **разлепляется**: если в ячейке кода N ≥ 2 непустых частей, каждая — целое число, и в каждой ячейке
|
||||
колонок значений либо пусто, либо ровно N непустых частей, получается N строк: i-й код, i-я часть каждой ячейки
|
||||
значений (пустая ячейка — пусто во всех), наименование — i-я из непустых частей, если их тоже N, иначе всё
|
||||
наименование у каждой; уверенность — исходной ячейки. Предупреждение уровня таблицы `строк с несколькими кодами
|
||||
разлеплено: N` (документ `needs_check`: разбивку подтверждает аналитик). Число частей не сходится хотя бы в одной
|
||||
ячейке — строка не разлепляется и отбрасывается как нечисловой код (предупреждение выше).
|
||||
**Повтор кода** в таблице: остаётся первая строка, остальные не попадают в документ, выдаётся предупреждение «код N
|
||||
повторяется: оставлена первая строка», документ `needs_check` (как §12 п. 5: в БД `financial_statement_line`
|
||||
уникален по `(meta_id, line_code)`; старое ядро дубли не убирало — решение №15 спеки 3a). Известный недостаток: в
|
||||
`aep_6` (ОФР) код 2410 повторён, первая строка пуста, а отброшенная содержит `(649)` — OQ-152 (сумма отброшенной
|
||||
строки остаётся в `raw/`).
|
||||
5. **Тип таблицы** (`_resolve_table_type`): по первой цифре кодов строк данных — больше `1xxx` → BALANCE, больше `2xxx` →
|
||||
INCOME; это запасной путь, когда `label.form_name` зашумлён; при расхождении с `label` — `needs_check`.
|
||||
6. **Код и наименование строки**: код очищается до букв, цифр, пробела и точки (`_clear_value`: переносы строк и
|
||||
табуляции → пробел, остальное выбрасывается); наименование берётся **из справочника по коду** (`consts.codes_mapping`;
|
||||
у нас — `api/rules/statement_lines.yaml` **scan-gateway** с редакциями форм `66n` / `fsbu4`, см. п. 10, а не
|
||||
`rules/statement_lines.yaml` из §12 — в tabular-parser такого файла нет), а из OCR — только если кода нет в справочнике
|
||||
выбранной редакции (`line_code_confidence < 100`, не выше 99). Код считается распознанным (`line_code_recognized`), если
|
||||
`confidence == 100`, очищенный текст равен сырому и код есть в справочнике.
|
||||
7. **Суммы** (`_parse_amount`): пробелы убираются; **сначала разделитель тысяч** (см. ниже), затем запятая → точка;
|
||||
содержимое скобок — отрицательное; из строки
|
||||
удаляется всё, кроме цифр, `+`, `-`, `.`; если точек больше одной, **удаляются все** (запятая уже стала точкой:
|
||||
`1.234,56` → `123456`, дробная часть не сохраняется, `is_recognized = false`); `Decimal`, иначе `None`. Прочерк, «–», «—», пусто — пустое значение (NULL, не 0). `string_value` — текст ячейки как распознан
|
||||
(`\n`/`\t` → пробел), **только если сумма не разобрана** (`amount = null`) и ячейка не пуста и не прочерк (DBML:
|
||||
«как распознано, если не число»); у разобранных сумм — `null` (решение №16 спеки 3a).
|
||||
**Перенесено дословно (расхождения с желаемым — OQ-147, OQ-148):** закрывающая скобка, распознанная как `}` или `]`
|
||||
(`(7744536}`), минус не ставит — сумма положительная, `is_recognized = false` (решение №12); длинное/среднее тире
|
||||
«—»/«–» при `confidence = 100` даёт `is_recognized = false` (тире вырезается как «лишний» символ), дефис «-» — `true`
|
||||
(решение №20). Скобки сами `is_recognized` не портят: признак считается до постановки минуса (решение №13; в источнике
|
||||
любая сумма в скобках получала `false`).
|
||||
**Разделитель тысяч** (AURORA-1405, scan-gateway#35, решение владельца 15.10.2026; отличие от источника, где «4,200» в
|
||||
PDF означало 4 200, а в прежнем АРМ показывалось как 4,2; в scan-gateway до этого правила ячейка отсекалась решением №30:
|
||||
`amount = null`, текст в `string_value`): если текст ячейки после `strip` и удаления пробелов, с учётом скобок
|
||||
(шаблон применяется к содержимому скобок, только если пара скобок занимает всю строку) и знака, полностью совпадает с `[+-]?[1-9]\d{0,2}(,\d{3})+`, запятые — разделители
|
||||
тысяч и **удаляются до** замены запятой на точку: `4,200` → 4200, `(4,200)` → −4200, `1,234,567` → 1234567, `-4,200` →
|
||||
−4200, `+4,200` → 4200. Удаление таких запятых не считается отбрасыванием символов: `is_recognized` определяется только
|
||||
`confidence == 100`. Остальное без изменений: `4,2` → 4.2, `12,34` → 12.34, `0,125` → 0.125 (далее отсекается решением
|
||||
№30), `1,23` → 1.23, `1 234,56` → 1234.56, `1.234,56` и `1,234.56` → 123456 (`is_recognized = false`); `12,3456`,
|
||||
`4,200,`, `(4,200}`, `(4,200) руб`, `abc(4,200)` и `(4,200)(1,000)` шаблону не соответствуют, разбор прежний.
|
||||
**Точка — разделитель тысяч** (AURORA-1405, scan-gateway#37, решение владельца 16.10.2026: ИС Сканирование местами
|
||||
возвращает «4.200», «837.994»; такие суммы разбирались как 4.200 с тремя знаками и отсекались решением №30 в
|
||||
`amount = null`, текст в `string_value`): то же правило, но шаблон шире (решение владельца 16.10.2026: если после точки ровно три цифры — это разделитель тысяч независимо от числа цифр до неё; если суммы не сойдутся, аналитик увидит это на проверках): `[+-]?[1-9]\d*(\.\d{3})+` — те же условия
|
||||
(весь текст ячейки после `strip` и удаления пробелов или содержимое пары скобок на всю строку), точки удаляются,
|
||||
`is_recognized` определяется только `confidence == 100`: `4.200` → 4200, `837.994` → 837994, `(4.200)` → −4200,
|
||||
`-4.200` → −4200, `1.234.567` → 1234567 (раньше при нескольких точках — `is_recognized = false`), `1234.567` →
|
||||
1234567, `12345.678` → 12345678, `1 234.567` → 1234567, `(1 234.567)` → −1234567. Остальное без изменений: `4.2` → 4.2,
|
||||
`4.20` → 4.20, `12.34` → 12.34, `1234.56` → 1234.56 (дробная часть не из трёх цифр — дробь); `0.125` и `01.234` (первая
|
||||
цифра нулевая — не тысячи) → дробь, при трёх знаках отсекается решением №30; `1.2345` — текст. Правило для запятой
|
||||
этим **не расширяется**: `1234,567` и `1 234,567` по-прежнему дробь 1234.567 (отсекается №30), тысячи — только
|
||||
`\d{0,2}` цифр до первой запятой. Смешанные записи
|
||||
(`1.234,56`, `1,234.56`, `1.234,567`) и «грязные» (`4.200.`, `(4.200}`, `(4.200) руб`, `1.234.56`) шаблону не
|
||||
соответствуют, разбор прежний. Правило действует только на ячейки колонок значений таблицы ББ/ОФР (`parse_amount` не
|
||||
вызывается для дат, кодов строк, ОКЕИ, ИНН); дробь с тремя знаками после точки и так не принималась (`Money`, №30).
|
||||
**Отличия от источника под контракт** (решение №30 спеки 3a): сумма с **более чем двумя** знаками после запятой
|
||||
(например `0,125`, `12.3456`) не укладывается в `Money` контракта (`numeric(18,2)`) — `amount = null`, `string_value` —
|
||||
текст ячейки, `is_recognized = false` (иначе один артефакт OCR валил всю таблицу); `1,234` под это не попадает —
|
||||
разбирается правилом тысяч выше (= 1234);
|
||||
`confidence` ячейки и полей шапки приводится к диапазону 0–100, NaN → 0.
|
||||
**`is_recognized`** значения = `confidence == 100` **и** разбор суммы не потребовал выбрасывать символы (сырой текст без
|
||||
скобок равен очищенному). Для ячеек шапки: `is_recognized` = `confidence == 100` и в тексте не было мусора; если дата
|
||||
разобралась — `period_text` нормализуется в «На ДД.ММ.ГГГГ», `period_date` — результат п. 2. Ячейки под колонкой
|
||||
заголовка нет — пустое значение с `is_recognized = false`, чтобы не сдвигать столбцы.
|
||||
8. **Шапка документа**: ИНН — только цифры из `inn.form_name`, «распознан», если `confidence == 100` и в сыром тексте не
|
||||
было ничего, кроме цифр (предупреждение уровня файла: `ИНН не распознан` — в поле нет ни одной цифры; `ИНН распознан
|
||||
неуверенно` — цифры извлечены, но `confidence` ≠ 100 или убран мусор); единицы — `_clear_value`, тот же критерий; дата отчёта — п. 2, «распознана», если
|
||||
`confidence == 100` и дата разобралась. Нераспознанное → `needs_check` (как в §11).
|
||||
9. **Ошибка разбора одной таблицы** (исключение в разборе `table_structure[i]`) не роняет файл: выдаётся предупреждение
|
||||
уровня таблицы `BACKEND_PARSING_ERROR: <класс исключения>: <сообщение>`, остальные таблицы файла разбираются;
|
||||
хотя бы один документ есть — конверт `needs_check` (статус не бывает `done`, пока есть предупреждение уровня таблицы),
|
||||
ни одной таблицы не разобрано — `error` с кодом `RECOGNITION_FAILED` («ни одной таблицы не разобрано», причины — в
|
||||
`metrics.warnings`). Ошибочный файл набора ответа при успешном соседе (например, `<job_id>_ofr.json` с
|
||||
`INVALID_DOCUMENT`) документы успешных файлов не отменяет: ошибка попадает в `metrics.warnings` строкой
|
||||
`<имя json>: <код ИС>: <сообщение>`, такой файл **понижает статус до `needs_check`** (`done` невозможен, решение №28 спеки 3a: потерянный документ, например ОФР,
|
||||
не должен остаться незамеченным).
|
||||
10. **Редакции форм** (`api/rules/statement_lines.yaml`): `66n` (Приказ Минфина № 66н, отчётность по 2024 год включительно)
|
||||
и `fsbu4` (ФСБУ 4/2023, с 2025 года; **пока пуста** — состав кодов даёт владелец, OQ-150). Редакция документа выбирается
|
||||
**по дате отчёта** шапки, если её нет — по **самой поздней `period_date`** колонок значений, если нет и её — по
|
||||
**числу совпадений кодов** строк со словарями редакций (с предупреждением «редакция определена по кодам»); если и кодов
|
||||
не хватает — редакция **не определена**: имена строк из OCR, предупреждение «редакция не определена», `needs_check`.
|
||||
Имя строки — из словаря выбранной редакции по коду, из OCR — только если кода в нём нет. **Пустая** выбранная
|
||||
редакция → имена строк из OCR, одно предупреждение «редакция `<id>` не заполнена», `needs_check`. Предупреждение «код N из редакции X»
|
||||
(код есть в другой редакции, но не в выбранной) выдаётся только для **непустой** выбранной редакции — иначе каждая
|
||||
строка формы 2025 года давала бы шум (решение №23 спеки 3a). Редакция формы в конверт не передаётся (OQ-145).
|
||||
### §11.1 Источник OCR: ИС Сканирование (адаптер шага 1 в scan-gateway)
|
||||
|
||||
**Формат ответа** — по реальным файлам `fixtures/ocr/*.json` (проверено 04.10.2026; OQ-13, OQ-14, OQ-77):
|
||||
|
||||
- Каталог ответа `<job_id>/` содержит **несколько json** — по одному на распознанный комплект отчётности. Шлюз читает
|
||||
все json каталога и склеивает их в `raw/{job_id}.json` (`{job_id, files[{name, response, parse_error}]}`) **до**
|
||||
нормализации; ошибка одного json не отменяет остальные. Допущение владельца (17.10.2026): несколько периодов в одном
|
||||
файле ИС отдаёт разными json; образца нет — проверить на первом таком файле (OQ-167).
|
||||
- Корень: `{file_uid, error {code, message}, data_ocr}`. **Успех — `error.code == ""`**; ошибка — непустой `code`
|
||||
(`INVALID_DOCUMENT`, `FILE_READ_ERROR`, `INVALID_FORMAT`, `RECOGNITION_FAILED`, `UNKNOWN_ERROR`).
|
||||
- `data_ocr` — объект: поля шапки `label`, `organization`, `inn`, `units`, `date` — каждое `{form_name, confidence}` (сырой
|
||||
OCR-текст; `date.form_name` ненадёжна — у aep_6 и «БО 1 кв 2025» там название организации), **одна шапка на json**;
|
||||
`table_structure[]` — таблицы `{label {form_name, confidence}, rows[{row_index, cells[{column_index, value,
|
||||
confidence}]}]}`; `column_index` не обязательно подряд; `confidence` — проценты 0–100 (NaN → 0). В одном json
|
||||
встречаются ББ и ОФР одного периода под общей шапкой. ИС отдаёт ячейку на каждую позицию строки: пустая — пустой
|
||||
текст с уверенностью 100, непрочитанная — пустой текст с уверенностью 0.
|
||||
|
||||
**Адаптер.** Каждая таблица успешного json — кандидат (`FormCandidate`): `source = ocr`; `locator = file:<имя
|
||||
json>;table:<i>` (как в golden-эталонах); шапка json копируется в каждую свою таблицу (`Cell.text = form_name`,
|
||||
`confidence` ИС); `label.form_name` таблицы — заголовок в `evidence` (тип — правилом §11.2 «тип-подсказка» по словарю
|
||||
заголовков; прежний `statement_labels.yaml` уходит в словарь пакета); сетка строится по `row_index` и `column_index`
|
||||
плотно, пустая позиция — `null`; `Cell.ref = row:<row_index>`, `value = null`.
|
||||
|
||||
**Ошибки набора.** Ошибочный json (`parse_error`, непустой `error.code`, корень не объект) при хотя бы одном успешном
|
||||
соседе — предупреждение файла `JSON_FAILED` (`error`, `locator = file:<имя json>`, в тексте код и сообщение ИС; решение
|
||||
№ 28 среза 3a: потерянный документ, например ОФР, не должен остаться незамеченным); все json ошибочны — конверт `error`
|
||||
с кодом первого. Исключение при разборе таблицы — `TABLE_PARSE_ERROR` (`error`) документа, остальные таблицы
|
||||
разбираются; ни одной таблицы не разобрано — `error RECOGNITION_FAILED`.
|
||||
|
||||
**Перенос из старого ядра.** Источник очистки и разборов — модуль `DKB_ARMKA` `api/src/services/ofr_result_conversion/`
|
||||
(`utils.py`, `date_utils.py`, `consts.py`; локально — `reference-old-arm/api/src/core/PDFWorker/`); решение владельца
|
||||
06.10.2026: проверен на стенде ИС Сканирование, перенос разрешён — **единственное исключение** из правила «доменный код из
|
||||
`reference-old-arm` не переносить». Перенесённые функции теперь — общие разборы пакета (§11.3–§11.6), отличия от
|
||||
источника перечислены там.
|
||||
|
||||
### §11.2 Источник Excel: tabular-parser (адаптер шага 1)
|
||||
|
||||
**Остаётся в сервисе:** лимиты и чтение книги (`PARSE_MAX_BYTES` 50 МБ, `PARSE_TIMEOUT_SECONDS` 120 с, формат по
|
||||
сигнатуре байтов — OLE2 → XLS, zip с `xl/workbook.bin` → XLSB, иной zip → XLSX, иначе `UNSUPPORTED_FORMAT`; несовпадение
|
||||
с `file_type` — `FILE_TYPE_MISMATCH` `info`; лимиты распакованного zip и площади объединённых ячеек; библиотека упала —
|
||||
`UNREADABLE`), классификация листов и путь ОСВ (§1).
|
||||
|
||||
1. **Классификация листов**: лист ОСВ (признак ведомости в первых 30 строках, §1) → форма → продолжение → прочее. Форма —
|
||||
заголовок («Бухгалтерский баланс», «Отчет о финансовых результатах», «…о прибылях и убытках») или ОКУД в любой строке до
|
||||
таблицы; титул «Бухгалтерская отчетность» — не тип. Продолжение — лист без заголовка, но с маркером «Форма … с.2», или
|
||||
с подписью «Код» и периодами правее, или с колонкой кодов, или с одним-двумя кодами сразу после листа формы. Прочее —
|
||||
`SHEET_OTHER` (`info`), если у листа нет ни типа, ни кодов.
|
||||
2. **Блоки форм на листе**: строки с заголовком или ОКУД — кандидаты; без кодов между ними блоки сливаются, заголовок
|
||||
после кодов — новая форма. Шапка **своя у каждого блока** в его границах: организация (после метки «Организация»,
|
||||
иначе текст с признаком ОПФ строкой выше метки), ИНН 10/12 цифр у метки «ИНН» (ИНН аудитора не берётся: строки после
|
||||
метки «аудитор» исключены), ОКПО, ОКОПФ, ОКФС, ОКЕИ, адрес, единицы, дата (подзаголовок формы или тройка «число | месяц
|
||||
| год» у метки «Дата»; для ОФР — конец периода). Шапка другого листа не используется. Найденное поле — `Cell` с
|
||||
`confidence = 100`, не найденное — `null`.
|
||||
3. **Ячейки**: `text` — как показывает числовой формат ячейки, `value` — число по формату (tp#42, OQ-138 пересмотрен
|
||||
15.10.2026): масштабирующие запятые (`#,##0,` → тысячи), число знаков после точки, секции и условия (`[=0]"-"`,
|
||||
`[=-646532]"(647)"`; литерал условной секции разбирается как текст), округление half-up, знак — от сырого значения,
|
||||
условия сравниваются с точностью 15 значащих цифр; `General` / пустой формат — значение как в ячейке; формат, который не
|
||||
удалось разобрать (`%`, экспонента, дробь, даты, `@`, неизвестные знаки вне кавычек, больше четырёх секций, условие не
|
||||
по шаблону, результат вне `numeric(18,2)`), — значение как в ячейке и одно `NUMBER_FORMAT_UNPARSED` (`info`) на документ
|
||||
с перечнем форматов; xlsb — стили не читаются, `XLSB_NO_FORMATS` (`info`). `ref = <лист>!<A1>`, `confidence = 100`.
|
||||
4. **Склейка листов-продолжений — до раскладки.** Строки продолжения дописываются в конец сетки открытой формы по
|
||||
маркеру «Форма … с.2» или по позиции листа; граница листа — в `segment_breaks` (всегда граница сегмента раскладки).
|
||||
Тип маркера сверяется с типом формы: расхождение — отдельный кандидат и `CONTINUATION_TYPE_CONFLICT` (`error`). Лист без
|
||||
маркера и шапки присоединяется, только если его коды в диапазоне открытой формы и, при трёх и более кодах, число колонок
|
||||
сумм совпадает с формой (проверки прежнего `join_blocker`); иначе — отдельный кандидат и `SHEET_NOT_JOINED` (`error`).
|
||||
`locator = sheet:<A>[,<B>…]`. **Страницы в разных файлах не склеиваются** (1С печатает форму постранично): страница 2
|
||||
без первой — документ без шапки, `needs_check` (OQ-140). Две половины формы одного типа и периода в одном файле
|
||||
склеиваются только листом-продолжением; иначе это две формы (Р-15, вопрос — OQ-165).
|
||||
5. **Пятизначные подстроки** (11101, 12301…) — обычные строки. **Шаблоны банка** (коды есть, сумм и шапки нет) —
|
||||
`needs_check` (OQ-142).
|
||||
|
||||
**Тип-подсказка (`type_hint`) — одно правило у обоих источников:** ОКУД по словарю (0710001 → `BALANCE`, 0710002 →
|
||||
`INCOME`, прочие 0710xxx — 0710003 капитал, 0710004 ДДС и др. → `DISCLOSURE`) → иначе заголовок по словарю заголовков
|
||||
(у OCR — `label` таблицы ИС; «Пояснения», «Расшифровка» → `DISCLOSURE`) → иначе `null`. Окончательный тип — по кодам строк
|
||||
(§11.7).
|
||||
|
||||
**Шапка — одно правило разбора** по `Cell.text`: ИНН — только цифры, **без замены двойников** (иначе «3|1 21 2|» из aep_3
|
||||
даёт лишние единицы и `MISMATCH` с ЮЛ), длина 10 или 12, иначе `null`; дата отчёта — словарь периодов §11.3 п. 3;
|
||||
единицы — словарь единиц. Сырой текст и уверенность каждого поля — в контракт (`<поле>_text`, `<поле>_confidence`).
|
||||
|
||||
### §11.3 Шаг 2 — раскладка таблицы
|
||||
|
||||
**Очистка OCR-текста** (общий разбор, применяется к любой ячейке без `value`; у Excel — к текстовым ячейкам, почти
|
||||
всегда без изменений). Буквы-двойники рядом с цифрами `О/о/O/o → 0`, `З/з → 3`, `б → 6`, `S/s → 5`, `I/l/|/і → 1`,
|
||||
`Ч → 4` заменяются **только вплотную к цифре**, в несколько проходов («2ОО5» → «2005»), слова не портятся; если первая
|
||||
строка значения состоит только из цифр, пробелов, `|`, точек и двоеточий — `|` считается разделителем клеток («2|0|2|4|» →
|
||||
«2 0 2 4»). **Применяется к датам, подписям колонок, кодам и суммам** (Р-11; отменяет прежнее «только к датам» и решение
|
||||
№ 22 среза 3a): сумма с заменой — `is_recognized = false`, код с заменой — неуверенный (уверенность не выше 99); в суммах
|
||||
крайние `|` (след границы таблицы: «| : 122477») удаляются, а не заменяются. ИНН двойники не трогают. Текст длиннее 200
|
||||
символов датой не разбирается (решение № 31 среза 3a: backtracking регулярок).
|
||||
|
||||
**Диапазоны кодов** (словарь пакета): ББ 1100–1799, ОФР 2100–2999, отчёт об изменениях капитала 3100–3699, отчёт о
|
||||
движении денежных средств 4100–4499, пояснения 5000–5999, отчёт о целевом использовании средств 6100–6499. «Кодовые
|
||||
диапазоны» для поиска столбца — объединение отрезков (годы 2000–2099 и четырёхзначные суммы вне отрезков кодами не
|
||||
считаются). «Диапазон столбца» — отрезок, в который попадает большинство кодов найденного столбца; им проверяются
|
||||
разлепление строк, слабые продолжения и наследование столбца в сегментах.
|
||||
|
||||
**Разбор кода:** убрать пробелы и переносы → двойники (`|` → 1, только если результат в кодовом диапазоне: «| 140» →
|
||||
1140) → краевая пунктуация («1220 ,» → 1220) → 4–5 цифр (допускается `.0`: `1110.0` из Excel), первые четыре — в кодовом
|
||||
диапазоне. Многострочная ячейка — код, только если каждая её часть код.
|
||||
|
||||
1. **Столбец кодов.** (а) Столбец с подписью из словаря («код», «коды», «код строки», «код показателя» — после
|
||||
нормализации регистра и пунктуации) над первой строкой с кодом, если под подписью доля кодов среди непустых ≥ 0,6 или
|
||||
кодов ≥ 3; несколько кандидатов — с наибольшим числом кодов; берётся последняя такая подпись над первой строкой с кодом.
|
||||
Защита обязательна: в выгрузках 1С под «Коды» титульного блока стоят ОКУД, дата, ОКПО, ИНН, ОКОПФ (12267, 12300 — «коды
|
||||
баланса» по первым цифрам), ОКЕИ — один код из девяти, доля 0,11. Документ из двух строк остаётся документом (два из
|
||||
двух, доля 1,0). (б) Подписи нет — столбец с наибольшим числом ячеек-кодов, кодов ≥ 3, доля ≥ 0,6. Нет —
|
||||
`CODE_COLUMN_NOT_FOUND`; сопоставление по наименованиям строк не делается (OQ-139).
|
||||
2. **Сегменты.** Новый сегмент открывает строка-шапка (в ней есть подпись кода из словаря, или не менее двух её непустых
|
||||
ячеек совпадают после нормализации с ячейками шапки первого сегмента) или граница листа из `segment_breaks`. У
|
||||
сегмента своя раскладка: по его шапке; без шапки — столбец кодов по п. 1, а если сегмент не первый и кодов меньше трёх —
|
||||
ослабленный поиск: столбец с наибольшим числом кодов из диапазона столбца первого сегмента, минимум один (лист «_2» НПЗ
|
||||
с единственной строкой 2400, у которого код в другой колонке). Колонки значений сегмента без шапки — столбцы правее
|
||||
кода, где в строках с кодами числа, сопоставление с первым сегментом по порядку; число не совпало —
|
||||
`SEGMENT_COLUMNS_MISMATCH` (`error`), значения по позиции. Столбец кодов не найден ни так, ни так —
|
||||
`SEGMENT_WITHOUT_CODES` (`error`); строки сегмента не теряются (роль `data`, статусы по §11.4).
|
||||
3. **Дата колонки — один словарь периодов** `statement_periods.yaml`, объединяющий прежний словарь парсера и шаблоны
|
||||
`dates.py` шлюза (порядок приоритета текстовых форм: диапазон «с 01.01.2025 по 30.09.2025» — конец; «30 сентября 2025
|
||||
г./r./года»; «I квартал 2026» / «1-й кв.» / «первый квартал»; «1 полугодие» / «1 п/г»; «9 месяцев 2025»; «январь –
|
||||
сентябрь 2025» — последний месяц; «за 2025 г.» / «2025 год» — 31.12; затем числовые: три группы цифр «30.09.2025»,
|
||||
«2025 03 31», «30/09/25»; иначе склеенные цифры — 8 → ДДММГГГГ / ГГГГММДД / ГГГГДДММ (первый валидный), 4 → год (31.12,
|
||||
1990–2100), 6 → ДДММГГ; хвост «0:00:00» отбрасывается; двузначный год → 20YY; квартал / полугодие / N месяцев → последний
|
||||
день последнего месяца; не дата → `null`). Строки `test_dates.py::test_parses_real_forms` шлюза — обязательные тесты
|
||||
словаря. Результат — `period_end` и `period_kind`:
|
||||
- абсолютные подписи → `absolute`;
|
||||
- относительные подписи бланка 66н без впечатанных дат (Р-6): «На отчётную дату», «За отчётный период» →
|
||||
`relative:current` (= эффективная дата); «На 31 декабря предыдущего года» → `relative:prev_year_end` (31.12 года
|
||||
эффективной даты − 1); «На 31 декабря года, предшествующего предыдущему» → `relative:prev_prev_year_end` (31.12 года
|
||||
− 2); «За аналогичный период предыдущего года» → `relative:prev_period` (эффективная дата − 1 год; 29.02 → 28.02);
|
||||
- подписи нет → `none`.
|
||||
|
||||
**Эффективная дата документа** — дата отчёта из шапки, иначе самая поздняя абсолютная дата колонок (та же формула,
|
||||
что период формы в ядре, §9.5 п. 2); нет ни той, ни другой → у относительных колонок `period_end = null`, вид
|
||||
остаётся относительным, ядро досчитает после правки даты отчёта. Подпись OCR с уверенностью ниже 100, разобранная в
|
||||
дату, подтверждается, если дата входит в ожидаемые: эффективная дата, она же минус один и минус два года, 31 декабря
|
||||
предыдущего и предшествующего ему года; иначе `COLUMN_DATE_UNCERTAIN` (`error`, колонка).
|
||||
4. **Строка заголовка** — последняя над первой строкой с кодом, где в столбце кодов стоит подпись кода; иначе последняя
|
||||
над первой строкой с кодом с двумя и более подписями-периодами правее кода (или одной, не голым годом); двухстрочный
|
||||
заголовок («На» + «20__ г.», перенос внутри ячейки) склеивается. Роль `title` — только строки выше найденной строки
|
||||
заголовка (титульный блок «Дата 30 | 9 | 2025»); в сегменте без заголовка роли `title` нет, строки выше первого кода с
|
||||
содержимым идут в данные. Заголовка нет ни у одного сегмента → колонки значений первого сегмента — столбцы правее кода,
|
||||
где в строках с кодами числа, `label` пустая, `period_end = null`, `period_kind = none`, одно `HEADER_ROW_NOT_FOUND`
|
||||
(`error`; прежний синтез шапки шлюза и «рулинг 06.10.2026» заменены этим правилом). Заголовок есть хотя бы у одного
|
||||
сегмента — остальные берут его колонки без предупреждения (листы 2–5 ББ НПЗ без шапки остаются `done`).
|
||||
5. **Строка нумерации** «1 2 3 4 5» — строка, где не меньше трёх непустых ячеек, каждая — целое число, и числа идут
|
||||
подряд с 1 слева направо — роль `numbering`, в данные не попадает (AURORA-1517).
|
||||
6. **Подпись без периода** (Р-6): период по словарю (абсолютный или относительный) → колонка с датой; подпись из
|
||||
словаря исключений («Изменение», «Отклонение», «%», «Темп», «Доля», «Примечание») → пропуск, `COLUMN_SKIPPED` (`info`);
|
||||
иначе, если в столбце есть числа в строках с кодами (OCR-мусор «| | г» с уверенностью 17, обрезанная или самодельная
|
||||
подпись), — колонка остаётся с подписью как есть, `period_end = null`, `period_kind = none`, `COLUMN_WITHOUT_DATE`
|
||||
(`error`, колонка); аналитик ставит дату через PATCH (§9.5 п. 9); иначе пропуск. Колонка без даты не мешает периоду
|
||||
формы (эффективная дата), но межформенная проверка и гудвилл «на отчётную дату» по ней не считаются, пока дата не
|
||||
проставлена. Уверенность подписи — `label_confidence` (Excel — 100, подписи нет — `null`).
|
||||
7. **Столбец наименования** — подпись, начинающаяся с «наименование», в строке заголовка; иначе столбец левее кода с
|
||||
наибольшим числом текстов, кроме «Пояснения» (пустые или ≤ 3 символов); наименование строки — ячейка этого столбца,
|
||||
пуста — ближайшая текстовая ячейка левее кода, кроме «Пояснения».
|
||||
|
||||
### §11.4 Шаг 3 — отбор строк данных и статусы
|
||||
|
||||
Строка данных — строка сетки с ролью `data`, у которой есть содержимое: код, число или непустой текст в колонке значений
|
||||
с уверенностью ниже 100 (признак потерянной суммы: «[190 :», «" :» в aep_3). У строки: `row_index` (номер в документе с
|
||||
0, по порядку сетки после разлепления), `ref` (уникален в документе: OCR `row:<row_index ИС>`, разлепленные
|
||||
`row:<i>#<k>` с k от 1; Excel — адрес ячейки столбца кодов строки `<лист>!<A1>`, без столбца кодов — первой непустой
|
||||
ячейки), статус, ячейки кода и наименования, ячейки значений по колонкам сегмента, `parent_ref`. **Статусы:**
|
||||
|
||||
- `ok` — код разобран;
|
||||
- `unreadable` — ячейка кода содержит хотя бы одну цифру, но кодом не разобралась («ИЧ,250», «изо :», «[190 :»); ячейка
|
||||
без единой цифры («}», «:») считается пустой;
|
||||
- `detail` (Р-8) — строка без кода с содержимым под строкой с кодом (в том же или в предыдущих сегментах — родитель
|
||||
продолжается через разрыв страницы): расшифровка («в том числе: …», «из них …», «Оборудование к установке», «Товар
|
||||
(41.01)»); родитель — ближайшая строка с кодом выше. Сервисы её отдают, ядро хранит; в проверки §7, агрегаты и `done` она
|
||||
не входит; экран по умолчанию не показывает;
|
||||
- `missing` — строка без кода с содержимым, над которой во всём документе нет строки с кодом («Выручка» aep_3, где 2110
|
||||
уехал в ячейку значения); строки роли `title` сюда не попадают.
|
||||
|
||||
Строка без кода и без содержимого (заголовок раздела «АКТИВ», «в том числе:» без сумм) — не строка. **Счётчиков
|
||||
отброшенных строк нет — есть статусы** (отменяет «строк с нечисловым кодом пропущено», «строк без кода с суммами
|
||||
пропущено», OQ-36 / OQ-141; OQ-149 — восстановление строк по наименованию — остаётся открытым). Строка формы с пустой
|
||||
ячейкой кода под другой строкой с кодом попадёт в `detail` и будет скрыта; её значения не войдут в сумму раздела,
|
||||
нарушение §7 подсветит родителя (пустая цель при непустых членах — нарушение), аналитик раскроет расшифровки и назначит
|
||||
код (Р-13) — осознанная цена Р-8.
|
||||
|
||||
**Повтор кода** (Р-4) — законный случай (один код, разные наименования; договорённость ДБД в AURORA-1431, закрывает
|
||||
AURORA-1406): все строки `ok`, порядок как в файле, ни предупреждения, ни `duplicate_line_codes` (отменяет «оставлена
|
||||
первая строка», решение № 15 среза 3a и OQ-152). Для §7 и всего после — сумма по коду (агрегат формы по коду, §9.5 п. 2).
|
||||
Неверно прочитанный код, совпавший с существующим, ловит структурная проверка (§11.9).
|
||||
|
||||
**Разлепление склеенных строк** (OQ-156; Р-17 заменяет решение владельца 08.10.2026 о безусловном `needs_check`) —
|
||||
**до** разбора кода: ячейка кода содержит N ≥ 2 частей через перевод строки, каждая непустая ячейка значений — ровно N
|
||||
частей, и хотя бы одна часть кода разобралась как код диапазона столбца («44 30\nI |» aep_6 не множится: 4430 не в
|
||||
диапазоне ББ). Получается N строк (`ref` `row:<i>#<k>`): k-й код (нечитаемая часть — строка `unreadable`), k-я часть
|
||||
каждой ячейки значений, наименование — k-я из непустых частей, если их тоже N, иначе целиком; `line_code_text` и `text` —
|
||||
k-е части; уверенность — исходной ячейки. Значения разлепленных строк — `is_recognized = false`, нуждаются в покрытии
|
||||
§7 (при сошедшихся соотношениях не подсвечиваются); само разлепление — `ROW_SPLIT` (`info`). Число частей не совпало или
|
||||
ни одна часть не код — одна строка, многострочное значение → `amount = null`, `string_value` = текст,
|
||||
`is_recognized = false`. Пример: ББ РБ и Счастливочной за 1 кв. 2025 — код `1500\n1700`, суммы `917 775\n2 371 333`.
|
||||
У Excel не срабатывает.
|
||||
|
||||
### §11.5 Шаг 4 — значения ячеек
|
||||
|
||||
Выход: `{column_index, amount, string_value, text, confidence, is_recognized}` (+ `needs_review`, §11.9). `text` — всегда
|
||||
сырой текст источника; `string_value` — только когда `amount = null` и текст непуст и не прочерк.
|
||||
|
||||
1. **`value` есть (Excel):** число → `amount` с квантованием до 2 знаков half-up, не больше 16 целых цифр (иначе `amount
|
||||
= null`, `string_value` = текст, `is_recognized = false`, `VALUE_OUT_OF_RANGE` `info`); дата или логическое → `amount
|
||||
= null`, `string_value` = текст, `is_recognized = false`.
|
||||
2. **`value` нет — `parse_amount(text, source)`**, один для обоих: двойники (§11.3) и крайние `|`; пробелы, NBSP и тонкие
|
||||
пробелы; минус «−» (U+2212) как знак; скобки → минус, `}` и `]` принимаются как закрывающая скобка (OQ-147:
|
||||
«(7744536}» → −7744536, `is_recognized = false`); прочерк (`-`, `–`, `—`, `−` без цифр) и пусто → `amount = null`,
|
||||
`is_recognized = true` (OQ-148). Разделители:
|
||||
- `ocr`: запятая — тысячи, если весь текст (после `strip` и удаления пробелов, с учётом знака и пары скобок на всю
|
||||
строку) — `[+-]?[1-9]\d{0,2}(,\d{3})+` (AURORA-1405, sg#35: `4,200` → 4200, `1,234,567` → 1234567); точка — тысячи
|
||||
при `[+-]?[1-9]\d*(\.\d{3})+` (sg#37: `837.994` → 837994, `1.234.567` → 1234567, `1 234.567` → 1234567); удаление
|
||||
таких разделителей исправлением не считается. Иначе запятая и точка — десятичный разделитель (`4,2` → 4.2, `1 234,56`
|
||||
→ 1234.56); смешанные записи (`1.234,56`, `1,234.56`) — цифры подряд, `is_recognized = false`. **Три и более знаков
|
||||
после десятичного разделителя** (`0,125`, `12.3456`) — `amount = null`, `string_value` = текст (решение № 30 среза
|
||||
3a сохраняется для OCR-текста: такое значение не укладывается в `numeric(18,2)`);
|
||||
- `excel`: запятая и точка — десятичный разделитель («4,200» = 4,20, «1.500» = 1,5; как прежний `to_money`).
|
||||
3. **Пробел внутри числа** допустим только как разделитель тысяч (группы ровно по три цифры); иначе `amount` — «лучшая
|
||||
догадка» (цифры подряд, не длиннее 16 цифр, иначе `null`), `is_recognized = false`, сырой текст — в `text` («42831 1» →
|
||||
428311; AURORA-1511, 1519). Многострочное значение в строке с однострочным кодом → `amount = null`, `string_value` =
|
||||
текст, `is_recognized = false`. Excel-ячейки «число текстом» идут тем же путём.
|
||||
4. **`is_recognized`** — один смысл у обоих: текст разобран как число без выброшенных и исправленных символов (замена
|
||||
двойника, `}` / `]` вместо скобки — исправление). `confidence` — отдельно, от источника (у Excel — 100); прежнее «`is_recognized`
|
||||
= `confidence == 100` и ничего не выброшено» шлюза отменено.
|
||||
5. **Пустая ячейка** (Р-2): позиции нет, текст пуст или прочерк → `amount = null`, `string_value = null`,
|
||||
`is_recognized = true`, `confidence` ячейки (позиции нет — 100); собственной подсветки нет. Непрочитанная ячейка ИС
|
||||
(пустой текст, уверенность 0) нуждается в покрытии по §11.9.
|
||||
6. **Единицы:** «тыс. руб.» и ≥ 10 % дробных значений → `FRACTIONAL_IN_THOUSANDS` (`info`, документ остаётся `done`;
|
||||
OQ-138 — единицам шапки верим); то же в книге xlsb без числовых форматов — `XLSB_SCALE_SUSPECT` (`error`: вероятен
|
||||
масштаб ×1000, а §7 сходится при любом масштабе).
|
||||
|
||||
Разбор сумм ОСВ (`to_money` в `classify.py`, §1) этими правилами не затрагивается.
|
||||
|
||||
### §11.6 Шаг 5 — код, наименование, редакция
|
||||
|
||||
Строка: `{row_index, ref, line_status, line_code, line_code_text, line_code_confidence, line_name, line_name_confidence,
|
||||
parent_ref, needs_review, values}`. `line_code` — разобранный код или `null` у `unreadable` / `missing` / `detail`;
|
||||
`line_code_text` — сырой текст ячейки кода; `line_code_confidence` — от источника (у кода с заменой двойника — не выше 99),
|
||||
**без срезания до 99 по справочнику**; `line_name` — распознанный текст наименования как есть, с уверенностью источника
|
||||
(Р-3: имя из справочника шлюза `api/rules/statement_lines.yaml` отменено, файл удаляется; соответствие «код →
|
||||
наименование» — только в ядре, §7 «Полный состав формы»). **Редакция** (`66n` / `fsbu4`) в сервисах нужна только для
|
||||
упорядоченного перечня кодов (§11.9) и выбирается по эффективной дате (`fsbu4` — после 2024-12-31, как `edition` ядра,
|
||||
OQ-145); без даты проверяются обе редакции. Прежний выбор редакции шлюза для имён (по дате, по кодам, «редакция не
|
||||
определена» / «не заполнена» → `needs_check`) отменён.
|
||||
|
||||
### §11.7 Шаг 6 — тип формы, вариант, документ
|
||||
|
||||
1. **Тип** — большинство кодов строк `ok` в отрезке ББ → `BALANCE`, ОФР → `INCOME`, прочих форм и пояснений →
|
||||
`DISCLOSURE`; совпало с `type_hint` или `type_hint = null` — тип по кодам; противоречит — тип по кодам и `TYPE_CONFLICT`
|
||||
(`error`); кодов нет — `type_hint`. Без столбца кодов: `type_hint` есть → документ без строк (`DISCLOSURE` — `done`,
|
||||
иначе `needs_check` с `CODE_COLUMN_NOT_FOUND` `error`); `type_hint` нет → у OCR таблица ИС с содержимым —
|
||||
предупреждение файла `TABLE_LOST` (`error`), у Excel — лист «прочее», `SHEET_OTHER` (`info`). `DISCLOSURE` по `type_hint`
|
||||
— шаги 2–7 не выполняются; `DISCLOSURE` по кодам — строки и колонки отбрасываются (ядро строки пояснений не хранит),
|
||||
проверки не считаются, документ `done`.
|
||||
2. **Вариант формы** `form_variant` — только по структуре кодов (ОКУД 0710096 — код КНД комплекта, на бланках
|
||||
0710001 / 0710002, признаком не служит): ББ — есть 1600 и нет ни 1100, ни 1200 → `simplified`; ОФР — есть 2400 или
|
||||
2110 и нет ни 2100, ни 2200, ни 2300 → `simplified`; иначе `full`; у `DISCLOSURE` / `OTHER` — `null`. Шаг 7 и перечень
|
||||
кодов берут набор варианта. Образец упрощённой формы — в fixtures при первой возможности (OQ-166).
|
||||
3. **`locator`** — пары `ключ:значение` через `;`: `file:<имя json>;table:<i>` / `sheet:<A>[,<B>]`. Шапка документа —
|
||||
поля кандидата (`text` → `<поле>_text`, разобранное → поле, `confidence` → `<поле>_confidence`).
|
||||
|
||||
### §11.8 Шаг 7 — самопроверки §7
|
||||
|
||||
Правила счёта — §7 («Самопроверка в сервисах»): один словарь у обоих сервисов, значение кода — сумма строк `ok`,
|
||||
«посчитанное» соотношение, допуск и округление, `abs_terms` подтверждают величину, но не знак, полный или упрощённый
|
||||
набор по `form_variant`, родитель подстроки. `checks` есть у каждого `BALANCE` / `INCOME` со строками и отсутствует у
|
||||
документа без строк. Проверки по словарю имён и перечню кодов («код не в словаре», «код не из редакции») — в ядре,
|
||||
`info` к строке.
|
||||
|
||||
### §11.9 Шаг 8 — `needs_review`, статус, предупреждения
|
||||
|
||||
**Подтверждение проверкой** (Р-5, Р-7, Р-11, Р-17). Проверка §7 — арбитр, уверенность — подсказка там, где проверки нет.
|
||||
|
||||
- Значение **нуждается в покрытии**, если `confidence < 100` (Р-5: у ИС уверенность дискретная — 100, ~80, 71, 67, 60,
|
||||
50, 25, 0; в группе 70–89 есть настоящие ошибки, например «11 30» с уверенностью 80) или `is_recognized = false`
|
||||
(грязный текст, замена двойников, разлепленная строка). Чистые значения в покрытии не нуждаются.
|
||||
- Значение **покрыто**, если в его колонке есть жёсткое соотношение с участием его кода, которое посчитано и сошлось (в
|
||||
пределах допуска или округления), и `amount` значения не `null`.
|
||||
- **Знак** члена `abs_terms`, нуждающегося в покрытии: подтверждён, если форма записи знака (скобки, минус, без знака —
|
||||
по `text`) совпадает с формой у большинства членов `abs_terms` той же колонки с непустым `amount`, `is_recognized =
|
||||
true` и без правки аналитика; не совпадает или сравнивать не с чем — не подтверждён. «(7744536}» подтверждается, если
|
||||
2210 и 2220 той же колонки в скобках; «{38378)» при остальных в скобках — подсвечивается.
|
||||
- `needs_review = true`, если значение нуждается в покрытии и не покрыто или его знак не подтверждён; а также у всех
|
||||
ячеек жёсткого соотношения, не сошедшегося вне допуска и округления, независимо от уверенности. Иначе `false`: сырой
|
||||
текст и уверенность остаются в подсказке ячейки.
|
||||
|
||||
Не покрыто проверками и остаётся на уверенности: справочные строки ОФР 25xx / 29xx, детализация без проверки родителя,
|
||||
`DISCLOSURE`, колонки только с мягкими соотношениями. Риск двух взаимно компенсирующих ошибок в одном соотношении
|
||||
принимается. Закрывает AURORA-1524 без ущерба для 1519.
|
||||
|
||||
**Код строки** (Р-5, Р-12, Р-16). Код неуверенный — `line_code_confidence < 100` или заменён двойник; структурная
|
||||
проверка — **только для неуверенных кодов** (у Excel и чистых OCR-кодов расхождение с перечнем — `info` ядра
|
||||
`CODE_NOT_IN_EDITION`, не `needs_check`: пятизначные подстроки 1С и нестандартные коды клиента не блокируют
|
||||
автоподтверждение). В пакете — **упорядоченный перечень кодов** каждой формы по редакциям (66н, ФСБУ 4) и вариантам, без
|
||||
наименований, в порядке бланка (итоги после слагаемых: 1110 … 1190, 1100; 2110, 2120, 2100); источник — объединение кодов
|
||||
шаблонов банка (словарь ядра) и кодов печатных форм 1С по редакции (1105, 1530, 2420, 2460, 2930 и др.). Алгоритм:
|
||||
четырёхзначные коды строк `ok` документа — последовательность; для каждой проверяемой редакции — наибольшая
|
||||
подпоследовательность, согласованная с порядком бланка; редакция — с более длинной (при равенстве — по эффективной дате);
|
||||
уверенные коды — подтверждённые якоря; при нескольких наибольших подпоследовательностях подтверждены только общие коды.
|
||||
Пятизначный код подтверждён, если подтверждён его родитель (§7) и подстрока стоит после него. Повтор кода, у которого
|
||||
хотя бы одна копия неуверенна, — `needs_review` у неуверенной копии. Неподтверждённый неуверенный код — `needs_review`
|
||||
строки, документ `needs_check`. Подмену неуверенного кода на незапечатанного соседа того же раздела (1110 → 1120)
|
||||
структура не ловит — это закрывает ядро: для строк с неуверенным кодом условие `RECOGNIZED` включает совпадение
|
||||
распознанного наименования со справочным (§9.3). **Совпадение наименований:** обе строки — нижний регистр, `ё` → `е`,
|
||||
всё кроме букв и цифр → пробел, пробелы схлопываются; совпало, если строки равны, или более короткая (≥ 5 символов)
|
||||
входит в более длинную, или `difflib.SequenceMatcher` ≥ 0,8 — с любым справочным именем кода в любой редакции словаря
|
||||
ядра; кода нет в словаре или наименование пусто — не совпало; несовпадение — `NAME_MISMATCH` (`info`) у строки. По
|
||||
`fixtures/ocr` коды часто приходят с уверенностью 50 и 25 — требовать 100 нельзя.
|
||||
|
||||
**Поля шапки.** Дата отчёта с уверенностью ниже 100 подтверждена, если совпала с самой поздней абсолютной датой колонок;
|
||||
иначе `report_date` попадает в `needs_review_fields` и `REPORT_DATE_UNCERTAIN` (`error`) — неверно прочитанный год давал
|
||||
бы неверный период и автоподтверждение. ИНН и организация с уверенностью ниже 100 — `info` (их сверяет ядро: ИНН —
|
||||
`MISMATCH` с ЮЛ, организация — по алиасам), в `needs_review_fields` не попадают; единицы и прочие поля на статус не
|
||||
влияют.
|
||||
|
||||
**Предупреждение** — структура `{code, severity, message, line_refs?, column_index?, field?}`; уровень файла
|
||||
(`metrics.warnings`) — ещё `locator?`. Место — `locator` документа, не текст. Принцип: `error` — только то, что меняет
|
||||
данные или их полноту; `error` → `needs_check`, `info` на статус не влияет.
|
||||
|
||||
| Код | Уровень | Когда | Чем снимается в ядре |
|
||||
|---|---|---|---|
|
||||
| `JSON_FAILED` | error, файл | json набора ИС ошибочен при успешном соседе | перезагрузка |
|
||||
| `TABLE_PARSE_ERROR` | error | исключение при разборе таблицы ИС | перезагрузка |
|
||||
| `TABLE_LOST` | error, файл | таблица ИС с содержимым без столбца кодов и без типа | перезагрузка |
|
||||
| `CODE_COLUMN_NOT_FOUND` | error | форма `BALANCE` / `INCOME` без столбца кодов | перезагрузка |
|
||||
| `SEGMENT_WITHOUT_CODES` | error | сегмент с содержимым без столбца кодов (в т. ч. лист-продолжение) | перезагрузка |
|
||||
| `SHEET_NOT_JOINED` | error | лист с ≥ 3 кодами и другим числом колонок не присоединился | перезагрузка |
|
||||
| `CONTINUATION_TYPE_CONFLICT` | error | тип маркера продолжения противоречит форме | перезагрузка |
|
||||
| `SEGMENT_COLUMNS_MISMATCH` | error | колонки сегмента не совпали с первым | перезагрузка |
|
||||
| `COLUMN_WITHOUT_DATE` | error | числовая колонка без разобранной подписи | PATCH `columns[].period_date` |
|
||||
| `COLUMN_DATE_UNCERTAIN` | error | неуверенная подпись не попала в ожидаемые даты | PATCH `columns[].period_date` |
|
||||
| `HEADER_ROW_NOT_FOUND` | error | заголовка нет ни у одного сегмента | перезагрузка |
|
||||
| `TYPE_CONFLICT` | error | тип по кодам противоречит `type_hint` | перезагрузка |
|
||||
| `NO_ORGANIZATION_NO_INN` | error | нет ни организации, ни ИНН | PATCH `header.inn` / `organization_name` |
|
||||
| `PERIOD_UNKNOWN` | error | нет ни даты отчёта, ни абсолютных дат колонок | PATCH `header.report_date` |
|
||||
| `REPORT_DATE_UNCERTAIN` | error | неуверенная дата отчёта не совпала с датами колонок | PATCH `header.report_date` |
|
||||
| `XLSB_SCALE_SUSPECT` | error | xlsb без форматов, «тыс. руб.», ≥ 10 % дробных | перезагрузка |
|
||||
| `INN_NOT_FOUND`, `INN_UNCERTAIN` | info | ИНН не найден / неуверен при найденной организации | — |
|
||||
| `ORGANIZATION_NOT_FOUND`, `ORGANIZATION_UNCERTAIN` | info | организация не найдена / неуверенна при найденном ИНН | — |
|
||||
| `REPORT_DATE_UNPARSED` | info | дата отчёта не разобрана при наличии дат колонок | PATCH `header.report_date` |
|
||||
| `UNITS_NOT_FOUND` | info | единицы не найдены | — |
|
||||
| `FILE_TYPE_MISMATCH` | info, файл | формат файла не совпал с `file_type` | — |
|
||||
| `COLUMN_SKIPPED` | info | подпись из словаря исключений пропущена | — |
|
||||
| `FRACTIONAL_IN_THOUSANDS` | info | дробные при «тыс. руб.» (кроме xlsb без форматов) | — |
|
||||
| `XLSB_NO_FORMATS`, `NUMBER_FORMAT_UNPARSED` | info | числовые форматы недоступны или не разобраны | — |
|
||||
| `VALUE_OUT_OF_RANGE` | info | число Excel длиннее 16 целых цифр | — |
|
||||
| `SHEET_OTHER` | info | лист Excel «прочее» без типа и кодов | — |
|
||||
| `ROW_SPLIT` | info | разлеплена склеенная строка | — |
|
||||
| `OSV_*` | info | предупреждения листа ОСВ (§1) | — |
|
||||
| `CODE_NOT_IN_DICTIONARY`, `CODE_NOT_IN_EDITION`, `NAME_MISMATCH`, `STATEMENTS_VERSION_MISMATCH` | info, ядро | проверки ядра (§9.3, §9.5) | пересчёт |
|
||||
|
||||
Назначение кода и отметка `detail` снимают нарушения, пересчитываемые §7. Новый код предупреждения вводится словарём
|
||||
пакета и строкой этой таблицы; в контракте `code` — строка, не перечисление.
|
||||
|
||||
**Статус документа** — одна функция общего кода: `done` ⇔ раскладка найдена ∧ (организация или ИНН) ∧ период определён
|
||||
(дата отчёта или самая поздняя абсолютная дата колонок) ∧ все строки `ok` или `detail` ∧ нет значений, строк и полей
|
||||
шапки с `needs_review` ∧ нет жёстких нарушений §7 вне допуска и округления ∧ нет предупреждений документа уровня
|
||||
`error`; иначе `needs_check`. `DISCLOSURE` — `done` без условий о раскладке, строках и проверках. Ядро вызывает ту же
|
||||
функцию после правок, с подтверждениями (`is_modified` / `is_confirmed` ⇒ `needs_review = false`) и акцептом (жёсткие
|
||||
нарушения не учитываются). Квартал периода, сверка ИНН и наименований — знание ядра (§9.3), в `done` не входят. У ОСВ
|
||||
`document.status = done`, когда ведомость разобрана (§1.6), её предупреждения — `info`.
|
||||
|
||||
**Статус конверта:** `done` ⇔ все документы `done` ∧ нет предупреждений файла уровня `error`; иначе `needs_check`;
|
||||
документов нет — `error` (`RECOGNITION_FAILED` у scan-gateway, `INVALID_DOCUMENT` у tabular-parser — коды описывают
|
||||
разные причины). Порядок документов tabular-parser — сначала ОСВ в порядке листов, затем формы. Статус конверта в
|
||||
условие `RECOGNIZED` ядра не входит (Р-19).
|
||||
|
||||
### §11.10 Что отменено и заменено этим параграфом
|
||||
|
||||
| Прежнее правило | Теперь |
|
||||
|---|---|
|
||||
| Строки без кода не отдаются и не хранятся (OQ-36, OQ-141; §12 п. 5) | статусы `detail` / `missing` / `unreadable`, хранятся (Р-8); OQ-36 и OQ-141 закрыты |
|
||||
| Двойники — только в датах (§11.1 п. 1, решение № 22 среза 3a) | двойники в датах, подписях, кодах и суммах (Р-11); ИНН — без двойников |
|
||||
| Три и более знаков после разделителя → `amount = null` (решение № 30 среза 3a) | сохраняется для OCR-текста; для числа Excel — квантование до 2 знаков |
|
||||
| Разлепленная строка → документ `needs_check` (решение владельца 08.10.2026 по OQ-156) | значения нуждаются в покрытии §7, `ROW_SPLIT` — `info` (Р-17) |
|
||||
| Повтор кода: остаётся первая строка (решение № 15 среза 3a, §12 п. 5, OQ-152) | все строки `ok`, сумма по коду (Р-4) |
|
||||
| Наименование — из справочника шлюза, уверенность кода ≤ 99 вне справочника (§11.1 п. 6, п. 10) | наименование из источника, уверенность от источника (Р-3) |
|
||||
| Без шапки — синтез шапки и `needs_check` (рулинг 06.10.2026) | правило строки заголовка §11.3 п. 4 |
|
||||
| Подпись без периода не читается (§12 п. 4) | колонка без даты и `error` (Р-6) |
|
||||
| Закрывающая `}` / `]` не даёт минус (решение № 12, OQ-147) | `}` / `]` — закрывающая скобка, `is_recognized = false` |
|
||||
| Длинное тире → `is_recognized = false` (решение № 20, OQ-148) | прочерк любого вида — `null`, `is_recognized = true` |
|
||||
| `is_recognized` OCR = уверенность 100 ∧ ничего не выброшено; любое предупреждение → `needs_check` | `is_recognized` — только разбор; статус — §11.9 |
|
||||
| `checks` ядро игнорирует и пересчитывает §7 при загрузке | хранит как пришли, пересчитывает только после правок |
|
||||
|
||||
### §11.11 Отложено
|
||||
|
||||
Восстановление строк по наименованию (OQ-149); сопоставление формы без столбца кодов по именам строк (OQ-139); склейка
|
||||
страниц из разных файлов (OQ-140) и половин формы из одного файла (OQ-165, Р-15); приведение единиц сверх числового
|
||||
формата (OQ-138); формулы §7 по редакциям и знаки 2410/2420 (OQ-12, OQ-146); образец упрощённой формы (OQ-166);
|
||||
несколько периодов в одном файле ИС (OQ-167, допущение владельца); переключатель «как в файле» на экране (Р-14).
|
||||
|
||||
Что это даёт ядру: один маппинг `statementDocument → document + financial_statement_* + document_issue` для PDF и Excel;
|
||||
экран «Проверка распознавания» показывает статусы строк и предупреждения у места.
|
||||
|
||||
---
|
||||
|
||||
## §12. ББ и ОФР из Excel (tabular-parser, второй тип документа)
|
||||
## §12. ББ и ОФР из Excel — см. §11
|
||||
|
||||
Источник: ТЗ 6.2 («парсинг ОСВ, бухгалтерского баланса и ОФР (эксель файлов)»), DOC-037734, DOC-221281; формат —
|
||||
Приказ 66н / ФСБУ 4/2023, но на практике это и выгрузка из 1С («Регламентированный отчёт»), и шаблон банка
|
||||
(`Копия Рекомендуемые формы расшифровок…`, лист `Отчетность` в `Анализ *.xlsx`), и вручную набитая таблица.
|
||||
|
||||
Принцип тот же, что в §1.0: **структура ищется по содержимому**, результат — тот же `statementDocument` контракта,
|
||||
что и у `scan-gateway`, чтобы ядро маппило ББ/ОФР одинаково независимо от источника (`confidence = 100`,
|
||||
`is_recognized = true` для табличного пути).
|
||||
|
||||
**Статус: реализовано (Срез 2b, tabular-parser feat/14, контракт v1.2; решения владельца 06.10.2026).** Модули —
|
||||
`api/src/parser/statements/`, словари — `api/rules/statement_headers.yaml`, `statement_periods.yaml`,
|
||||
`statement_checks.yaml`; ожидания по реальным файлам — `api/tests/statements_manifest.yaml` (06-test-data §4б).
|
||||
|
||||
1. **Классификация листов**: лист ОСВ → форма → продолжение → прочее. Форма: заголовок («Бухгалтерский баланс»,
|
||||
«Отчет о финансовых результатах», «Отчет о прибылях и убытках») или ОКУД в **любой** строке до таблицы (у НПЗ он стоит
|
||||
под титулом); титул «Бухгалтерская отчетность» — не тип, тип берёт ближайший следующий заголовок. Тип по ОКУД:
|
||||
0710001 → `BALANCE`, 0710002 → `INCOME`, прочие 0710xxx (0710003 капитал, 0710004 ДДС) и пояснения («Пояснения к
|
||||
бухгалтерскому балансу», «Расшифровка») → `DISCLOSURE`; при противоречии ОКУД и заголовка — тип по ОКУД и
|
||||
предупреждение. Две формы на одном листе — по второму заголовку, в книге несколько листов — несколько `document`.
|
||||
Продолжение: лист без заголовка формы, но с маркером «Форма … с.2», либо с подписью «Код» и периодами правее, либо с
|
||||
колонкой кодов. ОСВ и формы в одной книге допустимы.
|
||||
2. **Колонка кодов**: в блоке ≥ 60 % непустых значений — коды (ББ 1100–1799, ОФР 2100–2999, расшифровки 5xxx,
|
||||
пятизначные — по первым четырём цифрам), кодов не меньше `min_codes` = 3; заголовок «Код/Коды» необязателен, числа вида
|
||||
`1110.0` читаются как текст «1110». Колонки нет — документ без строк, `needs_check`, предупреждение «колонка кодов не
|
||||
найдена»; сопоставление по наименованиям строк **не делается** (решение владельца, OQ-139).
|
||||
3. **Колонка наименований** — ближайшая левее кодов с преобладанием текста; «Пояснения» (пустые или ≤ 3 символов) не
|
||||
наименование.
|
||||
4. **Колонки-периоды**: ячейки заголовка правее кодов с распознанным периодом («На 31 декабря 2024 г.», «31.12.2024»,
|
||||
«За 9 месяцев 2025 г.», «За январь - сентябрь 2025 г.», «2024»), по порядку слева направо (`column_index` 0, 1, 2…);
|
||||
`period_date` — дата или конец периода; `is_current` в контракте нет — ядро берёт самую позднюю. Заголовок в две строки
|
||||
(«На» + «20__ г.», перенос внутри ячейки) склеивается; строка нумерации «1 2 3 4 5» пропускается. Колонки без периода
|
||||
(«Изменение», «%») **не читаются** (одно предупреждение на документ): у `statementDocument` поля `extra` нет.
|
||||
5. **Значения**: `Decimal`, скобки → минус, «–»/пусто → `null`, нечисловой текст → `string_value`,
|
||||
`is_recognized = false`. **Значение из Excel — как его показывает числовой формат ячейки** (OQ-138, пересмотрено
|
||||
владельцем 15.10.2026, AURORA-1528): выгрузки 1С хранят рубли с копейками, а в тысячи их переводит формат. Применяются
|
||||
масштабирующие запятые (`#,##0,` → тысячи, `0,` → тысячи), число знаков после точки (не больше 2 — `numeric(18,2)`),
|
||||
секции и условия (`[=0]"-"`, `[=-646532]"(647)"`); литерал условной секции разбирается как текстовая ячейка (скобки →
|
||||
минус, пробелы/NBSP — разделители, «-»/«–»/«—»/пусто → `null`); округление «половина от нуля» (как в Excel); знак — от
|
||||
сырого значения (для числовых секций; у секций-литералов значение берётся из литерала). Условия `[=x]` и значение
|
||||
сравниваются с точностью 15 значащих цифр, как в Excel. Секция выбирается по порядку: при явных условиях ни одна не
|
||||
подошла — формат не разобран, неявные условия у нефинальных секций — по умолчанию по числу секций. `General`/пустой
|
||||
формат — значение как в ячейке. Формат, который не удалось разобрать (`%`, экспонента, дробь, даты, `@`, неизвестные
|
||||
знаки вне кавычек вроде `₽`, больше четырёх секций, условие не по шаблону вроде `[=1E-5]`, литерал не число/пусто/
|
||||
прочерк, результат вне `numeric(18,2)`), — значение как в ячейке и одно предупреждение на документ с перечнем форматов. xlsb: стили не читаются
|
||||
— значения как в файле и одно предупреждение на документ «формат чисел xlsb не читается: значения как в файле». ОСВ
|
||||
правило не затрагивает. Единицы сверх формата не нормализуются: `header.units` — текст шапки; при «тыс.» и ≥ 10 %
|
||||
отображаемых значений с дробной частью — предупреждение «значений с дробной частью при единице тыс. руб.: N из M»
|
||||
(OQ-138). Строки без кода (разделы, «в том числе:», «Реализация иного имущества…» у НПЗ) не отдаются
|
||||
(`ASSUMPTION(OQ-36)`, OQ-141); повтор кода — остаётся первая строка, код в `checks.duplicate_line_codes`, `needs_check`.
|
||||
Пятизначные подстроки (11101, 12301…) отдаются как обычные строки.
|
||||
6. **Шапка**: организация (после метки «Организация», иначе текст с признаком ОПФ строкой выше метки — НПЗ), ИНН 10/12
|
||||
цифр у метки «ИНН» (**ИНН аудитора не берётся**: строки после метки «аудитор» исключены), ОКПО, ОКОПФ/ОКФС, ОКЕИ, адрес,
|
||||
единицы, `report_date_text`/`report_date` — из подзаголовка формы или тройки «число | месяц | год» у метки «Дата» (для
|
||||
ОФР — конец периода). Шапка другого листа не используется: у ОФР НПЗ нет метки «ИНН» → `inn = null`. Найденное поле —
|
||||
`*_confidence = 100`, не найденное — `null`.
|
||||
7. **Самопроверка**: соотношения §7 по каждой колонке — данные `statement_checks.yaml`. **Жёсткие** (нарушение →
|
||||
`needs_check`): ББ `1100`, `1200` (+1215), `1300`, `1400`, `1500`, `1600 = 1100 + 1200`, `1700 = 1300 + 1400 + 1500`,
|
||||
`1600 = 1700`; ОФР `2100`, `2200`, `2300`. **Мягкие** (только предупреждение, `ASSUMPTION(OQ-12)`): `2400 = 2300 −
|
||||
2410 + 2420` и варианты ФСБУ 4/2023, `2500`, «сумма пятизначных подстрок = родитель» (по колонке — только если хоть одна подстрока в ней непуста; `null` ≠ 0, ноль — значение; AURORA-1527). Допуск ±1 **в единицах формы**,
|
||||
отсутствующий код = 0, соотношение проверяется, только если присутствует его цель; правая часть считается дважды — как
|
||||
записано и с членами `abs_terms` по модулю (1С выгружает расходы со знаком минус, 66н — в скобках). Результат —
|
||||
`documents[].checks` (`relations_failed`, `soft_relations_failed`, `duplicate_line_codes`) и предупреждения.
|
||||
Расхождение жёсткого соотношения больше ±1, но не больше 0,001 % от наибольшего по модулю числа соотношения — **округление итогов** (OQ-138, решение владельца 06.10.2026: единицам шапки верим): уходит в `soft_relations_failed` с предупреждением «сошлось в пределах округления», статус не меняется (`rounding_tolerance_percent` в словаре). Пример: книга НПЗ (`npz/2025-Q3`) — `1600 = 1700` расходится на 1.94 / 9.02 / 2.48 при итогах ~17 млрд,
|
||||
ОФР Унагранде Компани (стр. 1) — итог 2100 округлён до тысяч, ОФР Унагранде Групп 3 кв. 2025 — 2200 = −1 365 000 как
|
||||
малая разность слагаемых ~393 млн; все `done`. Пятизначная подстрока относится к ближайшей четырёхзначной строке выше с
|
||||
теми же тремя первыми цифрами (12310 под 1230); то же правило ядро отдаёт экрану как `parent_code` (§7). ББ Унагранде (стр. 1) — только актив, `1600 = 1700` не сходится,
|
||||
`needs_check`.
|
||||
8. **Расшифровки** (0710003/0710004, пояснения, лист «Примечания» шаблона) — `DISCLOSURE` с `locator` и шапкой, если
|
||||
нашлась, без `columns`/`lines`; на статус не влияет (OQ-23). Книга только из таких листов — `done`.
|
||||
9. **Склейка**: листы-продолжения внутри книги присоединяются к последнему открытому документу формы, если число колонок
|
||||
сумм совпадает; строки добавляются в конец, `locator` перечисляет листы через запятую. **Страницы в разных файлах не
|
||||
склеиваются** (1С печатает форму постранично, один файл = одна страница): страница 2 без первой — документ с пустой шапкой
|
||||
и `needs_check`, предупреждение «страница-продолжение без шапки: загрузите форму одним файлом» (решение владельца, OQ-140).
|
||||
Продолжение без колонки кодов при непустом результате не теряется молча — `needs_check`.
|
||||
10. **Статус**: документ `ok` ⇔ жёсткие соотношения сошлись по всем колонкам ∧ нет дубликатов кодов ∧ найдена колонка кодов
|
||||
∧ есть колонка-период ∧ в шапке есть организация или ИНН ∧ документ не одинокое продолжение. Книга — `done`, если `ok`
|
||||
у всех документов, иначе `needs_check`; книга без листов ОСВ и форм — `INVALID_DOCUMENT`. Порядок документов: сначала
|
||||
ОСВ в порядке листов, затем формы. Шаблоны (коды есть, сумм нет, шапки нет) — `needs_check`.
|
||||
11. **Отложено**: запасной путь по именам строк (OQ-139), приведение единиц сверх числового формата ячейки (OQ-138), признак продолжения в контракте и
|
||||
склейка страниц из разных файлов в ядре (OQ-140, Срез 3), строки без кода (OQ-141), образцы ФСБУ (OQ-142), лист
|
||||
продолжения с единственной кодовой строкой (OQ-143 реализовано: после листа формы хватает одного кода,
|
||||
`min_codes_continuation` = 1; первая страница по-прежнему требует `min_codes` = 3).
|
||||
|
||||
Что это даёт ядру: один маппинг `statementDocument → document + financial_statement_*` для PDF и Excel (маппинг форм —
|
||||
Срез 3); экран «Проверка распознавания» для Excel-источника показывает не страницы, а таблицу — UI-решение вне парсера.
|
||||
Правила разбора форм из Excel (tabular-parser) с контракта v2.0 — часть общей цепочки нормализации ББ/ОФР: источник
|
||||
Excel — §11.2, раскладка — §11.3, строки — §11.4, значения и числовой формат ячейки — §11.2 п. 3 и §11.5, тип и вариант —
|
||||
§11.7, самопроверка — §7 и §11.8, статус — §11.9. Прежний текст §12 (Срез 2b, контракт v1.2) — в истории git, коммит
|
||||
`43b4b80`. Ссылки «§12 п. N» в старых документах: п. 1 → §11.2 п. 1; п. 2 → §11.3 п. 1; п. 3 → §11.3 п. 7; п. 4 → §11.3
|
||||
п. 3, п. 6; п. 5 → §11.2 п. 3, §11.4, §11.5; п. 6 → §11.2 п. 2; п. 7 → §7, §11.8; п. 8 → §11.7 п. 1; п. 9 → §11.2 п. 4;
|
||||
п. 10 → §11.9; п. 11 → §11.11.
|
||||
|
||||
@ -38,10 +38,10 @@
|
||||
| OQ-30 | Макет: шахматка и КФО показаны в тыс. руб. с 2 знаками, ОСВ — в рублях; где и как округлять (связано с OQ-26) | макет [30] [31-01] | срез 5–6 | допущение: хранить и считать в рублях, переключатель единиц — только отображение |
|
||||
| OQ-31 | КПП ЮЛ: в макете колонка и поле форм (`50`, `61`, `51-02`, `79`), в DBML v5.13 у `legal_entity` поля нет. Нужен ли КПП (и в импорте, и для ИП, у которых его нет)? | макет [50] [51-02] | срез 1, DBML v5.14; пересмотр — DBML v5.17 (docs#53, arm-core#45) | **закрыт решением владельца 07.10.2026: КПП нужен** (пересмотр; повод — макет и комментарий фронтенда «в ручке получения ЮЛ не приходит КПП»): DBML v5.17 — `legal_entity.kpp`, `legal_entity_history.kpp` (nullable). Формат — 9 символов: 4 цифры, 2 символа (цифра или латинская A–Z), 3 цифры, иначе `INVALID_KPP`; у ИП КПП запрещён (422 `INVALID_KPP`, при импорте строка пропускается с предупреждением); для ЮЛ необязателен (старые записи и импорт без колонки валидны); уникален только `inn` (КПП — атрибут, сопоставление контрагентов ОСВ по ИНН не меняется); изменение КПП — обычный PATCH со снимком в `legal_entity_history`. *История: 04.10.2026 — «КПП не нужен» (DBML v5.14 без поля; импорт его не читал; формы — без КПП); отменено 07.10.2026* |
|
||||
| OQ-32 | ЮЛ в макете состоит одновременно в ГСК и ГСЗ («Входит в ГСК» и «Входит в ГСЗ» заполнены, [79]); ТЗ 6.1 / ANL-030 — только в одной. Что верно? | макет [79] [51-02] | срез 1 (partial unique `related_group_member.legal_entity_id`) | **закрыт решением владельца 04.10.2026: ЮЛ только в одной ГК, одновременно — в одной ГСК и одной ГСЗ.** DBML v5.14: `related_group_member.kind` + partial unique `(legal_entity_id, kind) WHERE is_current` (отменяет ANL-030); выгрузка ЕЦП это подтверждает (флаги «входит в ГСК» и «входит в ГСЗ» одновременно) |
|
||||
| OQ-33 | «ID ГСК/ГСЗ в CRM» в формах (`51-03`, `51-04`, `77-02`; пример «3-9CGSK01» в формате ЕЦП CRM): у `related_group` в DBML только внутренний `code` (GSK-001). Есть ли у ГСК/ГСЗ идентификатор в CRM? | макет [77-02] | срез 1, DBML | **закрыт реализацией Среза 1:** `related_group.code` генерируется системой (`GSK-NNNNNN`/`GSZ-NNNNNN`), ID из CRM не вводится и не импортируется (в выгрузке ЕЦП его нет — OQ-91). **Решение владельца 14.10.2026:** `POST`/`PATCH` ГСК/ГСЗ принимают `crm_id` и пока отбрасывают его (arm-core #111), колонка `related_group.crm_id` — отдельной задачей arm-core #112 (DBML v5.19) |
|
||||
| OQ-33 | «ID ГСК/ГСЗ в CRM» в формах (`51-03`, `51-04`, `77-02`; пример «3-9CGSK01» в формате ЕЦП CRM): у `related_group` в DBML только внутренний `code` (GSK-001). Есть ли у ГСК/ГСЗ идентификатор в CRM? | макет [77-02] | срез 1, DBML | **закрыт реализацией Среза 1:** `related_group.code` генерируется системой (`GSK-NNNNNN`/`GSZ-NNNNNN`), ID из CRM не вводится и не импортируется (в выгрузке ЕЦП его нет — OQ-91). **Решение владельца 14.10.2026:** `POST`/`PATCH` ГСК/ГСЗ принимают `crm_id` и пока отбрасывают его (arm-core #111), колонка `related_group.crm_id` — отдельной задачей arm-core #112 (DBML v5.19). **18.10.2026, DBML v5.19:** колонка `related_group.crm_id` (`varchar`, nullable, уникальна, как у ГК и ЮЛ) введена в миграцию v5.19 (arm-core#112, Р-10 change `unified-statement-normalization`); `POST`/`PATCH` ГСК/ГСЗ сохраняют её, повтор — 409 `CRM_ID_EXISTS`; импорт ЕЦП её не заполняет (OQ-91). Вопрос закрыт до реализации arm-core#112 |
|
||||
| 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` пишется только для строк с кодом (строк без кода в результате нет), экран их не получает. |
|
||||
| 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-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 — не делать до решения |
|
||||
@ -146,22 +146,22 @@
|
||||
| OQ-138 | Единицы «тыс. руб.» в шапке против значений в рублях с копейками в выгрузках 1С (Унагранде Компани/Групп, НПЗ) и тысяч с дробями (Межениновская): кто и где приводит единицы. Примеры: книга НПЗ (`npz/2025-Q3`) — ББ `needs_check`, `1600 = 1700` расходится на 1.94 / 9.02 / 2.48 руб.; ОФР НПЗ и ОФР Унагранде (стр. 1) — `needs_check` по 2100/2200/2300 по той же причине; полные формы по факту сходятся только у Межениновской | `fixtures/statements-xlsx/README.md`, tabular-parser design 2b D-6, D-7, решения №22–24 (R-11, R-13, R-14) | Срез 3 (маппинг форм), §8 (КФО в тыс. руб.) | **закрыт решением владельца 06.10.2026:** единица «тыс. руб.» в шапке считается верной, значения отдаются как в файле; расхождения итогов в пределах округления (до 0,001 % наибольшего числа соотношения) статус не портят — мягкие нарушения «в пределах округления» (arm-tabular-parser#20). Книга НПЗ, ОФР стр. 1 Унагранде — `done`. Принимать ли расхождение и приводить ли единицы — решает КА в ядре (акцепт проверок, Срез 3). **Пересмотрено владельцем 15.10.2026 (AURORA-1528, arm-tabular-parser#42):** значение ББ/ОФР из Excel — как его показывает числовой формат ячейки (масштабирующие запятые `#,##0,` → тысячи, знаки после точки до 2, секции и условия `[=0]"-"`, литералы условных секций как текстовые ячейки, округление половина от нуля, знак от сырого значения); `General` — как в ячейке; неразобранный формат (%, экспонента, дробь, даты, @) — как в ячейке + предупреждение на документ; xlsb — как в файле + предупреждение; ОСВ не затронута. Решение 06.10.2026 «значения как в файле» заменено |
|
||||
| OQ-139 | Форма без колонки кодов: сопоставление по именам строк из шаблонов 66н/ФСБУ (словарь `statement_lines.yaml`, `line_code_confidence < 100`)? Сейчас документ без строк и `needs_check` («колонка кодов не найдена») | решение владельца 06.10.2026 (вопрос 6), tabular-parser design 2b D-4 | §12 п. 2, Срез 3 | открыт: нужен реальный файл без колонки кодов; допущение — не сопоставлять по именам; **связано (Срез 3a):** то же для PDF-пути — справочник кодов по редакциям, имена из OCR при пустой редакции (OQ-146, OQ-150) |
|
||||
| OQ-140 | Страница-продолжение в отдельном файле (1С печатает ББ/ОФР постранично, один файл = страница): склейка в ядре по ЮЛ + период + тип формы; признак `is_continuation` / `form_page` в контракте (MINOR, две необязательных поля) | решение владельца 06.10.2026 (вопрос 2), design 2b D-8 | §12 п. 9, Срез 3, контракт v1.x | допущение принято: страницы разных файлов не склеиваются; страница 2 без шапки — документ с пустой шапкой и `needs_check`, «загрузите форму одним файлом»; **Срез 3b: допущение реализовано** — страницы одной формы из разных файлов не склеиваются: каждый файл — отдельный документ со своим периодом. |
|
||||
| OQ-141 | Строка без кода, но с суммами в ОФР ФСБУ 4/2023 (НПЗ «Реализация иного имущества…»): хранить ли (OQ-36 расширяется с расшифровочных строк на строки с суммами) | `fixtures/statements-xlsx/npz`, design 2b D-6 | Срез 3, DBML `financial_statement_line` (хранит только строки с кодом) | допущение принято: строки без кода не отдаются, при наличии сумм — предупреждение «строк без кода с суммами пропущено: N» |
|
||||
| OQ-141 | Строка без кода, но с суммами в ОФР ФСБУ 4/2023 (НПЗ «Реализация иного имущества…»): хранить ли (OQ-36 расширяется с расшифровочных строк на строки с суммами) | `fixtures/statements-xlsx/npz`, design 2b D-6 | Срез 3, DBML `financial_statement_line` (хранит только строки с кодом) | допущение принято: строки без кода не отдаются, при наличии сумм — предупреждение «строк без кода с суммами пропущено: N». **Закрыт решением владельца 17.10.2026 (Р-8):** строка без кода с суммами отдаётся и хранится — `detail` под строкой с кодом (НПЗ «Реализация иного имущества…»), `missing` без строки с кодом выше; подтвердить документ, пока у `unreadable` / `missing` есть суммы, нельзя (409 `LINES_UNRESOLVED`, Р-13). Правила — `05-domain-rules.md` §11.4, §9.5 п. 4 |
|
||||
| OQ-142 | Образец ФСБУ 4/2023 (`templates/ББ после 01.01.2025.xlsx`): «АКТИВ» и код 1110 в одной строке из-за объединённых ячеек; шаблоны (коды есть, сумм и шапки нет) не данные. Нужен ли их разбор вообще | `fixtures/statements-xlsx/templates`, design 2b D-8 | §12 п. 10, тесты | допущение принято: шаблоны дают `needs_check` (шапка пустая, значения `null`); разбор шаблонов как источника данных не планируется, пока владелец не скажет иначе |
|
||||
| OQ-143 | Лист продолжения с **единственной** кодовой строкой (книга НПЗ: лист ОФР с одной строкой 2400): `min_codes` = 3 → лист не распознаётся как форма и пропускается, строка 2400 теряется при `needs_check` документа. Понизить порог / принимать лист по близости к открытому документу ОФР / маркеру продолжения | tabular-parser design 2b: решение №25, O-9 | §12 п. 2, п. 9; полнота ОФР НПЗ | **реализовано в 2b (финальная волна, I-1):** лист с 1–2 кодами форм после листа формы берётся как продолжение (`min_codes_continuation = 1` в словаре) и присоединяется при совпадении числа колонок сумм и типа формы; не присоединён — `needs_check` и предупреждение; первая страница с 1–2 кодами формой не считается. Строка 2400 НПЗ в результате есть |
|
||||
| OQ-144 | Права на удаление на SMB-шаре контура (`Input` — входящие файлы и `.done`, `Output/<job_id>/`, `Tmp`): можно ли сервису `scan-gateway` удалять файлы, и не удаляет ли их сама ИС Сканирование после обработки. Пока `SCAN_CLEANUP=false` — сервис ничего не удаляет (кроме `.part`, ставшего целью публикации записью без замены), в `Input`, `Output` и `Tmp` накапливаются остатки | решение владельца 06.10.2026, change `slice-3a-ocr-normalizer` D-2, решения №26/№27 | scan-gateway (горячие папки), эксплуатация стенда | открыт: допущение — очистка выключена; dev-compose включает `SCAN_CLEANUP=true`. Спросить платформу/владельцев ИС; при положительном ответе — включить флаг на стенде |
|
||||
| OQ-145 | Редакция формы (`66n` / `fsbu4`) в контракте: нужна ли ядру редакция документа в конверте (необязательное поле `statementDocument`, кандидат MINOR 1.3), или ядро выбирает редакцию по `report_date` само, по тому же правилу §11.1 п. 10. Сейчас scan-gateway выбирает редакцию внутри и наружу её не отдаёт | change `slice-3a-ocr-normalizer`, открытый вопрос 2 | контракт `docs/contracts/` (v1.3), Срез 3b (маппинг форм, справочник кодов в ядре) | **закрыт решением владельца 07.10.2026:** редакция формы в контракт не добавляется (контракт v1.2); ядро определяет её по дате периода документа (`66n` при дате ≤ 2024-12-31, иначе `fsbu4`) и отдаёт в `GET /documents/{id}/statement` (`edition`), как реализовано в Срезе 3b. Досрочное применение ФСБУ 4/2023 до 2025 года — новый OQ при первом реальном случае |
|
||||
| OQ-146 | Словари §7 (проверки форм) и §12 (справочник кодов `statement_lines.yaml`, формулы ОФР) **по редакциям** 66н / ФСБУ 4/2023 для tabular-parser (2b) и ядра (3b): один набор правил на обе редакции или раздельные; как выбирать редакцию в ядре для Excel-пути | change `slice-3a-ocr-normalizer` (справочник с редакциями только в scan-gateway), OQ-12, OQ-139 | tabular-parser `rules/*.yaml`, ядро (Срез 3b), §7, §12 п. 2 | открыт, **решение владельца 07.10.2026: решается вместе с OQ-150** (состав редакции `fsbu4`); до тех пор допущение прежнее — scan-gateway ведёт свой `api/rules/statement_lines.yaml`, tabular-parser и ядро словарь по редакциям не используют, формулы §7 одни на обе редакции; расширяет OQ-12 и OQ-139 |
|
||||
| OQ-147 | Закрывающая скобка, распознанная OCR как `}` или `]` (`(7744536}` в `aep_3.json`): дословный перенос `parse_amount` минус не ставит — сумма положительная, `is_recognized = false`. Расширить ли множество закрывающих скобок в §11.1 п. 7 после проверки на стенде | change `slice-3a-ocr-normalizer`, решение №12; владелец 06.10.2026: «пока устроит как есть» | scan-gateway `normalizer/ocr/amounts.py`, §11.1 п. 7 | открыт: допущение — перенос дословный; знак теряется, строка подсвечена `is_recognized = false`, исходный текст — в `raw/` |
|
||||
| OQ-148 | Длинное/среднее тире «—»/«–» как прочерк: дословный `parse_amount` вырезает тире как «лишний» символ → сумма `null`, но `is_recognized = false` даже при `confidence = 100` (дефис «-» даёт `true`). Лишний `needs_check` на формах с длинным тире. Считать ли прочерк любого вида распознанным | change `slice-3a-ocr-normalizer`, решение №20 | scan-gateway `amounts.py`, §11.1 п. 7, доля `done` на реальных сканах | открыт: допущение — перенос дословный; правило уточняется после прогона на стенде |
|
||||
| OQ-149 | Строки таблицы OCR, отброшенные классификацией (поведение источника, §11.1 п. 4), не восстанавливаются: с непустым нечисловым кодом (`1150 :`) и с пустым кодом, но с суммами (в `aep_3.json` — «БАЛАНС» и «Выручка»: код пуст, мусор `211(1 \|` стоит в колонке значения). Нормализатор выдаёт предупреждения `строк с нечисловым кодом пропущено: N` и `строк без кода с суммами пропущено: N` (как OQ-141 для Excel), документ — `needs_check`, содержимое строк остаётся в `raw/`. Восстанавливать ли такие строки по наименованию (словарь редакции) или по структуре формы | `fixtures/ocr/aep_3.json`, `aep_6.json`, change `slice-3a-ocr-normalizer`, решение №29, §11.1 п. 4 | scan-gateway `rows.py`/`document.py`, полнота форм, OQ-36, OQ-139, OQ-141 | открыт: допущение — строки пропускаются с предупреждением; аналитик видит расхождение итогов в проверках 3b |
|
||||
| OQ-150 | Состав редакции `fsbu4` (ФСБУ 4/2023, отчётность с 2025 года) в `api/rules/statement_lines.yaml` scan-gateway: коды и наименования строк ББ и ОФР; источник — Приложения 3–4 к ФСБУ 4/2023 (черновой перечень кодов — шаблоны `fixtures/statements-xlsx/templates/`, OQ-142). Пока `lines: {}` | change `slice-3a-ocr-normalizer`, D-7 | scan-gateway `api/rules/`, эталоны `fixtures/ocr/expected_normalize/` для документов 2025 года, статус `done` | открыт: **данные даёт владелец**, код нормализатора не меняется; до заполнения документы 2025 года идут с именами из OCR, предупреждением «редакция fsbu4 не заполнена» и `needs_check`. Порядок заполнения — `scan-gateway/README.md` **09.10.2026, решение владельца:** состав `fsbu4` (и `66n`) для ядра принят из шаблонов банка — `arm-core/api/rules/statement_lines.yaml` (arm-core#80); словарь scan-gateway — follow-up, копии расходятся (66н scan-gateway — словарь старого ядра). |
|
||||
| OQ-145 | Редакция формы (`66n` / `fsbu4`) в контракте: нужна ли ядру редакция документа в конверте (необязательное поле `statementDocument`, кандидат MINOR 1.3), или ядро выбирает редакцию по `report_date` само, по тому же правилу §11.1 п. 10. Сейчас scan-gateway выбирает редакцию внутри и наружу её не отдаёт | change `slice-3a-ocr-normalizer`, открытый вопрос 2 | контракт `docs/contracts/` (v1.3), Срез 3b (маппинг форм, справочник кодов в ядре) | **закрыт решением владельца 07.10.2026:** редакция формы в контракт не добавляется (контракт v1.2); ядро определяет её по дате периода документа (`66n` при дате ≤ 2024-12-31, иначе `fsbu4`) и отдаёт в `GET /documents/{id}/statement` (`edition`), как реализовано в Срезе 3b. Досрочное применение ФСБУ 4/2023 до 2025 года — новый OQ при первом реальном случае. **18.10.2026 (change `unified-statement-normalization`):** решение сохраняется — редакция в контракт v2.0 не добавлена; сервисы выбирают редакцию тем же правилом только для упорядоченного перечня кодов (структурная проверка неуверенных кодов, `05-domain-rules.md` §11.6, §11.9) |
|
||||
| OQ-146 | Словари §7 (проверки форм) и §12 (справочник кодов `statement_lines.yaml`, формулы ОФР) **по редакциям** 66н / ФСБУ 4/2023 для tabular-parser (2b) и ядра (3b): один набор правил на обе редакции или раздельные; как выбирать редакцию в ядре для Excel-пути | change `slice-3a-ocr-normalizer` (справочник с редакциями только в scan-gateway), OQ-12, OQ-139 | tabular-parser `rules/*.yaml`, ядро (Срез 3b), §7, §12 п. 2 | открыт, **решение владельца 07.10.2026: решается вместе с OQ-150** (состав редакции `fsbu4`); до тех пор допущение прежнее — scan-gateway ведёт свой `api/rules/statement_lines.yaml`, tabular-parser и ядро словарь по редакциям не используют, формулы §7 одни на обе редакции; расширяет OQ-12 и OQ-139. **18.10.2026:** словарь §7 — единственный, в общем коде `arm_statements` (копия ядра удаляется), полный и упрощённый наборы по `form_variant`; раздельные формулы по редакциям по-прежнему не вводятся |
|
||||
| OQ-147 | Закрывающая скобка, распознанная OCR как `}` или `]` (`(7744536}` в `aep_3.json`): дословный перенос `parse_amount` минус не ставит — сумма положительная, `is_recognized = false`. Расширить ли множество закрывающих скобок в §11.1 п. 7 после проверки на стенде | change `slice-3a-ocr-normalizer`, решение №12; владелец 06.10.2026: «пока устроит как есть» | scan-gateway `normalizer/ocr/amounts.py`, §11.1 п. 7 | открыт: допущение — перенос дословный; знак теряется, строка подсвечена `is_recognized = false`, исходный текст — в `raw/`. **Закрыт дизайном, согласованным владельцем 17.10.2026 (change `unified-statement-normalization`, шаг 4, Р-7):** `}` и `]` принимаются как закрывающая скобка — «(7744536}» → −7 744 536, `is_recognized = false` (исправление); знак члена `abs_terms` подтверждается конвенцией колонки. `05-domain-rules.md` §11.5 п. 2. Действует с переходом scan-gateway на v2.0 |
|
||||
| OQ-148 | Длинное/среднее тире «—»/«–» как прочерк: дословный `parse_amount` вырезает тире как «лишний» символ → сумма `null`, но `is_recognized = false` даже при `confidence = 100` (дефис «-» даёт `true`). Лишний `needs_check` на формах с длинным тире. Считать ли прочерк любого вида распознанным | change `slice-3a-ocr-normalizer`, решение №20 | scan-gateway `amounts.py`, §11.1 п. 7, доля `done` на реальных сканах | открыт: допущение — перенос дословный; правило уточняется после прогона на стенде. **Допущение спеки `unified-statement-normalization` (18.10.2026, решение S-7, ждёт подтверждения владельца):** прочерк любого вида (`-`, `–`, `—`, `−` без цифр) — пустое значение, `amount = null`, `is_recognized = true` (как пустая ячейка, Р-2); `05-domain-rules.md` §11.5 п. 2 |
|
||||
| OQ-149 | Строки таблицы OCR, отброшенные классификацией (поведение источника, §11.1 п. 4), не восстанавливаются: с непустым нечисловым кодом (`1150 :`) и с пустым кодом, но с суммами (в `aep_3.json` — «БАЛАНС» и «Выручка»: код пуст, мусор `211(1 \|` стоит в колонке значения). Нормализатор выдаёт предупреждения `строк с нечисловым кодом пропущено: N` и `строк без кода с суммами пропущено: N` (как OQ-141 для Excel), документ — `needs_check`, содержимое строк остаётся в `raw/`. Восстанавливать ли такие строки по наименованию (словарь редакции) или по структуре формы | `fixtures/ocr/aep_3.json`, `aep_6.json`, change `slice-3a-ocr-normalizer`, решение №29, §11.1 п. 4 | scan-gateway `rows.py`/`document.py`, полнота форм, OQ-36, OQ-139, OQ-141 | открыт: допущение — строки пропускаются с предупреждением; аналитик видит расхождение итогов в проверках 3b. **18.10.2026:** остаётся открытым (Р-8); с v2.0 такие строки не пропадают — приходят со статусами `unreadable` / `missing` / `detail` и сырым текстом, аналитик назначает код или отмечает расшифровкой (`PATCH …/statement lines`); восстановление по наименованию не делается |
|
||||
| OQ-150 | Состав редакции `fsbu4` (ФСБУ 4/2023, отчётность с 2025 года) в `api/rules/statement_lines.yaml` scan-gateway: коды и наименования строк ББ и ОФР; источник — Приложения 3–4 к ФСБУ 4/2023 (черновой перечень кодов — шаблоны `fixtures/statements-xlsx/templates/`, OQ-142). Пока `lines: {}` | change `slice-3a-ocr-normalizer`, D-7 | scan-gateway `api/rules/`, эталоны `fixtures/ocr/expected_normalize/` для документов 2025 года, статус `done` | открыт: **данные даёт владелец**, код нормализатора не меняется; до заполнения документы 2025 года идут с именами из OCR, предупреждением «редакция fsbu4 не заполнена» и `needs_check`. Порядок заполнения — `scan-gateway/README.md` **09.10.2026, решение владельца:** состав `fsbu4` (и `66n`) для ядра принят из шаблонов банка — `arm-core/api/rules/statement_lines.yaml` (arm-core#80); словарь scan-gateway — follow-up, копии расходятся (66н scan-gateway — словарь старого ядра). **18.10.2026:** словарь имён шлюза `api/rules/statement_lines.yaml` удаляется (Р-3: наименования — только из источника, справочное имя — в ядре); для сервисов нужен состав кодов `fsbu4` в упорядоченном перечне кодов `arm_statements` — источник тот же (шаблоны банка + печатные формы 1С) |
|
||||
| OQ-151 | Эмулятор ИС Сканирование: файл, совпавший по sha256 с PDF из `SCAN_FIXTURES_DIR`, у которого нет JSON-набора (`<имя>.json`), отвечает «нет пары» (`INVALID_DOCUMENT`) и **не** уходит на `SCAN_EMULATOR_DEFAULT`. Пробел спеки эмулятора: считать ли такой файл промахом и отдавать набор по умолчанию | change `slice-3a-ocr-normalizer`, спека `scan-emulator` | scan-gateway `scan_emulator`, dev-стенд (`dev/qa-guide.md` §6) | открыт: допущение — поведение как есть; PDF без JSON-пары в `fixtures/ocr` не класть |
|
||||
| OQ-152 | Повтор кода строки в таблице OCR: остаётся **первая** строка (паритет с Excel-путём, §12 п. 5; решение №15), но в `aep_6.json` (ОФР) код 2410 повторён, оставленная первая строка пуста, а отброшенная содержит `(649)`. Выбрать правило «первая строка с суммой» вместо «первая» (для `aep_3` оно даёт иной результат, чем для `aep_6` — нужна проверка на стенде) | change `slice-3a-ocr-normalizer`, решение №34 (R-16), финальное ревью I-2 | scan-gateway `document.py`, §11.1 п. 4, Excel-путь §12 п. 5 | открыт: допущение — первая строка; предупреждение «код N повторяется» сигнализирует аналитику, сумма отброшенной строки остаётся в `raw/` |
|
||||
| OQ-152 | Повтор кода строки в таблице OCR: остаётся **первая** строка (паритет с Excel-путём, §12 п. 5; решение №15), но в `aep_6.json` (ОФР) код 2410 повторён, оставленная первая строка пуста, а отброшенная содержит `(649)`. Выбрать правило «первая строка с суммой» вместо «первая» (для `aep_3` оно даёт иной результат, чем для `aep_6` — нужна проверка на стенде) | change `slice-3a-ocr-normalizer`, решение №34 (R-16), финальное ревью I-2 | scan-gateway `document.py`, §11.1 п. 4, Excel-путь §12 п. 5 | открыт: допущение — первая строка; предупреждение «код N повторяется» сигнализирует аналитику, сумма отброшенной строки остаётся в `raw/`. **Закрыт решением владельца 17.10.2026 (Р-4):** повтор кода — законный случай, все строки сохраняются со статусом `ok`, для §7 и всего после — сумма по коду (в `aep_6` 2410 = пустая строка + `(649)`); неверно прочитанный совпавший код ловит структурная проверка (Р-16). `05-domain-rules.md` §11.4 |
|
||||
| OQ-153 | Журналировать ли скачивание первички (`GET /files/{id}/content`) и CSV чек-листа (ТЗ 8.2: перечень событий журнала включает «выгрузку»)? Экспорт сводной ОСВ в xlsx пишет `audit_event(EXPORT)` (Срез 4), а скачивание исходного файла и CSV чек-листа в Срезе 3b — нет; комментарии к документу тоже не аудируются (`comment` хранит автора и время) | change `slice-3b-statements-api`, design D-10, D-11, D-13, решение №8; ТЗ 8.2 | `audit_event`, `GET /files/{id}/content`, `GET …/checklist?format=csv`, §9.5 п. 6 | **закрыт решением владельца 07.10.2026 (вариант А):** скачивание исходного файла (`GET /files/{id}/content`, это же и просмотр PDF) и CSV чек-листа в `audit_event` не пишутся; `EXPORT` пишет только экспорт сводной ОСВ в xlsx (Срез 4) |
|
||||
| OQ-154 | Автозамена файла работает на уровне файла (ключ документа: тип, счёт/NULL, год, квартал, нарастающий): исправленный отдельный ОФР заменит весь прежний файл-книгу «ББ + ОФР» того же периода (`SUPERSEDED`), а подтверждённый ББ из неё отвяжется от версий «в работе». Допустима ли такая замена на уровне файла, или формы одного периода нужно заменять по документам | финальное ревью Среза 3b (заметка контроллера), change `slice-3b-statements-api` | Срез 3b / 5 | **закрыт решением владельца 07.10.2026 (вариант А):** замена остаётся на уровне файла (DBML `file_status.SUPERSEDED`, ТЗ 8.2); если у заменённого файла есть документы, ключа которых нет в новом, — предупреждение в `metrics.mapping_warnings` нового файла (какие документы выпали, был ли `CONFIRMED`, из каких версий сняты привязки). Правило — `05-domain-rules.md` §9.5 п. 3; реализация — RSHB/arm-core#58 |
|
||||
| OQ-155 | Эталонная ручная консолидация (`fixtures/golden/Консолидация_Группа РБ.xlsx`, лист `01.10.2024`, «в т. ч. взаимные обязательства») элиминирует у каждой компании **полную сумму её строк на компании группы** (Счастливочная 1230 = 108 062 тыс. = 62.01 «БП Производство» 106 311 116,46 + 62.01 «БП Аренда» 1 401 330 + 76.06 350 000; РБ 1520 = 106 311 тыс.), а ТЗ (DOC-223044, §4–§5) — по **парам** с `min` и порогом 5 %. Какой алгоритм верен | разбор OQ перед Срезом 5 (08.10.2026), сверка эталона с golden ОСВ РБ/Счастливочной Q3 2024 | Срез 5 (матчинг §5, тест сверки с эталоном), Срез 6 (КФО) | **закрыт решением владельца 08.10.2026: алгоритм ТЗ** — пары, `min`, порог 5 % (§4–§5); эталон сверяется только по точным парам (106 311 116,46 по правилу № 6), асимметрия эталона (1 751 тыс. у Счастливочной) — ожидаемое расхождение, описывается в тесте. Связано: OQ-07 (строго 1:1 или гибрид — ждёт владельца), OQ-08 |
|
||||
| OQ-156 | Ответ ИС Сканирование склеивает соседние строки формы в одну: код `1500\n1700`, наименование `Итого по разделу V\n\nБАЛАНС`, суммы `917 775\n2 371 333` (ББ РБ и Счастливочной за 1 кв. 2025, `fixtures/ocr/Бухгалтерская отчетность 1 кв.25.json`, `БО 1 кв 2025.json`). Нормализатор отбрасывал строку как нечисловой код — в документе не было 1500 и 1700 | прогон 1 кв. 2025 на реальных ответах ИС (arm-ka-fixtures#16) | scan-gateway `rows.py`, §11.1 п. 4 | **решено** (владелец 08.10.2026): разлеплять, если в каждой непустой ячейке значений столько же частей, сколько кодов; иначе — как раньше; предупреждение «строк с несколькими кодами разлеплено: N», `needs_check`; реализация — RSHB/arm-scan-gateway#16 |
|
||||
| OQ-156 | Ответ ИС Сканирование склеивает соседние строки формы в одну: код `1500\n1700`, наименование `Итого по разделу V\n\nБАЛАНС`, суммы `917 775\n2 371 333` (ББ РБ и Счастливочной за 1 кв. 2025, `fixtures/ocr/Бухгалтерская отчетность 1 кв.25.json`, `БО 1 кв 2025.json`). Нормализатор отбрасывал строку как нечисловой код — в документе не было 1500 и 1700 | прогон 1 кв. 2025 на реальных ответах ИС (arm-ka-fixtures#16) | scan-gateway `rows.py`, §11.1 п. 4 | **решено** (владелец 08.10.2026): разлеплять, если в каждой непустой ячейке значений столько же частей, сколько кодов; иначе — как раньше; предупреждение «строк с несколькими кодами разлеплено: N», `needs_check`; реализация — RSHB/arm-scan-gateway#16. **Пересмотрено решением владельца 17.10.2026 (Р-17):** разлепленные строки не держат документ в `needs_check` безусловно — их значения `is_recognized = false` и нуждаются в покрытии §7, при сошедшихся соотношениях не подсвечиваются; разлепление — предупреждение `ROW_SPLIT` уровня `info`; нечитаемая часть кода становится строкой `unreadable`. `05-domain-rules.md` §11.4 |
|
||||
| OQ-157 | Чистый документ (без замечаний) требовал ручного «Подтвердить» (`RECOGNIZED` → `CONFIRMED`), хотя строки ОСВ не правятся (OQ-114) и ТЗ 03_9 п. 4 / 10_10 даёт «Загружен» сразу при отсутствии замечаний; на комплекте 1 кв. 2025 — 16 пустых кликов | прогон владельца 08.10.2026 | arm-core `services/auto_confirm.py`, `result_mapping`, `primary_files.delete`; §9.3, §9.5 п. 4–5 | **решено** (владелец 08.10.2026): чистый документ при маппинге сразу `CONFIRMED` системой (`confirmed_by` NULL), переход версии — от загрузившего; удаление автоподтверждённого файла — пока версии «в работе»; RSHB/arm-core#64, change `auto-confirm-clean-documents` |
|
||||
| OQ-158 | В «Осталось N» (строки свода без ЮЛ и без ИНН) попадают строки аналитики, которые не являются контрагентами-организациями: «Ведомость в счет з/платы», подотчётные лица, физлица и т. п. — на РБ 2025 К1 таких большинство из 1291 строки без ИНН, и довести баннер до нуля сопоставлением с ЮЛ нельзя. Нужно ли отличать такие строки (признак «не организация» вручную / по словарю / по счёту) и исключать их из «Осталось N» и предупреждения [31-00] | жалоба владельца на «Указать» в сводной ОСВ (08.10.2026), RSHB/arm-core#74 | arm-core `domain/consolidated_osv.needs_mapping`, §3, экраны [30], [32-osv-no-inn], [31-00] | открыт: до решения такие строки считаются несопоставленными (EXTERNAL), переход к следующему шагу ими не блокируется — блокировки в API нет |
|
||||
| OQ-159 | Note DBML `recognition_job.reply_deadline` «таймаут ожидания (OCR — 5 мин)» расходится с решением владельца 09.10.2026: срок ответа OCR — 20 мин (`DEFAULT_REPLY_TIMEOUT` в arm-core, `SCAN_TIMEOUT_SECONDS=1200` в scan-gateway). Поправить note при следующей версии DBML | решение владельца 09.10.2026 (не выпускать версию DBML ради note); arm-core #87, arm-scan-gateway #26, arm-ka-docs #84 | DBML `recognition_job`, `05-domain-rules.md` §9.4 | **закрыт 09.10.2026:** note исправлен в DBML v5.18 (docs #90) |
|
||||
@ -170,6 +170,9 @@
|
||||
| OQ-162 | ОСВ 1С, шапка варианта 2 «Детализация по субсчетам, субконто: Контрагенты, Контрагенты.ИНН» (ООО «Зерновая компания „Новопетровская“», ОСВ 60 за 9 мес. 2025, замечание банка 13.10.2026): ИНН выводится **отдельной строкой под** контрагентом (уровень глубже, те же суммы, ИНН числом). Парсер принимал «Контрагенты.ИНН» за второй уровень контрагентов: в «Контрагент / аналитика» — `2370012036.0`, наименования нет, ИНН у строк `null` → нет признака ГК/ГСК/Внешний, суммы контрагента в своде дважды | замечание банка 13.10.2026; RSHB/arm-tabular-parser#32 | tabular-parser §1.1, §1.3; свод и резолв контрагента в ядре | **решено владельцем 13.10.2026:** `<группировка>.ИНН` — реквизит, не уровень; строка ИНН склеивается с родительским контрагентом (ИНН — родителю, строка из `rows` убирается, дети — к контрагенту), только если она единственная у родителя и суммы равны; иначе строка `OTHER` и `needs_check`; пустая ячейка ИНН (физлицо) — склейка без ИНН (рулинг R-1); целое число из ячейки xls — без «.0». `05-domain-rules.md` §1.1, §1.3; **реализовано** RSHB/arm-tabular-parser#32 (713 из 716 контрагентов файла получили ИНН, 3 физлица без ИНН). Правило строки ИНН заменено OQ-163 (13.10.2026). |
|
||||
| OQ-163 | ОСВ, где ИНН контрагента выведен **отдельной строкой** рядом с наименованием, в любой раскладке: над / под × в дереве / не в дереве × с суммами / без (требование владельца 13.10.2026). До решения парсер склеивал только «над, не в дереве, без сумм» (OQ-130) и «под, в дереве» при подсказке «Контрагенты.ИНН» (OQ-162); «под, не в дереве, без сумм» отдавал ИНН следующему контрагенту | владелец, 13.10.2026; RSHB/arm-tabular-parser#35, #34 | tabular-parser §1.1, §1.3 | **решено владельцем 13.10.2026:** без подсказки в шапке строка ИНН — только с верной контрольной суммой ИНН (во всех раскладках, включая OQ-130); с подсказкой — без проверки, порядок элементов задаёт направление; «над, в дереве» — только единственный ребёнок; соседи — по суммам, без сумм — по направлению листа (шапка → пары по суммам → однозначные блоки); неоднозначное — строка `OTHER`, `needs_check`; ведущий ноль числовой строки ИНН (#34). Уточнения владельца (H-1): строка из 10/12 цифр с неверной контрольной суммой не склеивается, остаётся обычной строкой, предупреждение «похожа на ИНН …», `needs_check` (синтетика `s28`); (H-2): если пара найдена, но раскладка противоречит направлению подсказки `<группировка>.ИНН`, строка склеивается, но выдаётся отдельное предупреждение «стоит под/над наименованием … а шапка указывает над/под — проверьте» и `needs_check`. `05-domain-rules.md` §1.3; **реализовано** RSHB/arm-tabular-parser#35 |
|
||||
| OQ-164 | Субсчета 76 с налоговой природой в ВГО: исключать ли **76.АВ** «НДС по авансам и предоплатам» из ДЗ и зеркально **76.ВА** «НДС по авансам выданным» из КЗ (правила № 5–8 и результирующая таблица §6: «76 кроме 76.07.5/76.07.9»). В ОСВ Счастливочной на 30.09.2024 на 76.АВ — 2 366 666,69 с контрагентом АО «Русский бисквит»: результирующая таблица даёт ДЗ 110 429 113,15, эталон (`fixtures/golden/Консолидация_Группа РБ.xlsx`, лист `01.10.2024`, строка 1230 «в т. ч. взаимные») — 108 062 тыс., ровно без 76.АВ (107 712 446,46 + 350 000 по 76.06 = 108 062 446,46). Есть ли ещё субсчета 76, которые нужно исключать | ревью RSHB/arm-core#114 (шахматка, AURORA-1537), 15.10.2026; вопрос аналитику — AURORA-1373 | `elimination_rule` (Прил. 3, данные), §4, §6; `RESULT_ARTICLES` шахматки; сверка с эталоном (OQ-155) | открыт: до ответа аналитика/методологов расчёт по §4/§6 как есть (76.АВ входит в ДЗ); исключение — правка правил-данных и §4/§6, не кода шахматки |
|
||||
| OQ-165 | Склейка двух половинок одной формы из **одного** файла (Р-15): Excel с листом-продолжением другого типа, двойная печать формы, две таблицы ИС одного типа и периода. Сейчас вторая форма того же типа и периода хранится отдельным документом (Р-10), ни одна не автоподтверждается (Р-18), подтверждение второй при подтверждённой первой — 409 `DUPLICATE_FORM`; склейки нет — файл перезагружают. Нужна ли склейка (кнопка «объединить формы») и по каким правилам | change `unified-statement-normalization`, Р-15, Р-18 | arm-core (`document.file_ordinal`, подтверждение), arm-web, §9.5 п. 2 | открыт: от ИС Сканирование такой случай не ожидается (владелец 17.10.2026), основной источник — Excel; допущение — склейки нет, файл перезагружают |
|
||||
| OQ-166 | Упрощённая форма ББ/ОФР (приказ 66н, прил. 5): признак — по структуре кодов (ББ: есть 1600, нет 1100 и 1200; ОФР: есть 2400 или 2110, нет 2100, 2200, 2300), ОКУД 0710096 признаком не служит. Набор соотношений §7 и перечень кодов упрощённой формы заданы по бланку (`05-domain-rules.md` §7, решение S-5 спеки) без образца в `fixtures/` | change `unified-statement-normalization`, design (Decisions № 14) | `arm_statements` `statement_checks.yaml`, перечень кодов, `form_variant` | открыт: допущение — формулы §7 «Упрощённый набор»; нужен реальный образец упрощённой отчётности (Excel и скан) в `fixtures/` для проверки признака и формул |
|
||||
| OQ-167 | Несколько отчётных периодов в одном файле, отданном в ИС Сканирование (например, ББ за два квартала в одном PDF): шапка в json ИС одна на json и копируется во все его таблицы | change `unified-statement-normalization`, design шаг 1 (допущение владельца 17.10.2026) | scan-gateway (адаптер шага 1), период документа | допущение владельца: ИС отдаёт разные периоды разными json; образца нет — проверить на первом таком файле, при расхождении — шапка из json неверна для части таблиц |
|
||||
|
||||
Служебные идентификаторы в DBML/аналитике: `ANL-nnn` — замечания аналитика к макету; `ARCH-nnn` — архитектурные решения;
|
||||
`DECISION-n` — решения по модели; `FIX-nn` — исправления дефектов v4.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user