diff --git a/openspec/changes/unified-statement-normalization/design.md b/openspec/changes/unified-statement-normalization/design.md index a02c59f..a2f34f7 100644 --- a/openspec/changes/unified-statement-normalization/design.md +++ b/openspec/changes/unified-statement-normalization/design.md @@ -1,7 +1,7 @@ # Единый алгоритм нормализации ББ/ОФР (scan-gateway и tabular-parser) — дизайн Issue: arm-ka-docs#116. Статус: черновик; текущая цепочка описана, целевая картина по шагам 1–9 — предложение, ждёт -решений владельца Р-1…Р-6 (brainstorming 17.10.2026). +решений владельца Р-1…Р-3, Р-5…Р-7; Р-4 решено (brainstorming 17.10.2026). ## Цель @@ -317,7 +317,7 @@ sheet:,`. `DISCLOSURE` и форма без колонки кодов — Каждый шаг ниже: **вход = выход предыдущего шага**, единый выход для обоих сервисов, таблица «различие → к чему приводим → что меняется в scan-gateway → что меняется в tabular-parser». Решения владельца помечены датой; остальное — -предложение (17.10.2026), решения, которые нужны от владельца, собраны в конце (Р-1…Р-6). Имена полей — рабочие, +предложение (17.10.2026), решения, которые нужны от владельца, собраны в конце (Р-1…Р-7). Имена полей — рабочие, уточняются при написании спеки. Принципы, из которых выведены решения по строкам: @@ -327,8 +327,9 @@ sheet:,`. `DISCLOSURE` и форма без колонки кодов — 2. **Ничего не выбрасывать молча.** Строка, колонка или ячейка, которую не удалось истолковать, идёт дальше со статусом, а не превращается в счётчик; пользователь её видит и исправляет. 3. **Качество — структурно и с привязкой к месту:** уверенность ячейки (от источника), `is_recognized` (разбор числа), - статус строки, нарушения §7 по ячейкам, предупреждения с уровнем и привязкой. Статус документа сервис выводит из - них сам; ядро не пересчитывает. + статус строки, нарушения §7 по ячейкам, предупреждения с уровнем и привязкой. Уверенность, `is_recognized` и §7 + сервис сводит в один признак ячейки `needs_review` (шаг 8) и из него, статусов строк и предупреждений выводит + статус документа; ядро при загрузке ничего не пересчитывает, после правок аналитика считает тем же общим кодом. 4. **Один словарь — одно место:** строки форм, проверки §7, периоды, заголовки форм, диапазоны кодов. 5. **Очистка OCR (§11.1) — не отдельный шаг, а первая стадия типовых разборов текста** в общем коде, там, где уже известен смысл ячейки: ИНН и дата шапки (шаг 1), подписи колонок (шаг 2), код строки и разлепление склеенных @@ -425,10 +426,10 @@ sheet:,`. `DISCLOSURE` и форма без колонки кодов — - **2. Склейка двух строк в одну** — одно правило: ячейка кода содержит N кодов через перевод строки и ячейки значений — N частей → N строк (OQ-156); у Excel не срабатывает - scan-gateway: как сейчас, через общее правило - tabular-parser: правило общее, no-op -- **3. Повтор кода** — **решение владельца (17.10.2026):** повтор кода — законный случай (один код, разные наименования), не ошибка и не предупреждение. Все строки сохраняются как есть со статусом `ok`; текстовое предупреждение и `checks.duplicate_line_codes` уходят. Для проверок §7, подтверждения и всего, что идёт после, значения одного кода идут суммой (см. «Что уходит из ядра»; договорённость ДБД в AURORA-1431). Неверно прочитанный код, случайно совпавший с существующим, ловит §7 через сумму +- **3. Повтор кода** — **решение владельца (17.10.2026):** повтор кода — законный случай (один код, разные наименования), не ошибка и не предупреждение. Все строки сохраняются как есть со статусом `ok`; текстовое предупреждение и `checks.duplicate_line_codes` уходят. Для проверок §7, подтверждения и всего, что идёт после, значения одного кода идут суммой (см. «Что уходит из ядра»; договорённость ДБД в AURORA-1431; закрывает AURORA-1406). Неверно прочитанный код, случайно совпавший с существующим, ловит §7 через сумму - scan-gateway: «оставлена первая» → все строки - tabular-parser: `duplicates` уходит -- **4. Отброшенная строка** — строки не отбрасываются: `unreadable` / `missing` идут дальше со статусом (AURORA-1634, 1462, 1406); ядро не достраивает вместо них пустую шаблонную строку молча +- **4. Отброшенная строка** — строки не отбрасываются: `unreadable` / `missing` идут дальше со статусом (AURORA-1634, 1462); ядро не достраивает вместо них пустую шаблонную строку молча - scan-gateway: счётчики → статусы - tabular-parser: счётчики → статусы @@ -439,10 +440,10 @@ sheet:,`. `DISCLOSURE` и форма без колонки кодов — **Единый выход:** `StatementValue {column_index, amount, string_value, confidence, is_recognized}` — как в контракте, с одним смыслом полей у обоих. -- **1. Предобработка текста** — у ячейки после шага 1 либо есть `value`, либо только `text`. `value` есть → `amount = value` (2 знака), `is_recognized = true`. `value` нет → один `parse_amount(text)` для обоих: двойники, разделители тысяч, скобки → минус, прочерк → `null`; Excel-ячейки «число текстом» идут тем же путём +- **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` — отдельно, от источника; экран подсвечивает по любому из двух (как сейчас) +- **2. Смысл `is_recognized`** — один: текст разобран как число без выброшенных и исправленных символов; `confidence` — отдельно, от источника; оба признака сервис сводит в `needs_review` (шаг 8, «Подтверждение проверкой»), экран подсвечивает по нему - scan-gateway: отвязывает от `confidence` - tabular-parser: как сейчас - **3. «42831 1»** — в `parse_amount` пробел допустим только как разделитель тысяч (группы ровно по три цифры); иначе `amount` — лучшая догадка (цифры подряд), `is_recognized = false`, `string_value` — сырой текст (AURORA-1511, 1519) @@ -464,7 +465,7 @@ line_name_confidence, values}`: `line_code` — разобранный код (4 `line_code_text` — сырой текст ячейки кода; `line_status` = `code_status` шага 3 (`ok` | `unreadable` | `missing`). Несколько строк с одним кодом — обычные строки `ok`, порядок как в файле. -- **1. Источник наименования** — одно правило: наименование — из файла, с уверенностью источника; справочник наименование не подменяет. Справочное имя по коду и редакции добавляет ядро из своего (единственного) словаря, экран показывает справочное, файловое — рядом (Р-3; AURORA-1406, 1522) +- **1. Источник наименования** — одно правило: наименование — из файла, с уверенностью источника; справочник наименование не подменяет. Справочное имя по коду и редакции добавляет ядро из единственного словаря строк (общий код, п. 3), экран показывает справочное, файловое — рядом (Р-3; AURORA-1406, 1522) - scan-gateway: имя из `statement_lines.yaml` → имя из OCR - tabular-parser: как сейчас - **2. Редакция (66н / ФСБУ 4)** — в сервисах не определяется; редакция — свойство словаря строк (по дате отчёта), используется в шаге 7 (проверка «код не из редакции формы») и в ядре при достройке шаблонных строк @@ -516,10 +517,10 @@ line_name_confidence, values}`: `line_code` — разобранный код (4 статус конверта — агрегат. Предупреждение — структура `{code, severity: error | info, message, line_ref?, column_index?}`, а не строка. -- **1. Критерий `done`** — один критерий из самого результата: раскладка найдена ∧ (организация или ИНН) ∧ дата отчёта ∧ все строки `ok` ∧ нет значений `needs_review` (см. «Подтверждение проверкой» ниже) ∧ жёсткие проверки §7 сошлись ∧ нет предупреждений уровня `error`. Excel выполняет условия уверенности автоматически +- **1. Критерий `done`** — один критерий из самого результата: раскладка найдена ∧ (организация или ИНН) ∧ дата отчёта ∧ все строки `ok` ∧ нет значений `needs_review` (см. «Подтверждение проверкой» ниже) ∧ жёсткие проверки §7 сошлись ∧ нет предупреждений уровня `error`. `DISCLOSURE` — `done` без условий о раскладке, строках и проверках (как сейчас). Excel выполняет условия уверенности автоматически - scan-gateway: «ничего не выброшено и уверенность 100» → общий критерий - tabular-parser: `_is_ok` → общий критерий; добавляются строки и значения -- **2. Влияние предупреждений** — по уровню: `error` → `needs_check`, `info` — не влияет; у обоих одинаково +- **2. Влияние предупреждений** — по уровню: `error` → `needs_check`, `info` — не влияет; у обоих одинаково; уровень каждого предупреждения задаётся в спеке (например, «строка заголовка не найдена», «колонка без даты», «тип по кодам противоречит заголовку» — `error`; «подпись из словаря исключений пропущена», «дробные при тыс. руб.» — `info`) - scan-gateway: «любое → `needs_check`» уходит - tabular-parser: предупреждения начинают влиять - **3. Статус документа** — в контракте: `document.status`; конверт `done` ⇔ все документы `done` ∧ нет предупреждений файла уровня `error`; иначе `needs_check`; документов нет — `error` (как сейчас) @@ -535,8 +536,9 @@ column_index?}`, а не строка. **Подтверждение проверкой (вопрос владельца 17.10.2026).** Проверка §7 — арбитр, уверенность — подсказка там, где проверки нет. По каждому значению сервис считает `needs_review`: -- `true`, если `is_recognized = false` или `confidence` ниже порога (Р-5) **и** значение не покрыто сошедшимся жёстким - соотношением §7 в своей колонке (не входит ни в одно, соотношение не сошлось или оно мягкое); +- `true`, если `confidence` ниже порога (Р-5) **и** значение не покрыто сошедшимся жёстким соотношением §7 в своей + колонке (не входит ни в одно, соотношение не сошлось или оно мягкое); для `is_recognized = false` — то же правило, + если Р-7 = да, иначе всегда `true`; - `true` у всех ячеек соотношения в колонке, где оно не сошлось, независимо от уверенности; - иначе `false`: значение подтверждено проверкой, не подсвечивается и статус не трогает; сырой текст и уверенность остаются в подсказке ячейки. @@ -550,8 +552,9 @@ column_index?}`, а не строка. ### Шаг 9 — Результат Одинаково, как сейчас; контракт `recognition-result.schema.json` → **v1.3**: `line_status`, `line_code` nullable, -`line_code_text`; `document.status`; `document.warnings[]` и `metrics.warnings[]` структурные; `checks` обязателен для -`BALANCE` / `INCOME` у обоих конвейеров, нарушения с кодами слагаемых; `line_warnings`. +`line_code_text`; `needs_review` у значения; `document.status`; `document.warnings[]` и `metrics.warnings[]` +структурные (уровень, привязка); `checks` обязателен для `BALANCE` / `INCOME` со строками у обоих конвейеров (у +документа без строк отсутствует), нарушения с кодами слагаемых, без `duplicate_line_codes`; `line_warnings`. ### Что уходит из ядра @@ -582,8 +585,9 @@ column_index?}`, а не строка. - **Р-4** (шаг 3, п. 3) — **решено владельцем 17.10.2026:** повторы кодов законны; все строки сохраняются и показываются, для проверок и всех операций после подтверждения или автоподтверждения — суммой по коду. Следствие: снять уникальность `(meta_id, line_code)` в DBML. -- **Р-5** (шаг 8, п. 1) — порог уверенности для `done` (= решение № 7 по спринту 2, AURORA-1519 / 1524); предложение — - 100, как сейчас. +- **Р-5** (шаг 8, «Подтверждение проверкой») — порог уверенности, ниже которого значение попадает в `needs_review`, + если не подтверждено проверкой §7 (= решение № 7 по спринту 2, AURORA-1519 / 1524); предложение — 100, как сейчас: + ложных подсветок станет меньше за счёт подтверждения проверкой, а не за счёт порога. - **Р-6** (шаг 2, п. 6) — неизвестная числовая колонка без периода: оставлять без даты с предупреждением (`needs_check`) или пропускать, как парсер сейчас. - **Р-7** (шаг 8, «Подтверждение проверкой») — снимать ли подсветку сошедшимся соотношением §7 и с `is_recognized =