From bdf4c025ead4d498214f7a58475388c76ce131ec Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Sat, 17 Oct 2026 15:11:47 +0400 Subject: [PATCH] =?UTF-8?q?docs(#116):=20=D1=82=D0=B0=D0=B1=D0=BB=D0=B8?= =?UTF-8?q?=D1=86=D1=8B=20=D1=86=D0=B5=D0=BB=D0=B5=D0=B2=D0=BE=D0=B9=20?= =?UTF-8?q?=D0=BA=D0=B0=D1=80=D1=82=D0=B8=D0=BD=D1=8B=20=E2=86=92=20=D1=81?= =?UTF-8?q?=D0=BF=D0=B8=D1=81=D0=BA=D0=B8=20(=D0=BF=D1=8F=D1=82=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=BE=D0=BD=D0=BE=D1=87=D0=BD=D1=8B=D0=B5=20?= =?UTF-8?q?=D1=82=D0=B0=D0=B1=D0=BB=D0=B8=D1=86=D1=8B=20=D0=BD=D0=B5=20?= =?UTF-8?q?=D0=BF=D0=BE=D0=BC=D0=B5=D1=89=D0=B0=D0=BB=D0=B8=D1=81=D1=8C=20?= =?UTF-8?q?=D0=B2=20glow)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5.1 --- .../unified-statement-normalization/design.md | 156 ++++++++++++------ 1 file changed, 105 insertions(+), 51 deletions(-) diff --git a/openspec/changes/unified-statement-normalization/design.md b/openspec/changes/unified-statement-normalization/design.md index 28dadd3..3012ae7 100644 --- a/openspec/changes/unified-statement-normalization/design.md +++ b/openspec/changes/unified-statement-normalization/design.md @@ -354,15 +354,27 @@ sheet:,`. `DISCLOSURE` и форма без колонки кодов — знает; у OCR — `null`), `confidence` 0–100 (у Excel — 100), `ref` (позиция в источнике для сообщений: `row_index` / `column_index` у OCR, адрес A1 у Excel). -| № | Различие | Приводим к | scan-gateway | tabular-parser | -|---|---|---|---|---| -| 1 | Кто размечает | остаётся, фиксируется полем `source` | — | — | -| 2 | Единица разметки | файл → список кандидатов; шаги 2–8 работают только с кандидатом (Р-1 — форма общего кода) | адаптер «таблица json → кандидат» | адаптер «блок формы на листе → кандидат» | -| 3 | Шапка | у каждого кандидата своя; поля = поля `header` контракта, каждое — `Cell`; разбор текста поля (ИНН → цифры, дата → `date`, единицы → словарь) — общий код по `text` | шапку json копирует в каждую свою таблицу | добавляет сырой текст и уверенность 100 | -| 4 | Тип формы | одно правило: ОКУД → заголовок по одному словарю → `null`; окончательно — по кодам строк в шаге 6, у обоих | `label` таблицы становится заголовком; `statement_labels.yaml` уходит в общий словарь | добавляет сверку по кодам (шаг 6) | -| 5 | Уверенность | поле `Cell.confidence`; Excel всегда 100 | — | добавляет поле | -| 6 | Многостраничная форма | одна форма = одна сетка на выходе шага 1; строки продолжения дописываются в конец сетки по маркеру «Форма … с.2» или по позиции листа; проверка «число колонок сумм совпало» из склейки уходит (повторная шапка внутри сетки — правило шага 2, общее для обоих: у OCR она и так внутри таблицы); тип из маркера сверяется с `type_hint`, расхождение — отдельный кандидат + предупреждение | — | склейка переезжает до раскладки; `join_blocker` остаётся только по типу | -| 7 | Ячейка | один формат `Cell`; Excel: `text` — как показывает числовой формат, `value` — число по формату (tp#42; формат не разобран — сырое значение + предупреждение); OCR: `text` сырой, `value` `null`, `confidence` от ИС | обёртка над `value` + `confidence` ИС | отображаемый текст и значение считаются здесь, а не в шаге 4 | +- **1. Кто размечает** — остаётся, фиксируется полем `source` + - scan-gateway: без изменений + - tabular-parser: без изменений +- **2. Единица разметки** — файл → список кандидатов; шаги 2–8 работают только с кандидатом (Р-1 — форма общего кода) + - scan-gateway: адаптер «таблица json → кандидат» + - tabular-parser: адаптер «блок формы на листе → кандидат» +- **3. Шапка** — у каждого кандидата своя; поля = поля `header` контракта, каждое — `Cell`; разбор текста поля (ИНН → цифры, дата → `date`, единицы → словарь) — общий код по `text` + - scan-gateway: шапку json копирует в каждую свою таблицу + - tabular-parser: добавляет сырой текст и уверенность 100 +- **4. Тип формы** — одно правило: ОКУД → заголовок по одному словарю → `null`; окончательно — по кодам строк в шаге 6, у обоих + - scan-gateway: `label` таблицы становится заголовком; `statement_labels.yaml` уходит в общий словарь + - tabular-parser: добавляет сверку по кодам (шаг 6) +- **5. Уверенность** — поле `Cell.confidence`; Excel всегда 100 + - scan-gateway: без изменений + - tabular-parser: добавляет поле +- **6. Многостраничная форма** — одна форма = одна сетка на выходе шага 1; строки продолжения дописываются в конец сетки по маркеру «Форма … с.2» или по позиции листа; проверка «число колонок сумм совпало» из склейки уходит (повторная шапка внутри сетки — правило шага 2, общее для обоих: у OCR она и так внутри таблицы); тип из маркера сверяется с `type_hint`, расхождение — отдельный кандидат + предупреждение + - scan-gateway: без изменений + - tabular-parser: склейка переезжает до раскладки; `join_blocker` остаётся только по типу +- **7. Ячейка** — один формат `Cell`; Excel: `text` — как показывает числовой формат, `value` — число по формату (tp#42; формат не разобран — сырое значение + предупреждение); OCR: `text` сырой, `value` `null`, `confidence` от ИС + - scan-gateway: обёртка над `value` + `confidence` ИС + - tabular-parser: отображаемый текст и значение считаются здесь, а не в шаге 4 ### Шаг 2 — Раскладка таблицы @@ -372,14 +384,24 @@ sheet:,`. `DISCLOSURE` и форма без колонки кодов — роль каждой строки сетки (`header` | `numbering` | `data`), сегменты (участки сетки между повторными шапками, у каждого своя раскладка колонок). -| № | Различие | Приводим к | scan-gateway | tabular-parser | -|---|---|---|---|---| -| 1 | Что считается кодом при поиске столбца | одно правило: 4–5 цифр (допускается `.0`), первые четыре — в диапазонах словаря кодов всех форм (`type_hint` неизвестен — объединение диапазонов); столбец с наибольшим числом кодов, кодов ≥ 3, доля среди непустых ≥ порога; нет — «колонка кодов не найдена» | `int(text)` → правило словаря (закрывает AURORA-1517 вместе с п. 5) | диапазон формы → объединение; `relaxed` для одиноких продолжений не нужен (они склеены в шаге 1) | -| 2 | Повторная шапка внутри таблицы | одно правило: строка внутри сетки с признаком шапки («Код» / та же сигнатура) открывает новый сегмент; у сегмента своя раскладка (позиции колонок могут сдвинуться); колонки значений сегментов сопоставляются по порядку; число не совпало — предупреждение, значения по позиции | как сейчас, через общее правило | добавляет: бывший «лист-продолжение со своей раскладкой» становится сегментом | -| 3 | Дата колонки | один словарь периодов (`statement_periods.yaml`) → `period_end`; перед разбором — очистка двойников OCR (общий шаг, у Excel не срабатывает) | `parse_date` «размером с дату» → словарь | добавляет очистку двойников (no-op) | -| 4 | Без шапки | одно правило: строка заголовка не найдена → колонки значений = столбцы правее кода, где в строках с кодами стоят числа; `label` пустая, `period_end` `null`; одно предупреждение «строка заголовка не найдена» | синтез шапки уходит (он эквивалентен) | добавляет предупреждение | -| 5 | Строка нумерации «1 2 3 4 5» | одно правило `is_numbering_row` → роль `numbering`, в данные не попадает | добавляет (AURORA-1517) | как сейчас | -| 6 | Подпись без периода | одно правило: подпись — период по словарю → колонка с датой; подпись из словаря исключений («Изменение», «Отклонение», «%», «Темп», «Доля») → пропуск с предупреждением info; иначе, если в столбце есть числа в строках с кодами — колонка без даты + предупреждение (Р-6); иначе пропуск | добавляет словарь исключений | перестаёт молча пропускать неизвестные числовые колонки | +- **1. Что считается кодом при поиске столбца** — одно правило: 4–5 цифр (допускается `.0`), первые четыре — в диапазонах словаря кодов всех форм (`type_hint` неизвестен — объединение диапазонов); столбец с наибольшим числом кодов, кодов ≥ 3, доля среди непустых ≥ порога; нет — «колонка кодов не найдена» + - scan-gateway: `int(text)` → правило словаря (закрывает AURORA-1517 вместе с п. 5) + - tabular-parser: диапазон формы → объединение; `relaxed` для одиноких продолжений не нужен (они склеены в шаге 1) +- **2. Повторная шапка внутри таблицы** — одно правило: строка внутри сетки с признаком шапки («Код» / та же сигнатура) открывает новый сегмент; у сегмента своя раскладка (позиции колонок могут сдвинуться); колонки значений сегментов сопоставляются по порядку; число не совпало — предупреждение, значения по позиции + - scan-gateway: как сейчас, через общее правило + - tabular-parser: добавляет: бывший «лист-продолжение со своей раскладкой» становится сегментом +- **3. Дата колонки** — один словарь периодов (`statement_periods.yaml`) → `period_end`; перед разбором — очистка двойников OCR (общий шаг, у Excel не срабатывает) + - scan-gateway: `parse_date` «размером с дату» → словарь + - tabular-parser: добавляет очистку двойников (no-op) +- **4. Без шапки** — одно правило: строка заголовка не найдена → колонки значений = столбцы правее кода, где в строках с кодами стоят числа; `label` пустая, `period_end` `null`; одно предупреждение «строка заголовка не найдена» + - scan-gateway: синтез шапки уходит (он эквивалентен) + - tabular-parser: добавляет предупреждение +- **5. Строка нумерации «1 2 3 4 5»** — одно правило `is_numbering_row` → роль `numbering`, в данные не попадает + - scan-gateway: добавляет (AURORA-1517) + - tabular-parser: как сейчас +- **6. Подпись без периода** — одно правило: подпись — период по словарю → колонка с датой; подпись из словаря исключений («Изменение», «Отклонение», «%», «Темп», «Доля») → пропуск с предупреждением info; иначе, если в столбце есть числа в строках с кодами — колонка без даты + предупреждение (Р-6); иначе пропуск + - scan-gateway: добавляет словарь исключений + - tabular-parser: перестаёт молча пропускать неизвестные числовые колонки ### Шаг 3 — Отбор строк данных @@ -390,12 +412,18 @@ sheet:,`. `DISCLOSURE` и форма без колонки кодов — есть | `duplicate` — код уже встречался), ячейка наименования, ячейки значений по колонкам раскладки. Строка без кода и без чисел (заголовок раздела «АКТИВ», «в том числе:») — не строка. Счётчиков отброшенных нет — есть статусы. -| № | Различие | Приводим к | scan-gateway | tabular-parser | -|---|---|---|---|---| -| 1 | Код-мусор | статус `unreadable`, строка сохраняется с сырым текстом кода (AURORA-1462: «ИЧ,250» видна, аналитик назначает 1250) | счётчик → статус | отличает «нет кода» от «не разобран» | -| 2 | Склейка двух строк в одну | одно правило: ячейка кода содержит N кодов через перевод строки и ячейки значений — N частей → N строк (OQ-156); у Excel не срабатывает | как сейчас, через общее правило | правило общее, no-op | -| 3 | Повтор кода | статус `duplicate` у второй и последующих строк; строки сохраняются; текстовое предупреждение и `checks.duplicate_line_codes` уходят; что показывать и как считать — ядро (Р-4) | «оставлена первая» → обе строки | `duplicates` → статус | -| 4 | Отброшенная строка | строки не отбрасываются: `unreadable` / `missing` / `duplicate` идут дальше со статусом (AURORA-1634, 1462, 1406); ядро не достраивает вместо них пустую шаблонную строку молча | счётчики → статусы | счётчики → статусы | +- **1. Код-мусор** — статус `unreadable`, строка сохраняется с сырым текстом кода (AURORA-1462: «ИЧ,250» видна, аналитик назначает 1250) + - scan-gateway: счётчик → статус + - tabular-parser: отличает «нет кода» от «не разобран» +- **2. Склейка двух строк в одну** — одно правило: ячейка кода содержит N кодов через перевод строки и ячейки значений — N частей → N строк (OQ-156); у Excel не срабатывает + - scan-gateway: как сейчас, через общее правило + - tabular-parser: правило общее, no-op +- **3. Повтор кода** — статус `duplicate` у второй и последующих строк; строки сохраняются; текстовое предупреждение и `checks.duplicate_line_codes` уходят; что показывать и как считать — ядро (Р-4) + - scan-gateway: «оставлена первая» → обе строки + - tabular-parser: `duplicates` → статус +- **4. Отброшенная строка** — строки не отбрасываются: `unreadable` / `missing` / `duplicate` идут дальше со статусом (AURORA-1634, 1462, 1406); ядро не достраивает вместо них пустую шаблонную строку молча + - scan-gateway: счётчики → статусы + - tabular-parser: счётчики → статусы ### Шаг 4 — Значения ячеек @@ -404,13 +432,21 @@ sheet:,`. `DISCLOSURE` и форма без колонки кодов — **Единый выход:** `StatementValue {column_index, amount, string_value, confidence, is_recognized}` — как в контракте, с одним смыслом полей у обоих. -| № | Различие | Приводим к | scan-gateway | tabular-parser | -|---|---|---|---|---| -| 1 | Предобработка текста | у ячейки после шага 1 либо есть `value`, либо только `text`. `value` есть → `amount = value` (2 знака), `is_recognized = true`. `value` нет → один `parse_amount(text)` для обоих: двойники, разделители тысяч, скобки → минус, прочерк → `null`; Excel-ячейки «число текстом» идут тем же путём | `parse_amount` переезжает в общий код | `to_money` заменяется общим `parse_amount` | -| 2 | Смысл `is_recognized` | один: текст разобран как число без выброшенных и исправленных символов; `confidence` — отдельно, от источника; экран подсвечивает по любому из двух (как сейчас) | отвязывает от `confidence` | как сейчас | -| 3 | «42831 1» | в `parse_amount` пробел допустим только как разделитель тысяч (группы ровно по три цифры); иначе `amount` — лучшая догадка (цифры подряд), `is_recognized = false`, `string_value` — сырой текст (AURORA-1511, 1519) | меняется через общий код | то же правило, у Excel почти не срабатывает | -| 4 | Единицы | одно правило: единицы «тыс. руб.» и дробных ≥ 10 % → предупреждение документа | добавляет | как сейчас | -| 5 | Пустая ячейка | одно правило: позиции нет, текст пуст или прочерк → `amount = null`, `string_value = null`, `is_recognized = true`, `confidence` ячейки (нет ячейки — 100). Потерянную OCR-ячейку ловят проверки §7 (Р-2) | «ячейки нет → 0, не распознано» уходит | как сейчас | +- **1. Предобработка текста** — у ячейки после шага 1 либо есть `value`, либо только `text`. `value` есть → `amount = value` (2 знака), `is_recognized = true`. `value` нет → один `parse_amount(text)` для обоих: двойники, разделители тысяч, скобки → минус, прочерк → `null`; Excel-ячейки «число текстом» идут тем же путём + - scan-gateway: `parse_amount` переезжает в общий код + - tabular-parser: `to_money` заменяется общим `parse_amount` +- **2. Смысл `is_recognized`** — один: текст разобран как число без выброшенных и исправленных символов; `confidence` — отдельно, от источника; экран подсвечивает по любому из двух (как сейчас) + - scan-gateway: отвязывает от `confidence` + - tabular-parser: как сейчас +- **3. «42831 1»** — в `parse_amount` пробел допустим только как разделитель тысяч (группы ровно по три цифры); иначе `amount` — лучшая догадка (цифры подряд), `is_recognized = false`, `string_value` — сырой текст (AURORA-1511, 1519) + - scan-gateway: меняется через общий код + - tabular-parser: то же правило, у Excel почти не срабатывает +- **4. Единицы** — одно правило: единицы «тыс. руб.» и дробных ≥ 10 % → предупреждение документа + - scan-gateway: добавляет + - tabular-parser: как сейчас +- **5. Пустая ячейка** — одно правило: позиции нет, текст пуст или прочерк → `amount = null`, `string_value = null`, `is_recognized = true`, `confidence` ячейки (нет ячейки — 100). Потерянную OCR-ячейку ловят проверки §7 (Р-2) + - scan-gateway: «ячейки нет → 0, не распознано» уходит + - tabular-parser: как сейчас ### Шаг 5 — Код, наименование, редакция @@ -420,12 +456,18 @@ sheet:,`. `DISCLOSURE` и форма без колонки кодов — line_name_confidence, values}`: `line_code` — разобранный код (4–5 цифр) или `null` при `unreadable` / `missing`; `line_code_text` — сырой текст ячейки кода; `line_status` = `code_status` шага 3. -| № | Различие | Приводим к | scan-gateway | tabular-parser | -|---|---|---|---|---| -| 1 | Источник наименования | одно правило: наименование — из файла, с уверенностью источника; справочник наименование не подменяет. Справочное имя по коду и редакции добавляет ядро из своего (единственного) словаря, экран показывает справочное, файловое — рядом (Р-3; AURORA-1406, 1522) | имя из `statement_lines.yaml` → имя из OCR | как сейчас | -| 2 | Редакция (66н / ФСБУ 4) | в сервисах не определяется; редакция — свойство словаря строк (по дате отчёта), используется в шаге 7 (проверка «код не из редакции формы») и в ядре при достройке шаблонных строк | `_choose_edition` уходит | — | -| 3 | Справочник строк | один словарь строк (в общем коде, Р-1); копия в шлюзе удаляется | удаляет `api/rules/statement_lines.yaml` | — | -| 4 | Уверенность кода | от источника (ИС / 100), без срезания до 99 по справочнику; «не в справочнике» — предупреждение к строке в шаге 7 | убирает `min(conf, 99)` | как сейчас | +- **1. Источник наименования** — одно правило: наименование — из файла, с уверенностью источника; справочник наименование не подменяет. Справочное имя по коду и редакции добавляет ядро из своего (единственного) словаря, экран показывает справочное, файловое — рядом (Р-3; AURORA-1406, 1522) + - scan-gateway: имя из `statement_lines.yaml` → имя из OCR + - tabular-parser: как сейчас +- **2. Редакция (66н / ФСБУ 4)** — в сервисах не определяется; редакция — свойство словаря строк (по дате отчёта), используется в шаге 7 (проверка «код не из редакции формы») и в ядре при достройке шаблонных строк + - scan-gateway: `_choose_edition` уходит + - tabular-parser: без изменений +- **3. Справочник строк** — один словарь строк (в общем коде, Р-1); копия в шлюзе удаляется + - scan-gateway: удаляет `api/rules/statement_lines.yaml` + - tabular-parser: без изменений +- **4. Уверенность кода** — от источника (ИС / 100), без срезания до 99 по справочнику; «не в справочнике» — предупреждение к строке в шаге 7 + - scan-gateway: убирает `min(conf, 99)` + - tabular-parser: как сейчас ### Шаг 6 — Сборка документа @@ -433,11 +475,15 @@ line_name_confidence, values}`: `line_code` — разобранный код (4 **Единый выход:** `StatementDocument {document_type, locator, header, columns, lines}` без `checks`. -| № | Различие | Приводим к | scan-gateway | tabular-parser | -|---|---|---|---|---| -| 1 | Тип формы | одно правило: большинство кодов 1xxx → `BALANCE`, 2xxx → `INCOME`; совпало с `type_hint` или `type_hint` `null` — тип по кодам; противоречит — тип по кодам + предупреждение; кодов нет — `type_hint`; без колонки кодов: `type_hint` есть → документ без строк (`DISCLOSURE` — `done`, иначе `needs_check`), `type_hint` нет → не документ, предупреждение info | как сейчас, через общее правило | добавляет сверку с кодами | -| 2 | Склейка страниц | решена в шаге 1, различия нет | — | — | -| 3 | `locator` | одна грамматика «пары `ключ:значение` через `;`», набор ключей по источнику: `json:<имя>;table:` / `sheet:[,]` | — | — | +- **1. Тип формы** — одно правило: большинство кодов 1xxx → `BALANCE`, 2xxx → `INCOME`; совпало с `type_hint` или `type_hint` `null` — тип по кодам; противоречит — тип по кодам + предупреждение; кодов нет — `type_hint`; без колонки кодов: `type_hint` есть → документ без строк (`DISCLOSURE` — `done`, иначе `needs_check`), `type_hint` нет → не документ, предупреждение info + - scan-gateway: как сейчас, через общее правило + - tabular-parser: добавляет сверку с кодами +- **2. Склейка страниц** — решена в шаге 1, различия нет + - scan-gateway: без изменений + - tabular-parser: без изменений +- **3. `locator`** — одна грамматика «пары `ключ:значение` через `;`», набор ключей по источнику: `json:<имя>;table:` / `sheet:[,]` + - scan-gateway: без изменений + - tabular-parser: без изменений Шапка документа — поля `header` кандидата как есть (`text` → `*_text`, `value` → поле, `confidence` → `*_confidence`). @@ -449,9 +495,9 @@ line_name_confidence, values}`: `line_code` — разобранный код (4 кодами слагаемых и `column_index`, чтобы экран подсветил ячейки), `line_warnings[]` (код не из редакции формы по дате отчёта, код не в словаре). `duplicate_line_codes` уходит в `line_status`. -| № | Различие | Приводим к | scan-gateway | tabular-parser | -|---|---|---|---|---| -| 1 | Проверки только у парсера; словарь §7 в двух копиях; ядро результат не читает | проверки — общий код с одним словарём `statement_checks.yaml` у обоих сервисов; для OCR нарушение §7 — главный признак неверно прочитанной цифры; ядро читает `checks` из результата и пересчитывает тем же кодом только после правок аналитика (PATCH) | добавляет шаг | `run_statement_checks` → общий код | +- **1. Проверки только у парсера; словарь §7 в двух копиях; ядро результат не читает** — проверки — общий код с одним словарём `statement_checks.yaml` у обоих сервисов; для OCR нарушение §7 — главный признак неверно прочитанной цифры; ядро читает `checks` из результата и пересчитывает тем же кодом только после правок аналитика (PATCH) + - scan-gateway: добавляет шаг + - tabular-parser: `run_statement_checks` → общий код ### Шаг 8 — Статус и предупреждения @@ -461,13 +507,21 @@ line_name_confidence, values}`: `line_code` — разобранный код (4 статус конверта — агрегат. Предупреждение — структура `{code, severity: error | info, message, line_ref?, column_index?}`, а не строка. -| № | Различие | Приводим к | scan-gateway | tabular-parser | -|---|---|---|---|---| -| 1 | Критерий `done` | один критерий из самого результата: раскладка найдена ∧ (организация или ИНН) ∧ дата отчёта ∧ все строки `ok` ∧ все значения `is_recognized` ∧ `confidence` всех ячеек ≥ порога (Р-5) ∧ жёсткие проверки §7 сошлись ∧ нет предупреждений уровня `error`. Excel выполняет условия уверенности автоматически | «ничего не выброшено и уверенность 100» → общий критерий | `_is_ok` → общий критерий; добавляются строки и значения | -| 2 | Влияние предупреждений | по уровню: `error` → `needs_check`, `info` — не влияет; у обоих одинаково | «любое → `needs_check`» уходит | предупреждения начинают влиять | -| 3 | Статус документа | в контракте: `document.status`; конверт `done` ⇔ все документы `done` ∧ нет предупреждений файла уровня `error`; иначе `needs_check`; документов нет — `error` (как сейчас) | добавляет | внутренний `ok` → в контракт | -| 4 | Формат предупреждений | структура (см. выше) вместо строк `: table:: …` / `sheet=<имя> …`; место — в `locator` документа, а не в тексте | меняет | меняет | -| 5 | Привязка | к документу, строке (`line_ref`) или ячейке (`line_ref` + `column_index`); экран показывает у места | меняет | меняет | +- **1. Критерий `done`** — один критерий из самого результата: раскладка найдена ∧ (организация или ИНН) ∧ дата отчёта ∧ все строки `ok` ∧ все значения `is_recognized` ∧ `confidence` всех ячеек ≥ порога (Р-5) ∧ жёсткие проверки §7 сошлись ∧ нет предупреждений уровня `error`. Excel выполняет условия уверенности автоматически + - scan-gateway: «ничего не выброшено и уверенность 100» → общий критерий + - tabular-parser: `_is_ok` → общий критерий; добавляются строки и значения +- **2. Влияние предупреждений** — по уровню: `error` → `needs_check`, `info` — не влияет; у обоих одинаково + - scan-gateway: «любое → `needs_check`» уходит + - tabular-parser: предупреждения начинают влиять +- **3. Статус документа** — в контракте: `document.status`; конверт `done` ⇔ все документы `done` ∧ нет предупреждений файла уровня `error`; иначе `needs_check`; документов нет — `error` (как сейчас) + - scan-gateway: добавляет + - tabular-parser: внутренний `ok` → в контракт +- **4. Формат предупреждений** — структура (см. выше) вместо строк `: table:: …` / `sheet=<имя> …`; место — в `locator` документа, а не в тексте + - scan-gateway: меняет + - tabular-parser: меняет +- **5. Привязка** — к документу, строке (`line_ref`) или ячейке (`line_ref` + `column_index`); экран показывает у места + - scan-gateway: меняет + - tabular-parser: меняет ### Шаг 9 — Результат