Архив change statement-form-split (AURORA-1734/1735, #214)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Andrey Epifancev 2026-11-07 10:21:12 +04:00
parent e5274285ea
commit 97c8dc05a3
5 changed files with 75 additions and 2 deletions

View File

@ -168,7 +168,8 @@ PR в `arm-statements` (байтовая копия yaml) → `sync-statements`
### Requirement: Шаг 1 — многостраничная форма
Требование относится к адаптеру Excel: у OCR каждая таблица ИС — отдельный кандидат, склейки таблиц нет (повторная
шапка внутри таблицы ИС — сегмент шага 2). У Excel одна форма SHALL быть одной сеткой на выходе шага 1. Строки продолжения дописываются в конец сетки по маркеру
шапка внутри таблицы ИС — сегмент шага 2; смена формы внутри таблицы — разрез кандидата, требование «Шаг 2 — разрез
кандидата по смене формы»). У Excel одна форма SHALL быть одной сеткой на выходе шага 1. Строки продолжения дописываются в конец сетки по маркеру
«Форма … с.2» или по позиции листа; граница листа записывается в `segment_breaks`. Тип из маркера продолжения SHALL
сверяться с `type_hint` формы: расхождение — отдельный кандидат и предупреждение `CONTINUATION_TYPE_CONFLICT`
(`error`). Лист без маркера и без шапки присоединяется, только если его коды в диапазоне открытой формы и, при трёх и
@ -326,7 +327,19 @@ tabular-parser и шаблоны `dates.py` scan-gateway; строки `test_dat
У сегмента без подписанных колонок (лист-продолжение без шапки) столбцы без текста подписи сверх числа колонок документа
SHALL считаться числами правее таблицы. Подписанный столбец без чисел в строках с кодами границей таблицы не служит.
Адрес ячейки — `<лист>!<A1>` у Excel и `row:<n>` у OCR. Без подписанных колонок, а также для столбца без подписи между
подписанными и для столбца с любым текстом подписи, не разобранным как период или исключение, поведение прежнее (`COLUMN_WITHOUT_DATE`).
подписанными и для столбца с любым текстом подписи, не разобранным как период или исключение, поведение прежнее (`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, иначе самая поздняя абсолютная дата колонок с
@ -367,6 +380,24 @@ SHALL считаться числами правее таблицы. Подпи
- **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`
@ -943,3 +974,45 @@ value_needs_review {key → bool}, detail_parents {key → parent_key | null}, s
#### Scenario: Смена кода меняет вариант
- **WHEN** у упрощённого по структуре ББ (есть 1600, нет 1100 и 1200) аналитик назначает строке код 1100
- **THEN** точка входа возвращает `form_variant = full`, нарушения считаются полным набором
### 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 = "<locator исходного>;form:<k>"`, `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:<json>;table:0"`, колонки ББ; ОФР — строки 41–60,
`locator = "file:<json>;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** разреза нет, сегмент остаётся в части ББ (как раньше)