Proposal, design (D-1…D-11, решения без обсуждения №1–№11), новая capability statement-parsing, дельта tabular-recognition-service (BookParser, schema_version 1.2), tasks. Решения владельца 06.10.2026: только парсер и контракт; страницы-продолжения без склейки между файлами; единицы как есть; пятизначные подстроки как строки; контракт MINOR 1.2; без запасного пути по именам. Refs #14 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: cedd7915-2efe-41e5-988c-a9117033598b
22 KiB
statement-parsing Specification
Purpose
Разбор бухгалтерского баланса (0710001) и отчёта о финансовых результатах (0710002) из книг Excel в
statementDocument контракта v1.2 по §12 05-domain-rules.md: тип формы, шапка, колонки-периоды, строки с кодами и
значения, соотношения §7, склейка листов-продолжений внутри книги. Структура ищется по содержимому: ни буквы колонок,
ни номера строк, ни имена листов не являются контрактом; синонимы и соотношения — данные в rules/statement_*.yaml.
ADDED Requirements
Requirement: Листы форм и их продолжения находятся по содержимому
Классификация листа SHALL различать OSV, STATEMENT, STATEMENT_CONTINUATION и OTHER. Лист SHALL считаться
STATEMENT, если в любой строке до первой строки заголовка таблицы формы или первой строки с кодом формы есть заголовок
формы или код ОКУД из словаря statement_headers.yaml. Лист без заголовка формы SHALL считаться STATEMENT_CONTINUATION,
если на нём есть маркер продолжения («Форма 0710001 с.2»), либо строка заголовка таблицы формы (ячейка «Код» и правее
ячейки-периоды), либо колонка, в которой не меньше 60 % непустых значений — коды строк форм. Признак ОСВ SHALL
проверяться первым. Тип формы SHALL определяться по заголовку или ОКУД: баланс / 0710001 → BALANCE, отчёт о
финансовых результатах (о прибылях и убытках) / 0710002 → INCOME, пояснения, расшифровки и прочие формы 0710xxx →
DISCLOSURE. Титул «Бухгалтерская отчетность» MUST NOT задавать тип: тип берётся из ближайшего следующего заголовка.
Два заголовка форм на одном листе SHALL давать два документа, второй — со строки своего заголовка.
Scenario: Заголовок формы после титульного блока
- WHEN разбирается
statements-xlsx/npz/2025-Q3/Бухгалтерская отчетность … .xlsx, лист «Бухгалтерский баланс. Лист 1», где заголовок «Бухгалтерский баланс» стоит ниже титула «Бухгалтерская отчетность за январь - сентябрь 2025 г.» - THEN лист классифицирован как
STATEMENT, документBALANCE,header.okud = "0710001"
Scenario: Лист-продолжение без заголовка
- WHEN разбирается лист «Бухгалтерский баланс. Лист 2» той же книги (первая строка — «в том числе:», далее 11901, 1100…)
- THEN лист классифицирован как
STATEMENT_CONTINUATIONи не даёт отдельного документа
Scenario: Маркер «с.2» в 1С-выгрузке
- WHEN разбирается
statements-xlsx/unagrande-co/2024-FY/Б.Б. Лист 2.xls(первая строка «Форма 0710001 с.2») - THEN лист классифицирован как
STATEMENT_CONTINUATION, тип по ОКУД маркера —BALANCE
Scenario: Две формы на одном листе
- WHEN разбирается синтетический лист
st01_both_forms_one_sheet(баланс, затем ОФР) - THEN результат содержит два документа —
BALANCEиINCOMEс одинаковымlocatorлиста, строки ОФР не попали в баланс
Scenario: Расшифровка
- WHEN разбирается лист с заголовком «Пояснения к бухгалтерскому балансу и отчёту о финансовых результатах»
- THEN документ
DISCLOSUREсlocator, безcolumnsиlines; статус книги от него не зависит
Requirement: Заголовок таблицы, колонка кодов и колонки-периоды определяются по тексту
Строкой заголовка таблицы SHALL считаться первая после заголовка формы строка с ячейкой «Код»/«Коды» (синонимы в
словаре) и хотя бы одной ячейкой-периодом правее; заголовок, разнесённый на две строки или с переносами внутри ячейки,
SHALL склеиваться; строка нумерации колонок («1 2 3 4 5») SHALL пропускаться. Колонкой кодов SHALL считаться колонка,
в которой не меньше 60 % непустых значений блока — четырёх- или пятизначные коды строк форм (1100–1799 для ББ, 2100–2999
для ОФР и их пятизначные расширения, 5xxx для расшифровок); коды читаются как текст из ячеек любого типа. Колонкой
наименований SHALL считаться ближайшая левее кодов колонка с преобладанием текста; колонка «Пояснения» в наименования
MUST NOT попадать. Колонками-периодами SHALL быть все ячейки заголовка правее кодов с распознанным периодом, по порядку
слева направо, column_index с нуля, period_date — дата или конец периода из statement_periods.yaml. Ячейки заголовка
без периода MUST NOT становиться колонками; их значения не читаются, в предупреждения пишется одна строка. Если колонки
кодов нет, документ SHALL отдаваться без строк со статусом needs_check и предупреждением «колонка кодов не найдена».
Scenario: Три раскладки, одни и те же колонки
- WHEN разбираются
unagrande-co/2024-FY/Б.Б Лист 1.xls(колонки «Пояснения | Наименование показателя | Код | На 31 декабря 2024 г. | … 2023 | … 2022»),npz/2025-Q3/…xlsx(«Пояснения | Наименование показателя | Код | На 30 сентября 2025 г. | …») иmezheninovskaya/2025-Q3/1_Форма1_бух_09.xlsx(«Наименование показателя | Коды | За 9 месяцев 2025 года | На 31 декабря 2024 года | …», под шапкой строка «1 2 3 4 5») - THEN у каждого документа три колонки с
period_date2024-12-31/2023-12-31/2022-12-31,2025-09-30/2024-12-31/2023-12-31и2025-09-30/2024-12-31/2023-12-31соответственно, строка нумерации не стала строкой данных, «Пояснения» не попали вline_name
Scenario: Заголовок в две строки
- WHEN разбирается шаблон
templates/ББ после 01.01.2025.xlsx(«На» в одной строке, «20__ г.» в следующей) - THEN колонки-периоды найдены по склеенному тексту, документ
BALANCEбез сумм, статусneeds_check
Scenario: Колонка без периода
- WHEN в синтетическом листе правее колонок дат есть колонка «Изменение, %»
- THEN она не входит в
columns, предупреждение «колонка без периода пропущена: Изменение, %»
Scenario: Нет колонки кодов
- WHEN разбирается синтетический лист
st03_no_codes(наименования и суммы без кодов) - THEN документ
BALANCEс пустымиlines, статусneeds_check, предупреждение «колонка кодов не найдена»
Requirement: Шапка документа
Поля шапки SHALL искаться по меткам из statement_headers.yaml в области от начала блока формы до строки заголовка
таблицы; значение — ближайшая непустая ячейка правее метки, иначе первая непустая ячейка следующей строки. inn SHALL
быть 10 или 12 цифр у метки «ИНН», причём строки после метки аудитора MUST NOT использоваться. organization_name SHALL
браться после метки «Организация», а при пустом значении — из ближайшей строки выше с признаком ОПФ. units SHALL
отдаваться текстом как в файле без нормализации. report_date_text/report_date SHALL браться из подзаголовка формы
(«на <дата>», «за <период>»), иначе из тройки «число, месяц, год»; для ОФР report_date — конец периода. Найденные поля
SHALL иметь *_confidence = 100. У документа без шапки все поля SHALL быть null, кроме form_name из маркера.
Scenario: Полная шапка 1С
- WHEN разбирается
unagrande-co/2024-FY/Б.Б Лист 1.xls - THEN
organization_name = 'Акционерное общество "Унагранде Компани"',inn = "3249004697"(не ИНН аудитора 7716021332),okpo = "47888105",okud = "0710001",units = "в тыс. рублей",report_date = 2024-12-31,report_date_text = "на 31 декабря 2024 г.",addressначинается с «242440»
Scenario: Организация над меткой, ОФР за период
- WHEN разбирается лист «Отчет о финансовых результата» книги НПЗ
- THEN
organization_name = 'ООО "НОВОРОССИЙСКИЙ ПРОКАТНЫЙ ЗАВОД"',inn = null(на листе ОФР метки «ИНН» нет, а шапка другого листа не используется),okpo = "16098811",report_date = 2025-09-30,units = "тыс. руб."; у документаBALANCEтой же книгиinn = "2315995868"из титульного блока над заголовком формы
Scenario: Дата из тройки чисел
- WHEN разбирается
mezheninovskaya/2025-Q3/1_Форма1_бух_09.xlsx(ячейки 30 | 9 | 2025 как числа) - THEN
report_date = 2025-09-30,inn = "7016006635",okpo = "85262693"
Requirement: Строки с кодом и значения
Каждая строка блока с кодом в колонке кодов SHALL давать line с line_code (текст без пробелов), line_name (trim,
переносы заменены пробелом), line_code_confidence = line_name_confidence = 100 и по одному value на каждую
колонку-период. Строки без кода MUST NOT отдаваться (ASSUMPTION(OQ-36)); пятизначные подстроки SHALL отдаваться как
обычные строки. Число SHALL приводиться к Decimal по §1.2 (скобки → минус, пробелы убираются); пусто, «–», «-» →
amount = null, is_recognized = true; нечисловой текст → amount = null, string_value = текст, is_recognized = false; confidence = 100. Повтор кода внутри документа SHALL оставлять первую строку, записывать код в
checks.duplicate_line_codes и переводить документ в needs_check. Единицы MUST NOT нормализоваться: при units с
«тыс.» и дробной частью не меньше чем у 10 % значений в предупреждения SHALL писаться «значений с дробной частью при
единице тыс. руб.: N».
Scenario: Подстроки и значения с копейками
- WHEN разбирается
unagrande-co/2024-FY/Б.Б Лист 1.xls - THEN есть строки
1110(«Нематериальные активы»,5971264.14) и11101(«Нематериальные активы в организации»,5852464.14); строка без кода с нулями после 11103 не отдана; предупреждение о дробной части при «тыс. рублей» есть
Scenario: Пустые значения и прочерки
- WHEN в
mezheninovskaya/2025-Q3/1_Форма1_бух_09.xlsxу строки 1170 пусто в первой колонке и 10 в остальных - THEN
values[0].amount = null,values[1].amount = "10", всеis_recognized = true
Scenario: Отрицательные в скобках
- WHEN в синтетическом ОФР 2120 записана как «(6 162 067)»
- THEN
amount = "-6162067"
Requirement: Соотношения §7 — данные, проверка по колонкам
Соотношения SHALL храниться в statement_checks.yaml в виде {form, relation, strict, abs_terms} и вычисляться чистой
функцией над «код → сумма» по каждой колонке-периоду отдельно и только по четырёхзначным кодам; отсутствующий код —
0; соотношение SHALL считаться выполненным, если сходится правая часть как записана либо с членами abs_terms по
модулю, с допуском ±1 в единицах формы. Нарушение жёсткого соотношения SHALL попадать в checks.relations_failed
(column_index, relation, expected, actual) и переводить документ в needs_check; нарушение мягкого — в
checks.soft_relations_failed и предупреждение. Проверка «сумма пятизначных подстрок = родитель» SHALL генерироваться по
коду как мягкая, когда подстроки есть. Перечень нарушений SHALL дублироваться строками в metrics.warnings.
Scenario: Полные формы сходятся
- WHEN разбираются книга НПЗ и обе формы Межениновской
- THEN у документов
BALANCEиINCOMEchecks.relations_failed = []по всем колонкам
Scenario: Знаки 1С
- WHEN в ОФР Унагранде Компани (страница 1) расходы записаны со знаком минус (2120 = −6162067308.99)
- THEN
2100 = 2110 − 2120выполнено через вариант сabs_terms, вrelations_failedего нет
Scenario: Испорченный итог
- WHEN в синтетическом балансе 1600 на 5 больше суммы 1100 + 1200
- THEN
relations_failedсодержит{column_index: 0, relation: "1600 = 1100 + 1200", expected, actual}, статусneeds_check
Scenario: Мягкое соотношение
- WHEN в ОФР подстроки 2411 + 2412 не равны 2410
- THEN нарушение в
soft_relations_failed, статус документа не меняется
Requirement: Склейка листов-продолжений и статус документа
Листы книги SHALL обрабатываться по порядку; лист STATEMENT_CONTINUATION SHALL присоединяться к последнему открытому
документу формы при совпадении числа колонок-сумм, строки добавляются в конец, locator SHALL перечислять имена листов
через запятую. Продолжение без открытого документа SHALL давать документ с пустой шапкой, типом по ОКУД маркера (иначе
OTHER), статусом needs_check и предупреждением «страница-продолжение без шапки: загрузите форму одним файлом».
Документ SHALL считаться ok, если все жёсткие соотношения сошлись по всем колонкам, дубликатов кодов нет, колонка
кодов найдена, есть хотя бы одна колонка-период, в шапке есть организация или ИНН и документ не является одиночным
продолжением. Книга SHALL получать done, если все документы ОСВ и форм ok, иначе needs_check; книга без документов
ОСВ и форм SHALL давать error INVALID_DOCUMENT.
Scenario: Баланс на пяти листах
- WHEN разбирается книга НПЗ
- THEN два документа:
BALANCEсlocator = "sheet:Бухгалтерский баланс. Лист 1,Бухгалтерский баланс. Лист 2,…,Лист 5"и строками от 1105 до 1700 включительно,INCOMEс тремя листами; статус книгиdone
Scenario: Одинокая страница 2
- WHEN разбирается
unagrande-group/2024-FY/ОФР. Лист 2.xls(стр. 1 в пакете нет) - THEN один документ
INCOMEсо строками 2510–2910, пустой шапкой, статусneeds_check, предупреждение о продолжении
Scenario: Страница 1 без продолжения
- WHEN разбирается
unagrande-co/2024-FY/Б.Б Лист 1.xls(только актив) - THEN документ
BALANCEсо строками 1110–1600, шапка полная,1600 = 1100 + 1200выполнено,1700/1300отсутствуют (считаются нулём: соотношение1600 = 1700нарушено →needs_checkс перечнем)
Scenario: Смесь ОСВ и форм в одной книге
- WHEN разбирается синтетическая книга
st04_mixed_book(лист ОСВ 60 и лист баланса) - THEN результат содержит
osvDocumentиstatementDocument, статус книги по обоим
Requirement: Словари форм, координаты, golden
Словари statement_headers.yaml, statement_periods.yaml, statement_checks.yaml SHALL валидироваться при старте;
каждая запись SHALL срабатывать хотя бы на одном файле ../fixtures/statements-xlsx/** или синтетике. Код
src/parser/statements/** и src/parser/book.py MUST NOT содержать литералов координат (линтер test_no_coordinates).
Снимки ../fixtures/golden/expected_statement_parse/** SHALL покрывать все файлы statements-xlsx/** и обновляться
только make golden-update; property-тест по манифесту tests/fixtures/statements_manifest.yaml SHALL проверять тип,
ИНН, ожидаемый статус и сходимость жёстких соотношений для полных форм.
Scenario: Новый синоним без релиза кода
- WHEN в
statement_headers.yamlдобавлен синоним заголовка «Баланс предприятия» и файл с таким заголовком - THEN лист классифицирован как
BALANCEбез изменения кода
Scenario: Мёртвая запись словаря
- WHEN запись словаря не срабатывает ни на одном файле
- THEN тест покрытия падает с именем записи
Scenario: Регрессия по снимкам
- WHEN результат разбора любого файла
statements-xlsx/**отличается от снимка - THEN golden-тест падает с diff; обновление — только
make golden-updateи отдельный коммит в fixtures