docs(2b): §12 ББ/ОФР по реализации, §7 жёсткие/мягкие, §1.1/§1.3–§1.5 по факту PR tabular-parser #13/#15
Refs #26 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: cedd7915-2efe-41e5-988c-a9117033598b
This commit is contained in:
parent
909e7ff084
commit
6d39800e02
@ -86,6 +86,13 @@ r11 Договоры ←
|
||||
"period_text":"1 квартал 2025 г.","data_type":"BU","groups":[{"type":"ACCOUNT"},{"type":"COUNTERPARTY","has_inn":true},
|
||||
{"type":"CONTRACT"}]}` и → `document(document_type=OSV, osv_account_code, period_year, period_quarter, is_cumulative, locator)`.
|
||||
|
||||
**Шапка без меток и без строки организации (реализовано, PR tabular-parser #13, OQ-134; ОСВ 63 Новоросметалл).** Период
|
||||
может стоять отдельной ячейкой («Бухгалтерский | 9 месяцев 2025 г.»: `periods.yaml` — `standalone`, `data_types.yaml` —
|
||||
`standalone_header`), организация — хвостом названия ведомости, а колонки счёта — подписями «Счет: Код / Наименование»
|
||||
(`osv_headers.yaml` — `column_captions`: «Код» и «Наименование» под группой «Счет» не группировки). Код счёта берётся из
|
||||
первой строки-счёта таблицы, если «по счету …» в шапке нет, — с предупреждением «код счёта … из строки таблицы». Текст
|
||||
строки может стоять правее колонки текста: уровень задаёт колонка, в которой он найден.
|
||||
|
||||
### 1.2 Колонки сумм
|
||||
|
||||
Заголовки сумм объединены (merged) и позиции колонок плавают между файлами даже одной организации. Определять по тексту
|
||||
@ -99,8 +106,13 @@ r11 Договоры ←
|
||||
Для каждой строки табличной части: `row_number` (исходный номер), `level` (outline level Excel, в xls — `rowinfo_map`; без группировок — каскад §1.0 п. 5),
|
||||
`row_value` (текст первой колонки), шесть сумм, ИНН (**только из колонки ИНН**; в ячейке оставляются цифры, результат
|
||||
только при 10 или 12 цифрах — «ИНН 352819549855» → `352819549855`, иначе NULL; число из 9/11 цифр — ИНН региона 01–09
|
||||
с потерянным ведущим нулём — при извлечении из выбранной колонки дополняется нулём слева, R-24; ИНН внутри текста строки контрагента
|
||||
(«Полиграф, 3528315710») парсер не извлекает: `counterparty_name` = весь текст строки, **[открыто]** OQ-100),
|
||||
с потерянным ведущим нулём — при извлечении из выбранной колонки дополняется нулём слева, R-24; ИНН контрагента — в порядке
|
||||
приоритета: колонка ИНН (в том числе склеенная «ИНН/КПП»), затем строка без сумм **прямо над** названием на том же уровне
|
||||
оформления (в `rows` не попадает, предупреждение «ИНН … над строкой … отнесён к ней»), затем конец текста названия
|
||||
(«Полиграф, 3528315710», «ООО Альфа (ИНН 7701234567)» — 10 или 12 цифр после запятой, скобки, «/», пробела или метки «ИНН»).
|
||||
Из текста ИНН отрезается всегда: `counterparty_name` без ИНН и разделителя, `row_value` — исходный текст; только для строк
|
||||
`COUNTERPARTY`, договоры и прочее не трогаются; правила — `osv_headers.yaml` (`counterparty_inn_suffix`, `inn_only_row`,
|
||||
`inn_kpp_cell`); **реализовано**, OQ-100, OQ-130 (tabular-parser #9, PR #15; синтетика `s17`–`s19`),
|
||||
`data_type` (строки «USD», «Вал.», «Кол.» под контрагентом → CURRENCY/QUANTITY; по умолчанию — из шапки).
|
||||
|
||||
Строки «Вал.», «Кол.» и трёхбуквенный код валюты («USD») — не уровень группировки, а вид сумм родителя:
|
||||
@ -159,7 +171,12 @@ r11 Договоры ←
|
||||
значения родителя и выше не затронуты);
|
||||
- родитель (0, 0) при детях (X Дт, X Кт) принимается (нетто 0) — осознанное поведение, закреплено тестом;
|
||||
- вне R-21: текст вида «12.ООО» принимается за счёт (§1.3, R-16).
|
||||
- Строка «Итого» (TOTAL) == корневая строка ACCOUNT (первая строка уровня 0). Если корня нет (**пустая ОСВ**: заголовок и
|
||||
- Строка «Итого» (TOTAL) == корневая строка ACCOUNT (первая строка уровня 0); если корней уровня 0 несколько (лист без
|
||||
строки корневого счёта: верхний уровень — субсчета 58.01, 58.03, OQ-133, PR #13) — «Итого» == **сумме строк уровня 0**
|
||||
(показатели без суммы и сама «Итого» не входят; сальдо — по колонкам либо по нетто, если «Итого» свёрнуто, как у родителя
|
||||
выше, R-21); при одном корне сверка прежняя. Вторая итоговая строка сразу за «Итого» («Итого развернутое») — тоже `TOTAL`,
|
||||
в сверку не входит; строка сумм без подписи в конце таблицы принимается за «Итого» с предупреждением.
|
||||
Если корня нет (**пустая ОСВ**: заголовок и
|
||||
сразу «Итого» — в фикстурах две, schastl 58 и 66 за 2 кв. 2025) — «Итого» должна быть пустой или нулевой; такая ОСВ
|
||||
штатна: `done`, `hierarchy=trivial`, `metrics.rows = 0`, предупреждение «нет строк данных». Если строка «Итого» не
|
||||
найдена вовсе — сверка невозможна: `total_row_matches_root = false` и предупреждение «строка «Итого» не найдена»
|
||||
@ -179,6 +196,15 @@ r11 Договоры ←
|
||||
| 4 квартал, IV кв. | Q4 | **false** [открыто] |
|
||||
| год, за 2025 год, 12 месяцев, годовая | Q4 | true |
|
||||
|
||||
**Период-диапазон месяцев** («Январь - Май 2025 г.», «Октябрь 2024 г. – Сентябрь 2025 г.»; `periods.yaml`: `months`, `range`;
|
||||
OQ-131, PR #13 и 2b): парсер заполняет границы `period_start`/`period_end`/`is_from_year_start` (контракт v1.2; для квартала
|
||||
и года: конец — последний день квартала, начало — 1 января у нарастающего и первый день квартала у квартального;
|
||||
`is_from_year_start` — `period_start` = 1 января). Диапазон «январь – конец квартала» — нарастающий
|
||||
(`is_cumulative = true`, квартал по месяцу конца), ровно один квартал — квартальный, остальное (скользящие 12 месяцев,
|
||||
полгода с апреля, конец не на границе квартала) — `period_year` = год конца, `period_quarter = null`,
|
||||
`is_cumulative = null`, границы по тексту и предупреждение «не с начала года» / «конец диапазона не совпадает с концом
|
||||
квартала»: ядро такой документ к версии не привязывает (OQ-131).
|
||||
|
||||
`ASSUMPTION(OQ-02)`: парсер определяет `period_quarter` и `is_cumulative` **только по тексту периода**
|
||||
(`rules/periods.yaml`, правила идут от специфичных к общим): квартальная ведомость Q2–Q4 считается не нарастающей;
|
||||
сверку по данным (`Q3.opening == Q1.opening`) выполняет ядро и может переопределить признак. `period_year` — четыре
|
||||
@ -420,6 +446,14 @@ 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)`). Допуск ±1 в единицах формы, расходы — двойной расчёт знаков (`abs_terms`). Межформенная проверка
|
||||
`1370 − 1370(начало года) = 2400` парсером не выполняется. Это сигнал о структуре и данных файла, а не замена проверок
|
||||
ядра: ядро (Срез 3) пересчитывает §7 и ведёт акцепт (`financial_statement_meta.checks_accepted_*`).
|
||||
|
||||
Когда: после распознавания; после «Сохранить» правок; при «Подтвердить данные». Непройдено → `document.status =
|
||||
NEEDS_CHECK`; пройдено/акцептовано + нажато «Подтвердить» → `CONFIRMED` + `audit_event(DOCUMENT_CONFIRM)`.
|
||||
Отрицательные значения в формах — в скобках `(2100)` → `-2100`. Значения с `confidence < 100` подсвечиваются.
|
||||
@ -742,30 +776,67 @@ OQ-92 (пустые «Организация»/«Группа компаний»
|
||||
что и у `scan-gateway`, чтобы ядро маппило ББ/ОФР одинаково независимо от источника (`confidence = 100`,
|
||||
`is_recognized = true` для табличного пути).
|
||||
|
||||
1. **Классификация листа**: ячейки с «Бухгалтерский баланс» / «0710001» → BALANCE; «Отчет о финансовых результатах» /
|
||||
«Отчет о прибылях и убытках» / «0710002» → INCOME. На одном листе могут быть обе формы подряд (ББ, затем ОФР) —
|
||||
разрезать по второму заголовку; в одном файле несколько листов — несколько `document`. Листы ОСВ и форм в одном
|
||||
файле — допустимо.
|
||||
2. **Колонка кодов строк**: та, где ≥ 60 % непустых значений — четырёхзначные коды из допустимого набора
|
||||
(1110–1700 для ББ, 2110–2500 для ОФР, плюс 5xxx для расшифровок). Заголовок «Код» необязателен. Если колонки кодов
|
||||
нет — попытаться сопоставить по наименованиям показателей (словарь `rules/statement_lines.yaml`: «Основные средства» →
|
||||
1150) с пометкой `line_code_confidence < 100` и warning; агент-аналитик подтвердит.
|
||||
3. **Колонка наименований**: ближайшая текстовая слева от кодов; «Пояснения» (1–2 символа/цифры) слева от неё — не
|
||||
наименование.
|
||||
4. **Колонки-периоды**: ячейки заголовка справа от кодов, матчащие дату («На 31 декабря 2024 г.», «31.12.2024»,
|
||||
«2024», «За 9 месяцев 2025 г.», «За январь–сентябрь 2025 г.») → `financial_statement_column` с `period_date`
|
||||
(для ОФР — дата конца периода) и `is_current` = самая поздняя. Число колонок — от 1 до 3+. Колонки без даты
|
||||
(«Изменение», «%») — в `extra`.
|
||||
5. **Значения**: Decimal по правилам §1.2; скобки → минус; «–»/«-»/пусто → NULL. Единица измерения — из шапки
|
||||
(«тыс. руб.», «млн руб.», ОКЕИ 384/385) → `header.units`; если не найдена — по порядку величины относительно
|
||||
ОСВ той же организации **не угадывать**, оставить NULL (ядро спросит).
|
||||
6. **Шапка**: организация, ИНН, дата, ОКПО/ОКОПФ/ОКФС/ОКВЭД — регулярками по области выше таблицы; как в §1.0 п. 6.
|
||||
7. **Самопроверка**: формулы §7 по каждой колонке; расхождение > 0 (в единицах формы) → `needs_check` с перечнем
|
||||
непройденных соотношений в `metrics.warnings`. Это не замена проверок ядра, а сигнал, что структура угадана неверно
|
||||
(например, перепутаны колонки периодов).
|
||||
8. Расшифровки к строкам (лист `К балансу`, рекомендуемые формы банка) — в первой очереди **не разбирать**; классифицировать
|
||||
как `DISCLOSURE`/`OTHER` и сохранить `locator`.
|
||||
**Статус: реализовано (Срез 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б).
|
||||
|
||||
Что это даёт ядру: один маппинг `statementDocument → document + financial_statement_*` для PDF и Excel; экран
|
||||
«Проверка распознавания» для Excel-источника показывает слева не страницы, а рендер листа (или просто таблицу) —
|
||||
UI-решение вне парсера.
|
||||
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-138).
|
||||
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`. **Единицы не нормализуются**: `amount` как в ячейке, `header.units` — текст шапки; при «тыс.» и
|
||||
≥ 10 % значений с дробной частью — предупреждение «N значений с дробной частью при единице тыс. руб.» (OQ-137,
|
||||
решение владельца). Строки без кода (разделы, «в том числе:», «Реализация иного имущества…» у НПЗ) не отдаются
|
||||
(`ASSUMPTION(OQ-36)`, OQ-140); повтор кода — остаётся первая строка, код в `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`, «сумма пятизначных подстрок = родитель». Допуск ±1 **в единицах формы**,
|
||||
отсутствующий код = 0, соотношение проверяется, только если присутствует его цель; правая часть считается дважды — как
|
||||
записано и с членами `abs_terms` по модулю (1С выгружает расходы со знаком минус, 66н — в скобках). Результат —
|
||||
`documents[].checks` (`relations_failed`, `soft_relations_failed`, `duplicate_line_codes`) и предупреждения.
|
||||
**Фактически сходятся полные формы только у Межениновской**: книга НПЗ (`npz/2025-Q3`) — ББ `needs_check`: `1600 = 1700`
|
||||
расходится на 1.94 / 9.02 / 2.48 руб., потому что значения в рублях с копейками при шапке «тыс. руб.»; допуск ±1 остаётся
|
||||
(решение владельца 06.10.2026, вариант А, OQ-137). ОФР НПЗ и ОФР Унагранде (стр. 1) тоже `needs_check` по 2100/2200/2300
|
||||
по той же причине; ББ Унагранде (стр. 1) — только актив, `1600 = 1700` не сходится. Это сигнал о данных файла, а не о
|
||||
неверно угаданной структуре; допуск под файл не подгоняется.
|
||||
8. **Расшифровки** (0710003/0710004, пояснения, лист «Примечания» шаблона) — `DISCLOSURE` с `locator` и шапкой, если
|
||||
нашлась, без `columns`/`lines`; на статус не влияет (OQ-23). Книга только из таких листов — `done`.
|
||||
9. **Склейка**: листы-продолжения внутри книги присоединяются к последнему открытому документу формы, если число колонок
|
||||
сумм совпадает; строки добавляются в конец, `locator` перечисляет листы через запятую. **Страницы в разных файлах не
|
||||
склеиваются** (1С печатает форму постранично, один файл = одна страница): страница 2 без первой — документ с пустой шапкой
|
||||
и `needs_check`, предупреждение «страница-продолжение без шапки: загрузите форму одним файлом» (решение владельца, OQ-139).
|
||||
Продолжение без колонки кодов при непустом результате не теряется молча — `needs_check`.
|
||||
10. **Статус**: документ `ok` ⇔ жёсткие соотношения сошлись по всем колонкам ∧ нет дубликатов кодов ∧ найдена колонка кодов
|
||||
∧ есть колонка-период ∧ в шапке есть организация или ИНН ∧ документ не одинокое продолжение. Книга — `done`, если `ok`
|
||||
у всех документов, иначе `needs_check`; книга без листов ОСВ и форм — `INVALID_DOCUMENT`. Порядок документов: сначала
|
||||
ОСВ в порядке листов, затем формы. Шаблоны (коды есть, сумм нет, шапки нет) — `needs_check`.
|
||||
11. **Отложено**: запасной путь по именам строк (OQ-138), нормализация единиц (OQ-137), признак продолжения в контракте и
|
||||
склейка страниц из разных файлов в ядре (OQ-139, Срез 3), строки без кода (OQ-140), образцы ФСБУ (OQ-141), лист
|
||||
продолжения с единственной кодовой строкой (OQ-142: лист ОФР НПЗ с одной строкой 2400 не распознаётся как форма и
|
||||
теряется, `min_codes` = 3).
|
||||
|
||||
Что это даёт ядру: один маппинг `statementDocument → document + financial_statement_*` для PDF и Excel (маппинг форм —
|
||||
Срез 3); экран «Проверка распознавания» для Excel-источника показывает не страницы, а таблицу — UI-решение вне парсера.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user