# Spec Delta ## MODIFIED Requirements ### Requirement: Словари форм, координаты, golden Словари форм (заголовки и ОКУД, периоды, соотношения §7, единицы, перечни кодов) SHALL жить в установленной библиотеке `arm_statements` и в tabular-parser MUST NOT дублироваться: файлы `api/rules/statement_*.yaml` удалены. Код `src/parser/statements/**` и `src/parser/book.py` MUST NOT содержать литералов координат (линтер `test_no_coordinates`); `ref` ячейки собирается из индексов функцией `grid.cell_ref`. Снимки `../fixtures/golden/expected_statement_parse/**` SHALL покрывать все файлы `statements-xlsx/**` и обновляться только `make golden-update`; `service.statements_version` — волатильное поле снимка. Property-тест по манифесту `tests/statements_manifest.yaml` SHALL проверять тип, ИНН, ожидаемый статус, сходимость жёстких соотношений для полных форм и, где задано, множество кодов предупреждений `error` документа. #### Scenario: Новый синоним без релиза кода - **WHEN** в словарь заголовков библиотеки `arm_statements` добавлен синоним заголовка «Баланс предприятия» и библиотека переустановлена командой `make install` - **THEN** лист классифицирован как `BALANCE` без изменения кода tabular-parser; собственного словаря форм в сервисе нет #### Scenario: Мёртвая запись словаря - **WHEN** запись словаря форм не срабатывает ни на одном файле - **THEN** это проверяет тест покрытия словарей библиотеки `arm_statements` (внутри пакета, на его данных); тест покрытия tabular-parser словари форм не охватывает #### Scenario: Регрессия по снимкам - **WHEN** результат разбора любого файла `statements-xlsx/**` отличается от снимка - **THEN** golden-тест падает с diff; обновление — только `make golden-update` и отдельный коммит в fixtures ### Requirement: Адаптер Excel шага 1 tabular-parser SHALL оставить у себя чтение книги (лимиты `PARSE_MAX_BYTES`, `PARSE_TIMEOUT_SECONDS`, формат по сигнатуре, лимиты zip и объединённых ячеек, `UNREADABLE`), классификацию листов (ОСВ — отдельный путь; форма; продолжение; прочее) и адаптер «блок формы на листе → `FormCandidate`» общего кода; шаги 2–8 по формам SHALL выполняться установленной библиотекой `arm_statements`. Адаптер SHALL: выделять блоки форм на листе по заголовку или ОКУД (без кодов между ними блоки сливаются, заголовок после кодов — новая форма); собирать шапку каждого блока в его границах (организация, ИНН без ИНН аудитора, ОКПО, ОКОПФ, ОКФС, единицы, дата, адрес) как `Cell` с `confidence = 100`; заполнять у каждой ячейки `text` по числовому формату (tp#42) и `value`, `ref = <лист>!` (имя листа в одинарных кавычках по правилу Excel, если в нём есть что-то кроме букв, цифр и `_`); дописывать листы-продолжения в сетку открытой формы до раскладки (маркер «Форма … с.2» или позиция листа; проверки типа, диапазона кодов и, при трёх и более кодах, числа колонок — как `join_blocker`), записывая границы листов в `segment_breaks`. Несовпадение формата с `file_type` — предупреждение файла `FILE_TYPE_MISMATCH` (`info`). Лист без признаков ОСВ и формы, без `type_hint` и кодов — предупреждение файла `SHEET_OTHER` (`info`, `locator = sheet:<имя>`), документа нет. #### Scenario: Книга ББ + ОФР - **WHEN** книга содержит лист «Баланс» (ОКУД 0710001), лист «Баланс (2)» с маркером «Форма 0710001 с.2» и лист «ОФР» - **THEN** адаптер выдаёт два кандидата: `BALANCE` с `locator = sheet:Баланс,Баланс (2)` и одной сеткой из двух листов (`segment_breaks` — первая строка второго листа) и `INCOME` с `locator = sheet:ОФР` #### Scenario: Ссылки ячеек - **WHEN** код строки стоит в ячейке C27 листа «Баланс» - **THEN** `ref` строки — `Баланс!C27`; у листа «Бухгалтерский баланс. Лист 1» — `'Бухгалтерский баланс. Лист 1'!C27`