## ADDED Requirements ### Requirement: Шаг 2 — разрез кандидата по смене формы Перед шагами 2–8 кандидат SHALL разрезаться на части по смене формы (AURORA-1734, решение владельца 06.11.2026). Сегменты кандидата ищутся по правилу «Шаг 2 — сегменты, повторная шапка и границы листов». Тип формы сегмента — по первым `min_codes` кодам (порог словаря диапазонов): все они одной формы (отрезок → `document_type`, как на шаге 6) — это тип, которым сегмент открывается; иначе тип не определён. Новая часть SHALL начинаться с первой строки сегмента, если у сегмента своя строка-шапка и он открывается формой, отличной от типа текущей части (тип части — по большинству кодов её первого сегмента с кодами; после разреза — тип, которым открылась часть). Сегменты без шапки, с числом кодов меньше порога или той же формы остаются в текущей части; строки другой формы, приклеенные к сегменту без своей шапки (сегмент открывается кодами текущей формы), разреза не дают. Часть — обычный `FormCandidate` для шагов 2–8: строки сетки с первой строки сегмента до начала следующей части, `segment_breaks` — сдвинутые на начало части; `header` — тот же, что у исходного кандидата (шапка файла ИС одна на таблицу); `source` тот же. Первая часть сохраняет `locator`, `type_hint`, `evidence` и `warnings` исходного кандидата; следующая часть `k` (с 1) получает `locator = ";form:"`, `type_hint = null`, пустые `evidence`, из `warnings` — только предупреждения источника `XLSB_NO_FORMATS` (без них шаг 4 неверно проверяет масштаб xlsb) — её тип определяется на шаге 6 только по кодам, без `TYPE_CONFLICT` от заголовка таблицы. Кандидат без смены формы SHALL оставаться одним кандидатом без изменений. Пакет SHALL экспортировать функцию разреза (`split_by_form(candidate, rules) → tuple[FormCandidate, ...]`); хост вызывает её на каждый кандидат и передаёт части в `normalize_candidate` по порядку. Документы частей — отдельные документы результата (ядро различает их по `file_ordinal`, Р-10); модель данных и контракт не меняются. Правило общее для OCR и Excel. #### Scenario: ББ и ОФР одной таблицей ИС (АК-ЮЛ) - **WHEN** таблица ИС «Бухгалтерский баланс» содержит строки 1110…1700 (две страницы ББ с повторной шапкой на строке 19), а со строки 41 — шапку «Наименование показателя | Коды | За I полугодие 2026 года | …» и строки 2110…2400 - **THEN** две части: ББ — строки 0–40, `locator = "file:;table:0"`, колонки ББ; ОФР — строки 41–60, `locator = "file:;table:0;form:1"`, колонки и периоды по своей шапке, `TYPE_CONFLICT` нет; повторная шапка строки 19 разреза не даёт (тот же тип) #### Scenario: Раздел 25xx после ОФР - **WHEN** в таблице ОФР повторная шапка открывает сегмент со строками 2510…2910 - **THEN** разреза нет: тип сегмента тот же (`INCOME`), сегмент — продолжение ОФР #### Scenario: ОФР без своей шапки после пассива - **WHEN** после повторной шапки ББ идут строки 1310, 1300, 1700, а следом без шапки — 2110, 2120, 2100, 2400 - **THEN** разреза нет: сегмент открывается кодами ББ (как и до изменения) #### Scenario: Пара строк другой формы - **WHEN** после строк ББ повторная шапка открывает сегмент с двумя кодами 2xxx (меньше порога `min_codes`) - **THEN** разреза нет, сегмент остаётся в части ББ (как раньше) ## MODIFIED Requirements ### Requirement: Шаг 1 — многостраничная форма Требование относится к адаптеру Excel: у OCR каждая таблица ИС — отдельный кандидат, склейки таблиц нет (повторная шапка внутри таблицы ИС — сегмент шага 2; смена формы внутри таблицы — разрез кандидата, требование «Шаг 2 — разрез кандидата по смене формы»). У Excel одна форма SHALL быть одной сеткой на выходе шага 1. Строки продолжения дописываются в конец сетки по маркеру «Форма … с.2» или по позиции листа; граница листа записывается в `segment_breaks`. Тип из маркера продолжения SHALL сверяться с `type_hint` формы: расхождение — отдельный кандидат и предупреждение `CONTINUATION_TYPE_CONFLICT` (`error`). Лист без маркера и без шапки присоединяется, только если его коды в диапазоне открытой формы и, при трёх и более кодах, число колонок сумм совпадает с формой; иначе — отдельный кандидат и предупреждение `SHEET_NOT_JOINED` (`error`). #### Scenario: Лист-продолжение с одной строкой - **WHEN** после листа ОФР идёт лист без шапки с единственной строкой 2400 - **THEN** лист присоединяется к ОФР (код в диапазоне формы, кодов меньше трёх — число колонок не сверяется), граница листа — в `segment_breaks` #### Scenario: Лист другой ширины с тремя кодами - **WHEN** лист без маркера и шапки содержит три кода ББ и четыре колонки сумм при трёх у открытой формы - **THEN** лист — отдельный кандидат, предупреждение `SHEET_NOT_JOINED` уровня `error` ### Requirement: Шаг 2 — подпись и дата колонки Подпись колонки SHALL разбираться одним словарём периодов `statement_periods.yaml`, объединяющим словарь tabular-parser и шаблоны `dates.py` scan-gateway; строки `test_dates.py::test_parses_real_forms` scan-gateway — обязательные тесты словаря. Результат — `period_date` (конец периода колонки) и `period_kind`: - абсолютная подпись («На 31 марта 2025 г.», «31.12.2024», «30/09/25», «30092025», «За 9 месяцев 2025 г.», «с 01.01.2025 по 31.03.2025», «2024», хвост «0:00:00», клетки «2|0|2|4|») → `absolute`; - относительная: «На отчётную дату», «За отчётный период» → `relative:current` (= эффективная дата); «На 31 декабря предыдущего года» → `relative:prev_year_end` (31.12 года эффективной даты − 1); «На 31 декабря года, предшествующего предыдущему» → `relative:prev_prev_year_end` (31.12 года − 2); «За аналогичный период предыдущего года» → `relative:prev_period` (эффективная дата − 1 год, 29 февраля → 28 февраля); - подписи нет → `none`. Эффективная дата документа SHALL быть датой отчёта из шапки, иначе самой поздней абсолютной датой колонок; нет ни той, ни другой → у относительных колонок `period_date = null`, `period_kind` остаётся относительным. Подпись из словаря исключений («Изменение», «Отклонение», «%», «Темп», «Доля», «Примечание») SHALL пропускаться с предупреждением `COLUMN_SKIPPED` (`info`). Подпись, не разобранная ни как период, ни как исключение, при числах в столбце в строках с кодами SHALL оставлять колонку с подписью как есть, `period_date = null`, `period_kind = none` и предупреждением `COLUMN_WITHOUT_DATE` (`error`, `column_index`); без чисел — пропуск. Подпись OCR с уверенностью ниже 100, разобранная в дату, подтверждается, если дата входит в ожидаемые: эффективная дата, она же минус один и минус два года, 31 декабря предыдущего и предшествующего ему года; иначе предупреждение `COLUMN_DATE_UNCERTAIN` (`error`, `column_index`). Уверенность подписи — `label_confidence` (у Excel 100; подписи нет — `null`). Если среди колонок значений сегмента есть хотя бы одна с подписью периода (абсолютной или относительной), столбец без текста подписи правее **последней** такой колонки SHALL НЕ считаться колонкой сумм: его числа не попадают в значения строк, `COLUMN_WITHOUT_DATE` не выдаётся, вместо этого выдаётся одно предупреждение `NUMBERS_OUTSIDE_TABLE` (`info`) с адресами ячеек, собранными по всем сегментам формы (в тексте — первые 10, дальше «и ещё N»). Предупреждение SHALL НЕ влиять на статус документа. Решение владельца 18.10.2026 (Р-44). Правило общее для Excel и OCR и живёт в пакете. У сегмента без подписанных колонок (лист-продолжение без шапки) столбцы без текста подписи сверх числа колонок документа SHALL считаться числами правее таблицы. Подписанный столбец без чисел в строках с кодами границей таблицы не служит. Адрес ячейки — `<лист>!` у Excel и `row:` у OCR. Без подписанных колонок, а также для столбца без подписи между подписанными и для столбца с любым текстом подписи, не разобранным как период или исключение, поведение прежнее (`COLUMN_WITHOUT_DATE`), кроме редкого столбца ниже. **Редкий столбец с неразобранной подписью** (AURORA-1735, решение владельца 06.11.2026) SHALL НЕ считаться колонкой сумм, если одновременно: у столбца есть текст подписи, не разобранный ни как период, ни как исключение; левее и правее него в сегменте есть колонки значений с подписью периода; число стоит не более чем в одной строке с кодом этого столбца, а в каждой колонке значений сегмента с подписью периода — не менее чем в трёх; в строке этого числа у всех колонок периода тоже есть числа (число лишнее, а не сдвинутое OCR из пустой ячейки периода). Его числа не попадают в значения строк, `COLUMN_WITHOUT_DATE` не выдаётся; адреса его чисел добавляются в то же предупреждение `NUMBERS_OUTSIDE_TABLE` (`info`). Если среди адресов предупреждения есть адреса такого столбца, текст — «числа вне колонок формы не учитываются: …», иначе прежний «числа правее таблицы формы не учитываются: …». Это разорванная OCR подпись периода («31» + «а I полугодие 2025 года») с посторонним числом под обрывком; столбец с числами в двух и более строках с кодом остаётся колонкой с `COLUMN_WITHOUT_DATE`; единственное число в строке, где ячейка колонки периода пуста, — тоже. Подтверждение SHALL опираться только на уверенные источники, чтобы неуверенное не подтверждало само себя: для неуверенной подписи эффективная дата — дата отчёта с уверенностью 100, иначе самая поздняя абсолютная дата колонок с уверенной подписью (`label_confidence = 100`); такой нет — подпись не подтверждена. Неуверенная дата отчёта (шаг 8, поля шапки) подтверждается только самой поздней абсолютной датой колонок с уверенной подписью. Для периода документа и относительных подписей эффективная дата считается, как выше, по всем источникам. #### Scenario: Бланк 66н без впечатанных дат - **WHEN** подписи колонок ББ — «На отчетную дату», «На 31 декабря предыдущего года», «На 31 декабря года, предшествующего предыдущему», дата отчёта шапки 30.09.2025 - **THEN** `period_date` колонок 2025-09-30, 2024-12-31, 2023-12-31, `period_kind` — `relative:current`, `relative:prev_year_end`, `relative:prev_prev_year_end`; предупреждений о дате колонок нет #### Scenario: Неуверенная подпись не подтверждает сама себя - **WHEN** даты отчёта в шапке нет, единственная колонка с абсолютной датой — подпись OCR «На 30.09.2О25» с уверенностью 60, остальные колонки без подписи - **THEN** эффективная дата для подтверждения строится без этой подписи — уверенных источников нет, подпись не подтверждена: `COLUMN_DATE_UNCERTAIN` уровня `error`; период документа (`period_known`) по-прежнему определяется этой датой #### Scenario: Мусорная подпись над числами - **WHEN** подпись колонки — `"| | г"` с уверенностью 17, а в колонке суммы - **THEN** колонка остаётся, `period_date = null`, `period_kind = none`, предупреждение `COLUMN_WITHOUT_DATE` уровня `error` с `column_index`, документ `needs_check` #### Scenario: Числа справа от таблицы - **WHEN** у ББ подписи периодов стоят над столбцами M, Q, S, а правее таблицы в строках с кодами лежат формулы аналитика (V45, W57, U58, V58, V72, W72) над пустой шапкой (Межениновская 2025-Q3) - **THEN** колонок значений три, числа U–W в значения не попадают, `COLUMN_WITHOUT_DATE` и `SEGMENT_COLUMNS_MISMATCH` нет; одно предупреждение `NUMBERS_OUTSIDE_TABLE` уровня `info` с адресами этих ячеек; документ `done`, если нет других причин #### Scenario: Столбец без подписи между подписанными - **WHEN** между колонками с подписями периода стоит столбец без подписи с числами - **THEN** он остаётся колонкой с `COLUMN_WITHOUT_DATE` уровня `error`, `NUMBERS_OUTSIDE_TABLE` нет #### Scenario: Подписанных колонок нет - **WHEN** ни у одного столбца значений нет подписи периода - **THEN** правило «числа справа от таблицы» не применяется, столбцы с числами — колонки с `COLUMN_WITHOUT_DATE` #### Scenario: Обрывок подписи периода (Калтан, AURORA-1735) - **WHEN** у ОФР подписи «За 1 полугодие 2026 года» (столбец 4), «31» (столбец 5), «а I полугодие 2025 года» (столбец 6); в столбцах 4 и 6 суммы в десятке строк с кодом, в столбце 5 — одно число «$301 .» в строке 2400 и тире - **THEN** колонок значений две (4 и 6), `COLUMN_WITHOUT_DATE` нет; `NUMBERS_OUTSIDE_TABLE` уровня `info` с адресом ячейки строки 2400 столбца 5 и текстом «числа вне колонок формы не учитываются: row:21»; число 301 в значения не попадает #### Scenario: Неразобранная подпись над двумя числами - **WHEN** между двумя колонками периодов стоит столбец с подписью «Прим.» (не из словаря исключений), числа в нём — в двух строках с кодом - **THEN** столбец остаётся колонкой с `COLUMN_WITHOUT_DATE` уровня `error` #### Scenario: Значение, сдвинутое в обрывок подписи - **WHEN** у столбца «31» между колонками периодов одно число «55» в строке 2460, а ячейка колонки периода 2025 этой строки пуста - **THEN** столбец остаётся колонкой с `COLUMN_WITHOUT_DATE` уровня `error`, документ `needs_check` — значение не теряется молча #### Scenario: Колонка «Изменение» - **WHEN** правее периодов колонка «Изменение, %» - **THEN** колонка пропускается, предупреждение `COLUMN_SKIPPED` уровня `info`