docs(#116): §7, §9, §11 и гайды по повторному ревью спеки: detail вне needs_review, очистка форм, мусор в суммах, сегменты, предупреждения файла объектами; OQ-168 (Р-23…Р-26, L-1…L-30)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BJKMLHdxMwSUeYcdkwqX4W
This commit is contained in:
parent
0e680c89e0
commit
429f86760f
@ -547,8 +547,8 @@ variance = |amount_a − amount_b|; variance_pct = variance / min(amount_a, amou
|
|||||||
1350 + 1360 + 1410 + 1450 + 1510 + 1520 + 1550` (жёсткое), `1600 = 1700` (жёсткое); ОФР — `2400 = 2110 − 2120 − 2330 +
|
1350 + 1360 + 1410 + 1450 + 1510 + 1520 + 1550` (жёсткое), `1600 = 1700` (жёсткое); ОФР — `2400 = 2110 − 2120 − 2330 +
|
||||||
2340 − 2350 − 2410` (**мягкое**, как `2400` полного набора: знак 2410 не определён, OQ-12; `abs_terms` те же; решение
|
2340 − 2350 − 2410` (**мягкое**, как `2400` полного набора: знак 2410 не определён, OQ-12; `abs_terms` те же; решение
|
||||||
владельца 18.10.2026, Р-22 / S-5). Оба набора — в одном словаре `statement_checks.yaml`. Без упрощённого набора полный
|
владельца 18.10.2026, Р-22 / S-5). Оба набора — в одном словаре `statement_checks.yaml`. Без упрощённого набора полный
|
||||||
дал бы нарушения «пустой цели» на каждой упрощённой форме. `form_variant = null` (результат 1.x, документ до v5.19) —
|
дал бы нарушения «пустой цели» на каждой упрощённой форме. `form_variant = null` (документ, оставшийся в БД с времён до
|
||||||
полный набор.
|
v5.19, если база не очищена) — полный набор; у результата 1.x вариант определяется по структуре кодов при приёме.
|
||||||
- **Родитель пятизначной подстроки** — ближайшая четырёхзначная строка выше с теми же первыми тремя цифрами, иначе код
|
- **Родитель пятизначной подстроки** — ближайшая четырёхзначная строка выше с теми же первыми тремя цифрами, иначе код
|
||||||
из первых четырёх цифр (12310 под 1230); то же правило ядро отдаёт экрану как `parent_code`.
|
из первых четырёх цифр (12310 под 1230); то же правило ядро отдаёт экрану как `parent_code`.
|
||||||
- **Межформенная** `1370(отчётная дата) − 1370(начало года) = 2400` сервисами не считается — только ядром (ниже).
|
- **Межформенная** `1370(отчётная дата) − 1370(начало года) = 2400` сервисами не считается — только ядром (ниже).
|
||||||
@ -556,10 +556,13 @@ variance = |amount_a − amount_b|; variance_pct = variance / min(amount_a, amou
|
|||||||
**Ядро.** При загрузке результата v2.0 ядро §7 **не пересчитывает**: `checks`, `needs_review` и статус документа
|
**Ядро.** При загрузке результата v2.0 ядро §7 **не пересчитывает**: `checks`, `needs_review` и статус документа
|
||||||
берутся из результата (§9.3, §9.5 п. 2). После правки аналитика (`PATCH …/statement`) ядро пересчитывает §7 копией
|
берутся из результата (§9.3, §9.5 п. 2). После правки аналитика (`PATCH …/statement`) ядро пересчитывает §7 копией
|
||||||
`arm_statements` по текущим суммам (`financial_statement_value.amount` после правок) — с теми же правилами, что в
|
`arm_statements` по текущим суммам (`financial_statement_value.amount` после правок) — с теми же правилами, что в
|
||||||
сервисах. Результат 1.x (окно выкладки) и формы, существовавшие до миграции v5.19, `checks` не несут: ядро **один раз**
|
сервисах. Пересчёт идёт точкой входа пакета для пересчёта по сохранённым данным (спека `statement-normalization`):
|
||||||
считает §7 копией `arm_statements` (`form_variant = null` — полный набор) — при приёме 1.x и в миграции — и пишет
|
она же заново определяет вариант формы и редакцию документа для проверок ядра. Результат 1.x (окно выкладки) `checks`
|
||||||
нарушения в `document_issue` (`source = core`, `relation` / `soft`); условие D-6 «проверки сошлись» для 1.x берётся из
|
не несёт: ядро **один раз при приёме** считает §7 той же точкой входа (вариант — по структуре кодов) и пишет нарушения в
|
||||||
этого расчёта. Экран (`GET …/statement`) и подтверждение (`CHECKS_NOT_PASSED`) читают нарушения **только** из
|
`document_issue` (`source = core`, `relation` / `soft`); условие D-6 «проверки сошлись» для 1.x берётся из этого
|
||||||
|
расчёта. Формы, существовавшие до миграции v5.19, не пересчитываются: перед выкладкой база очищается от форм ББ/ОФР
|
||||||
|
(Р-24, §9.5 п. 2), а у формы, оставшейся в неочищенной базе, записей `document_issue` нет — до первой правки её
|
||||||
|
нарушения не показываются и подтверждение их не проверяет (корректность не гарантируется). Экран (`GET …/statement`) и подтверждение (`CHECKS_NOT_PASSED`) читают нарушения **только** из
|
||||||
`document_issue` — у любой формы, какой бы версией контракта она ни пришла. Межформенная `1370(отчётная дата) − 1370(начало
|
`document_issue` — у любой формы, какой бы версией контракта она ни пришла. Межформенная `1370(отчётная дата) − 1370(начало
|
||||||
года) = 2400` — **мягкая**, считается ядром по агрегату формы по коду (§9.5 п. 2) и не блокирует подтверждение: пара —
|
года) = 2400` — **мягкая**, считается ядром по агрегату формы по коду (§9.5 п. 2) и не блокирует подтверждение: пара —
|
||||||
другой документ формы того же ЮЛ с тем же годом и кварталом периода (файл не `SUPERSEDED`; подтверждённый
|
другой документ формы того же ЮЛ с тем же годом и кварталом периода (файл не `SUPERSEDED`; подтверждённый
|
||||||
@ -701,7 +704,9 @@ INITIATED; в Срезе 2c консолидаций ещё нет, поэтом
|
|||||||
результатов 1.x на окне выкладки). `RECOGNIZED` — только если одновременно: `document.status = done` из результата и он согласован с
|
результатов 1.x на окне выкладки). `RECOGNIZED` — только если одновременно: `document.status = done` из результата и он согласован с
|
||||||
содержимым документа (нет строк `unreadable` / `missing` с суммой или текстом в значениях, нет строк, значений и полей
|
содержимым документа (нет строк `unreadable` / `missing` с суммой или текстом в значениях, нет строк, значений и полей
|
||||||
шапки с `needs_review`, нет `checks.relations_failed` и предупреждений документа уровня `error`; иначе ядро считает
|
шапки с `needs_review`, нет `checks.relations_failed` и предупреждений документа уровня `error`; иначе ядро считает
|
||||||
документ `needs_check` и пишет `RESULT_STATUS_INCONSISTENT` `info` — страховка от ошибки сервиса); в `metrics.warnings`
|
документ `needs_check` и пишет `RESULT_STATUS_INCONSISTENT` `info` — страховка от ошибки сервиса; проверка — вызов
|
||||||
|
общей функции статуса §11.9 по входу, собранному из результата, а не пересчёт §7: значения строк `detail` в неё не
|
||||||
|
входят); в `metrics.warnings`
|
||||||
файла нет предупреждений уровня `error` (потерянный json, таблица ИС или исключение разбора таблицы — решение № 28 среза
|
файла нет предупреждений уровня `error` (потерянный json, таблица ИС или исключение разбора таблицы — решение № 28 среза
|
||||||
3a; `needs_check` соседнего документа и статус конверта не учитываются); `counterparty_check ≠ MISMATCH`; определены
|
3a; `needs_check` соседнего документа и статус конверта не учитываются); `counterparty_check ≠ MISMATCH`; определены
|
||||||
`period_year` и `period_quarter` (дата — конец квартала); у каждой строки `ok` с неуверенным кодом (`line_code_confidence
|
`period_year` и `period_quarter` (дата — конец квартала); у каждой строки `ok` с неуверенным кодом (`line_code_confidence
|
||||||
@ -712,6 +717,12 @@ INITIATED; в Срезе 2c консолидаций ещё нет, поэтом
|
|||||||
`RECOGNIZED` после правки — только вручную, как и прежде. Документ, принятый по 1.x, после правки пересчитывается так же
|
`RECOGNIZED` после правки — только вручную, как и прежде. Документ, принятый по 1.x, после правки пересчитывается так же
|
||||||
(общим кодом), его `LEGACY_WARNING` и `MAPPING_WARNING` — `info` и статус не держат.
|
(общим кодом), его `LEGACY_WARNING` и `MAPPING_WARNING` — `info` и статус не держат.
|
||||||
|
|
||||||
|
**Документ ОСВ при приёме контракта v2.0.** `RECOGNIZED` — только если `osvDocument.status = done` (контрольные суммы
|
||||||
|
§1.4 листа сошлись), `checks` листа без нарушений, в `metrics.warnings` файла нет предупреждений уровня `error`,
|
||||||
|
`counterparty_check ≠ MISMATCH` и нет предупреждений маппинга ядра по этому листу; статус конверта не учитывается (как
|
||||||
|
Р-19 у форм: ОСВ в книге с сомнительной ОФР автоподтверждается). Предупреждения листа (`osvDocument.warnings`, `OSV_*`,
|
||||||
|
`info`) хранятся в `document_issue` документа ОСВ и отдаются в `DocumentOut.issues`.
|
||||||
|
|
||||||
### 9.4 Задание (`recognition_status`): queued → published → processing → done | needs_check | error | timeout → dlq;
|
### 9.4 Задание (`recognition_status`): queued → published → processing → done | needs_check | error | timeout → dlq;
|
||||||
replay: dlq → queued (`attempt + 1`). `max_attempts`: OCR 1, парсер 3; `reply_deadline` OCR 20 мин (решение владельца 09.10.2026; note DBML «5 мин» — OQ-159), парсер 5 мин.
|
replay: dlq → queued (`attempt + 1`). `max_attempts`: OCR 1, парсер 3; `reply_deadline` OCR 20 мин (решение владельца 09.10.2026; note DBML «5 мин» — OQ-159), парсер 5 мин.
|
||||||
|
|
||||||
@ -751,9 +762,10 @@ replay: dlq → queued (`attempt + 1`). `max_attempts`: OCR 1, парсер 3; `
|
|||||||
§9.3.
|
§9.3.
|
||||||
**Формы по контракту v2.0** (change `unified-statement-normalization`; правила выше — для результатов 1.x на окне
|
**Формы по контракту v2.0** (change `unified-statement-normalization`; правила выше — для результатов 1.x на окне
|
||||||
выкладки, у них поля v2.0 заполняются по умолчанию: `line_status = ok`, `row_index` по порядку строк результата с 0,
|
выкладки, у них поля v2.0 заполняются по умолчанию: `line_status = ok`, `row_index` по порядку строк результата с 0,
|
||||||
`line_code_original_value = line_code`, `source_ref = null`, `text = string_value`, `needs_review` значений — по
|
`line_code_original_value = line_code`, `source_ref = null`, `text = string_value`, `needs_review` значений = **НЕ**
|
||||||
прежнему правилу «правлено или уверенность 100 и `is_recognized`», `form_variant = null` (§7 — полный набор),
|
«правлено или уверенность 100 и `is_recognized`» (чистое значение — `false`), `form_variant` — по структуре кодов,
|
||||||
`header_source` — из полей шапки 1.x; нарушения §7 ядро считает один раз копией `arm_statements` и пишет в
|
`file_ordinal` — индекс в `documents[]`, `header_source` — из полей шапки 1.x; нарушения §7 ядро считает один раз
|
||||||
|
точкой входа `arm_statements` и пишет в
|
||||||
`document_issue` (`source = core`, §7 «Ядро»); предупреждения маппинга 1.x по документу (повтор кода, значение вне
|
`document_issue` (`source = core`, §7 «Ядро»); предупреждения маппинга 1.x по документу (повтор кода, значение вне
|
||||||
колонок) — `document_issue` `MAPPING_WARNING` `info` (`source = core`; статус при приёме — по D-6, после правки они
|
колонок) — `document_issue` `MAPPING_WARNING` `info` (`source = core`; статус при приёме — по D-6, после правки они
|
||||||
статус не держат); строки `metrics.warnings` 1.x — в `recognition_job.metrics.warnings` структурами `{code:
|
статус не держат); строки `metrics.warnings` 1.x — в `recognition_job.metrics.warnings` структурами `{code:
|
||||||
@ -773,7 +785,11 @@ replay: dlq → queued (`attempt + 1`). `max_attempts`: OCR 1, парсер 3; `
|
|||||||
RELATION_FAILED`, `error` или `kind = soft`, `code = RELATION_SOFT`, `info`, с `codes`, `relation`, `expected`,
|
RELATION_FAILED`, `error` или `kind = soft`, `code = RELATION_SOFT`, `info`, с `codes`, `relation`, `expected`,
|
||||||
`actual`, `column_id`; предупреждение о столбце вне колонок формы — с `column_index`) вместе с проверками ядра уровня
|
`actual`, `column_id`; предупреждение о столбце вне колонок формы — с `column_index`) вместе с проверками ядра уровня
|
||||||
`info` (`CODE_NOT_IN_DICTIONARY`, `CODE_NOT_IN_EDITION`, `NAME_MISMATCH`, `RESULT_STATUS_INCONSISTENT`); версия общего кода —
|
`info` (`CODE_NOT_IN_DICTIONARY`, `CODE_NOT_IN_EDITION`, `NAME_MISMATCH`, `RESULT_STATUS_INCONSISTENT`); версия общего кода —
|
||||||
`recognition_job.statements_version`. Каждый `BALANCE` / `INCOME` становится отдельным документом с `file_ordinal`:
|
`recognition_job.statements_version`. **Каждый** документ результата (ОСВ, формы, `DISCLOSURE`, `OTHER`) при любом
|
||||||
|
MAJOR получает `file_ordinal` — индекс в `documents[]` (несозданный документ номер не занимает и не сдвигает
|
||||||
|
остальные). Предупреждения файла хранятся в `recognition_job.metrics.warnings` структурами (у 1.x — `LEGACY_WARNING`)
|
||||||
|
и отдаются в `JobBrief.warnings` объектами (строки старых заданий конвертируются при чтении). Каждый `BALANCE` /
|
||||||
|
`INCOME` становится отдельным документом:
|
||||||
вторая форма того же типа в файле **хранится** (Р-10, Р-15; ни одна из двух форм одного типа и периода не
|
вторая форма того же типа в файле **хранится** (Р-10, Р-15; ни одна из двух форм одного типа и периода не
|
||||||
автоподтверждается — Р-18); ОСВ по счёту, `DISCLOSURE` и `OTHER` — по-прежнему один документ на тип (и счёт) на файл.
|
автоподтверждается — Р-18); ОСВ по счёту, `DISCLOSURE` и `OTHER` — по-прежнему один документ на тип (и счёт) на файл.
|
||||||
Все чтения формы «по коду» (межформенная проверка, суммы формы для консолидации и выгрузок, `parent_code`, раскладка
|
Все чтения формы «по коду» (межформенная проверка, суммы формы для консолидации и выгрузок, `parent_code`, раскладка
|
||||||
@ -781,6 +797,13 @@ replay: dlq → queued (`attempt + 1`). `max_attempts`: OCR 1, парсер 3; `
|
|||||||
`amount` строк `ok` с кодом по колонке, список `line_id`, справочное имя (DECISION-5, не хранится). Строки `detail`,
|
`amount` строк `ok` с кодом по колонке, список `line_id`, справочное имя (DECISION-5, не хранится). Строки `detail`,
|
||||||
`unreadable`, `missing` в агрегат, КФО и снимок ЕХ не попадают — цена Р-8. Записи `document_issue` удаляются вместе
|
`unreadable`, `missing` в агрегат, КФО и снимок ЕХ не попадают — цена Р-8. Записи `document_issue` удаляются вместе
|
||||||
с документом при перезагрузке файла.
|
с документом при перезагрузке файла.
|
||||||
|
**Перед выкладкой ядра v5.19** база очищается от форм ББ/ОФР (Р-24; скрипт ядра `scripts/purge_statements.py`,
|
||||||
|
`--dry-run` с отчётом, затем `--apply`; владелец или платформенная команда по его заявке, после резервной копии):
|
||||||
|
документы `BALANCE` / `INCOME` с `financial_statement_*`, комментариями и привязками к версиям «в работе»; файлы без
|
||||||
|
ОСВ — целиком с заданиями (объектам S3 — `expires_at`); смешанные «ОСВ + формы» — только формы, или целиком по
|
||||||
|
решению владельца; формы в верифицированных версиях — отказ без изменений; ОСВ, консолидации и справочники не
|
||||||
|
трогаются; системный аудит `FILE_DELETE`. Миграция v5.19 только добавляет колонки и значения по умолчанию (спека
|
||||||
|
`arm-core`, «Миграция DBML v5.19», «Очистка форм перед выкладкой»).
|
||||||
3. **Автозамена.** После успешного маппинга другие файлы того же ЮЛ (не `SUPERSEDED`), содержащие документ с теми же
|
3. **Автозамена.** После успешного маппинга другие файлы того же ЮЛ (не `SUPERSEDED`), содержащие документ с теми же
|
||||||
`document_type`, `osv_account_code`, `period_year`, `period_quarter`, переводятся в `SUPERSEDED`
|
`document_type`, `osv_account_code`, `period_year`, `period_quarter`, переводятся в `SUPERSEDED`
|
||||||
(`superseded_by_file_id` = новый файл); `version_no` нового = максимум заменённых + 1. Новый файл без контекста (`consolidation_id IS NULL`) наследует `consolidation_id` первого (по `id`) заменённого файла, у которого он задан; файл, загруженный в консолидацию, сохраняет свою (v5.18). Заменяется и файл с `CONFIRMED`
|
(`superseded_by_file_id` = новый файл); `version_no` нового = максимум заменённых + 1. Новый файл без контекста (`consolidation_id IS NULL`) наследует `consolidation_id` первого (по `id`) заменённого файла, у которого он задан; файл, загруженный в консолидацию, сохраняет свою (v5.18). Заменяется и файл с `CONFIRMED`
|
||||||
@ -861,7 +884,9 @@ replay: dlq → queued (`attempt + 1`). `max_attempts`: OCR 1, парсер 3; `
|
|||||||
`values[] {line_id, column_id, confirmed: true | false}` — подтвердить ячейку без изменения (`is_confirmed`,
|
`values[] {line_id, column_id, confirmed: true | false}` — подтвердить ячейку без изменения (`is_confirmed`,
|
||||||
`confirmed_by/at`, `needs_review = false`, `is_modified` не ставится) или снять подтверждение (`is_confirmed = false`,
|
`confirmed_by/at`, `needs_review = false`, `is_modified` не ставится) или снять подтверждение (`is_confirmed = false`,
|
||||||
`needs_review` пересчитывается); `columns[] {column_id, period_date}` — дата колонке без подписи или с неуверенной
|
`needs_review` пересчитывается); `columns[] {column_id, period_date}` — дата колонке без подписи или с неуверенной
|
||||||
подписью (обязательная дата, `null` — 422 ошибка валидации; пересчитывает `is_current` и период документа);
|
подписью (обязательная дата, `null` — 422 ошибка валидации; пересчитывает `is_current` и период документа; ставит
|
||||||
|
`period_kind = absolute`; та же дата у колонки с `COLUMN_DATE_UNCERTAIN` / `COLUMN_WITHOUT_DATE` — подтверждение,
|
||||||
|
предупреждение снимается);
|
||||||
`lines[] {line_id, line_code}` — назначить или сменить код; `lines[] {line_id, line_status: "detail"}` — отметить
|
`lines[] {line_id, line_code}` — назначить или сменить код; `lines[] {line_id, line_status: "detail"}` — отметить
|
||||||
расшифровкой; `lines[] {line_id, confirmed: true | false}` — подтвердить неуверенный код или снять подтверждение.
|
расшифровкой; `lines[] {line_id, confirmed: true | false}` — подтвердить неуверенный код или снять подтверждение.
|
||||||
Переходы: `unreadable` / `missing` / `detail` → `ok` назначением кода; `unreadable` / `missing` / `ok` → `detail`
|
Переходы: `unreadable` / `missing` / `detail` → `ok` назначением кода; `unreadable` / `missing` / `ok` → `detail`
|
||||||
@ -869,8 +894,8 @@ replay: dlq → queued (`attempt + 1`). `max_attempts`: OCR 1, парсер 3; `
|
|||||||
исправляется назначением кода, исходное состояние — перезагрузкой файла. `line_status` в PATCH принимает только
|
исправляется назначением кода, исходное состояние — перезагрузкой файла. `line_status` в PATCH принимает только
|
||||||
`"detail"`: `"ok"` без кода — 422 `LINE_CODE_REQUIRED` (в `ok` строку переводит только `line_code`). Назначенный или
|
`"detail"`: `"ok"` без кода — 422 `LINE_CODE_REQUIRED` (в `ok` строку переводит только `line_code`). Назначенный или
|
||||||
подтверждённый код — `line_code_confidence = 100`, `line_code_modified`, `needs_review` строки = `false`;
|
подтверждённый код — `line_code_confidence = 100`, `line_code_modified`, `needs_review` строки = `false`;
|
||||||
`line_code_original_value` не меняется (код из источника; у строк без кода — `null`); у `detail` родитель
|
`line_code_original_value` не меняется (код из источника; у строк без кода — `null`); после правки родитель **всех**
|
||||||
пересчитывается как ближайшая строка `ok` выше по `row_index`. Код — 4–5 цифр (422 `INVALID_LINE_CODE`), может
|
строк `detail` пересчитывается как ближайшая строка `ok` выше по `row_index` (нет такой — `null`). Код — 4–5 цифр (422 `INVALID_LINE_CODE`), может
|
||||||
совпадать с существующим (Р-4); незнакомый словарю — `info`. **Строка, созданная правкой** — `row_index IS NULL`
|
совпадать с существующим (Р-4); незнакомый словарю — `info`. **Строка, созданная правкой** — `row_index IS NULL`
|
||||||
(шаблонная): если строка с назначаемым кодом — такая и у неё есть суммы, — 409 `LINE_CODE_TAKEN`; без сумм — она
|
(шаблонная): если строка с назначаемым кодом — такая и у неё есть суммы, — 409 `LINE_CODE_TAKEN`; без сумм — она
|
||||||
удаляется (её `id` убирается из `document_issue.line_ids`), назначенная строка источника занимает её место. Строки
|
удаляется (её `id` убирается из `document_issue.line_ids`), назначенная строка источника занимает её место. Строки
|
||||||
@ -883,8 +908,9 @@ replay: dlq → queued (`attempt + 1`). `max_attempts`: OCR 1, парсер 3; `
|
|||||||
После правки ядро пересчитывает `checks`, `needs_review` и статус копией `arm_statements` (§11.9): правленое или
|
После правки ядро пересчитывает `checks`, `needs_review` и статус копией `arm_statements` (§11.9): правленое или
|
||||||
подтверждённое значение и строка не имеют `needs_review`, акцепт снимает жёсткие нарушения; записи `document_issue`
|
подтверждённое значение и строка не имеют `needs_review`, акцепт снимает жёсткие нарушения; записи `document_issue`
|
||||||
видов `relation` / `soft` заменяются целиком, предупреждения сервиса сохраняются, кроме снимаемых правкой (таблица
|
видов `relation` / `soft` заменяются целиком, предупреждения сервиса сохраняются, кроме снимаемых правкой (таблица
|
||||||
§11.9). **Акцепт** сбрасывается правкой данных — суммы, кода или статуса строки, даты колонки, поля шапки;
|
§11.9; значения строк `detail` — без `needs_review`, Р-23). **Акцепт** сбрасывается правкой данных — суммы, кода или
|
||||||
подтверждение ячейки или кода (`confirmed`) суммы не меняет и акцепт не сбрасывает. Расхождение `statements_version`
|
статуса строки, даты колонки, поля шапки; подтверждение ячейки или кода (`confirmed`) и подтверждение даты отчёта или
|
||||||
|
колонки тем же значением суммы не меняют и акцепт не сбрасывают (решение владельца 18.10.2026, Р-25). Расхождение `statements_version`
|
||||||
задания и копии ядра — `info` `STATEMENTS_VERSION_MISMATCH` (у заданий 1.x, где версии нет, — не пишется). Один PATCH —
|
задания и копии ядра — `info` `STATEMENTS_VERSION_MISMATCH` (у заданий 1.x, где версии нет, — не пишется). Один PATCH —
|
||||||
один пересчёт и одна запись `RECOGNIZED_DATA_EDIT` (`before/after` по кодам, статусам, родителям, подтверждениям).
|
один пересчёт и одна запись `RECOGNIZED_DATA_EDIT` (`before/after` по кодам, статусам, родителям, подтверждениям).
|
||||||
10. **Исходный файл и комментарии (Срез 3b).** `GET /files/{id}/content` (`DOCUMENT_VIEW`, policy по ЮЛ) отдаёт исходный
|
10. **Исходный файл и комментарии (Срез 3b).** `GET /files/{id}/content` (`DOCUMENT_VIEW`, policy по ЮЛ) отдаёт исходный
|
||||||
@ -1205,9 +1231,14 @@ file:<имя json>;table:<i>`; документа у такой таблицы
|
|||||||
ячеек совпадают после нормализации с ячейками шапки первого сегмента) или граница листа из `segment_breaks`. У
|
ячеек совпадают после нормализации с ячейками шапки первого сегмента) или граница листа из `segment_breaks`. У
|
||||||
сегмента своя раскладка: по его шапке; без шапки — столбец кодов по п. 1, а если сегмент не первый и кодов меньше трёх —
|
сегмента своя раскладка: по его шапке; без шапки — столбец кодов по п. 1, а если сегмент не первый и кодов меньше трёх —
|
||||||
ослабленный поиск: столбец с наибольшим числом кодов из диапазона столбца первого сегмента, минимум один (лист «_2» НПЗ
|
ослабленный поиск: столбец с наибольшим числом кодов из диапазона столбца первого сегмента, минимум один (лист «_2» НПЗ
|
||||||
с единственной строкой 2400, у которого код в другой колонке). Колонки значений сегмента без шапки — столбцы правее
|
с единственной строкой 2400, у которого код в другой колонке). Колонки документа задаёт первый сегмент; колонки
|
||||||
кода, где в строках с кодами числа, сопоставление с первым сегментом по порядку; число не совпало —
|
следующих сегментов новых колонок не создают, а сопоставляются с колонками первого: у сегмента **со своей шапкой**
|
||||||
`SEGMENT_COLUMNS_MISMATCH` (`error`), значения по позиции. Столбец кодов не найден ни так, ни так —
|
(повторная шапка OCR, лист-продолжение Excel со строкой заголовка — «Бухгалтерский баланс. Лист 4» НПЗ, колонки 8–10
|
||||||
|
при 16 / 21 / 24 у первого листа) — по `period_date`, если у каждой колонки сегмента дата есть и совпадает ровно с
|
||||||
|
одной колонкой первого, иначе по порядку; у сегмента без шапки колонки значений — столбцы правее кода, где в строках с
|
||||||
|
кодами числа, по порядку. Значения несут `column_index` колонки первого сегмента. При сопоставлении по порядку другое
|
||||||
|
число колонок — `SEGMENT_COLUMNS_MISMATCH` (`error`): значения по позиции, лишние столбцы сегмента в колонки не
|
||||||
|
попадают, их номера — в тексте предупреждения (S-56). Столбец кодов не найден ни так, ни так —
|
||||||
`SEGMENT_WITHOUT_CODES` (`error`); строки сегмента не теряются (роль `data`, статусы по §11.4).
|
`SEGMENT_WITHOUT_CODES` (`error`); строки сегмента не теряются (роль `data`, статусы по §11.4).
|
||||||
3. **Дата колонки — один словарь периодов** `statement_periods.yaml`, объединяющий прежний словарь парсера и шаблоны
|
3. **Дата колонки — один словарь периодов** `statement_periods.yaml`, объединяющий прежний словарь парсера и шаблоны
|
||||||
`dates.py` шлюза (порядок приоритета текстовых форм: диапазон «с 01.01.2025 по 30.09.2025» — конец; «30 сентября 2025
|
`dates.py` шлюза (порядок приоритета текстовых форм: диапазон «с 01.01.2025 по 30.09.2025» — конец; «30 сентября 2025
|
||||||
@ -1229,7 +1260,10 @@ file:<имя json>;table:<i>`; документа у такой таблицы
|
|||||||
что период формы в ядре, §9.5 п. 2); нет ни той, ни другой → у относительных колонок `period_date = null`, вид
|
что период формы в ядре, §9.5 п. 2); нет ни той, ни другой → у относительных колонок `period_date = null`, вид
|
||||||
остаётся относительным, ядро досчитает после правки даты отчёта. Подпись OCR с уверенностью ниже 100, разобранная в
|
остаётся относительным, ядро досчитает после правки даты отчёта. Подпись OCR с уверенностью ниже 100, разобранная в
|
||||||
дату, подтверждается, если дата входит в ожидаемые: эффективная дата, она же минус один и минус два года, 31 декабря
|
дату, подтверждается, если дата входит в ожидаемые: эффективная дата, она же минус один и минус два года, 31 декабря
|
||||||
предыдущего и предшествующего ему года; иначе `COLUMN_DATE_UNCERTAIN` (`error`, колонка).
|
предыдущего и предшествующего ему года; иначе `COLUMN_DATE_UNCERTAIN` (`error`, колонка). Для этого подтверждения
|
||||||
|
эффективная дата строится **только из уверенных источников** — дата отчёта с уверенностью 100, иначе самая поздняя
|
||||||
|
абсолютная дата колонок с подписью уверенности 100; нет таких — подпись не подтверждена (неуверенное не подтверждает
|
||||||
|
само себя, S-64).
|
||||||
4. **Строка заголовка** — последняя над первой строкой с кодом, где в столбце кодов стоит подпись кода; иначе последняя
|
4. **Строка заголовка** — последняя над первой строкой с кодом, где в столбце кодов стоит подпись кода; иначе последняя
|
||||||
над первой строкой с кодом с двумя и более подписями-периодами правее кода (или одной, не голым годом); двухстрочный
|
над первой строкой с кодом с двумя и более подписями-периодами правее кода (или одной, не голым годом); двухстрочный
|
||||||
заголовок («На» + «20__ г.», перенос внутри ячейки) склеивается. Роль `title` — только строки выше найденной строки
|
заголовок («На» + «20__ г.», перенос внутри ячейки) склеивается. Роль `title` — только строки выше найденной строки
|
||||||
@ -1265,8 +1299,11 @@ file:<имя json>;table:<i>`; документа у такой таблицы
|
|||||||
|
|
||||||
Строка данных — строка сетки с ролью `data`, у которой есть **содержимое**: в ячейке кода есть хотя бы одна цифра
|
Строка данных — строка сетки с ролью `data`, у которой есть **содержимое**: в ячейке кода есть хотя бы одна цифра
|
||||||
(разобранный код или мусор вроде «44 30\nI |» aep_6), или хотя бы одна ячейка значений даёт сумму (`amount` не `null`),
|
(разобранный код или мусор вроде «44 30\nI |» aep_6), или хотя бы одна ячейка значений даёт сумму (`amount` не `null`),
|
||||||
или непуста с уверенностью ниже 100 (признак потерянной суммы: «[190 :», «" :» в aep_3). Строка только с текстом
|
или непуста с уверенностью ниже 100 (признак потерянной суммы: «[190 :», «" :» в aep_3), или её текст — «цифра под видом
|
||||||
наименования — не строка данных (заголовок раздела). Строка `unreadable` без значений («44 30\nI |» при пустых суммах)
|
буквы» (после замены двойников без условия соседства есть цифра и нет других букв: `'I'` с уверенностью 100 в строке 38
|
||||||
|
aep_6). Строка только с текстом наименования — не строка данных (заголовок раздела); текст из настоящих букв в колонке
|
||||||
|
значений при уверенности 100 (подписи под формой Excel: «Руководитель … Косарев») — тоже нет, осознанное исключение
|
||||||
|
(S-72). Строка `unreadable` без значений («44 30\nI |» при пустых суммах)
|
||||||
— строка данных: документ `needs_check` (не все строки `ok` / `detail`), но подтверждение она не блокирует
|
— строка данных: документ `needs_check` (не все строки `ok` / `detail`), но подтверждение она не блокирует
|
||||||
(`LINES_UNRESOLVED` — только строки со значениями, §9.5 п. 4). У строки: `row_index` (номер в документе с
|
(`LINES_UNRESOLVED` — только строки со значениями, §9.5 п. 4). У строки: `row_index` (номер в документе с
|
||||||
0, по порядку сетки после разлепления), `ref` (уникален в документе: OCR `row:<row_index ИС>`, разлепленные
|
0, по порядку сетки после разлепления), `ref` (уникален в документе: OCR `row:<row_index ИС>`, разлепленные
|
||||||
@ -1274,13 +1311,14 @@ file:<имя json>;table:<i>`; документа у такой таблицы
|
|||||||
ячейки), статус, ячейки кода и наименования, ячейки значений по колонкам сегмента, `parent_ref`. **Статусы:**
|
ячейки), статус, ячейки кода и наименования, ячейки значений по колонкам сегмента, `parent_ref`. **Статусы:**
|
||||||
|
|
||||||
- `ok` — код разобран;
|
- `ok` — код разобран;
|
||||||
- `unreadable` — ячейка кода содержит хотя бы одну цифру, но кодом не разобралась («ИЧ,250», «изо :», «[190 :»); ячейка
|
- `unreadable` — ячейка кода содержит хотя бы одну цифру, но кодом не разобралась («ИЧ,250», «[190 :»); ячейка без
|
||||||
без единой цифры («}», «:») считается пустой;
|
единой цифры («}», «:», «изо :» — двойники вне соседства с цифрой цифрами не считаются, S-61) считается пустой;
|
||||||
- `detail` (Р-8) — строка без кода с содержимым под строкой с кодом (в том же или в предыдущих сегментах — родитель
|
- `detail` (Р-8) — строка без кода с содержимым, над которой есть строка `ok` (в том же или в предыдущих сегментах —
|
||||||
продолжается через разрыв страницы): расшифровка («в том числе: …», «из них …», «Оборудование к установке», «Товар
|
родитель продолжается через разрыв страницы): расшифровка («в том числе: …», «из них …», «Оборудование к установке»,
|
||||||
(41.01)»); родитель — ближайшая строка с кодом выше. Сервисы её отдают, ядро хранит; в проверки §7, агрегаты и `done` она
|
«Товар (41.01)»); родитель — ближайшая строка `ok` выше (`unreadable` / `missing` между ними пропускаются). Сервисы её
|
||||||
не входит; экран по умолчанию не показывает;
|
отдают, ядро хранит; в проверки §7, агрегаты, `needs_review` и `done` она не входит (у её значений `needs_review =
|
||||||
- `missing` — строка без кода с содержимым, над которой во всём документе нет строки с кодом («Выручка» aep_3, где 2110
|
false`, Р-23); экран по умолчанию не показывает; после правок ядро пересчитывает родителей всех строк `detail`;
|
||||||
|
- `missing` — строка без кода с содержимым, над которой во всём документе нет строки `ok` («Выручка» aep_3, где 2110
|
||||||
уехал в ячейку значения); строки роли `title` сюда не попадают.
|
уехал в ячейку значения); строки роли `title` сюда не попадают.
|
||||||
|
|
||||||
Строка без кода и без содержимого (заголовок раздела «АКТИВ», «в том числе:» без сумм) — не строка. **Счётчиков
|
Строка без кода и без содержимого (заголовок раздела «АКТИВ», «в том числе:» без сумм) — не строка. **Счётчиков
|
||||||
@ -1314,10 +1352,16 @@ k-е части; уверенность — исходной ячейки. Зн
|
|||||||
1. **`value` есть (Excel):** число → `amount` с квантованием до 2 знаков half-up, не больше 16 целых цифр (иначе `amount
|
1. **`value` есть (Excel):** число → `amount` с квантованием до 2 знаков half-up, не больше 16 целых цифр (иначе `amount
|
||||||
= null`, `string_value` = текст, `is_recognized = false`, `VALUE_OUT_OF_RANGE` `info`); дата или логическое → `amount
|
= null`, `string_value` = текст, `is_recognized = false`, `VALUE_OUT_OF_RANGE` `info`); дата или логическое → `amount
|
||||||
= null`, `string_value` = текст, `is_recognized = false`.
|
= null`, `string_value` = текст, `is_recognized = false`.
|
||||||
2. **`value` нет — `parse_amount(text, source)`**, один для обоих: двойники (§11.3) и крайние `|`; пробелы, NBSP и тонкие
|
2. **`value` нет — `parse_amount(text, source)`**, один для обоих, по стадиям (S-57): (а) прочерк (`-`, `–`, `—`, `−`
|
||||||
пробелы; минус «−» (U+2212) как знак; скобки → минус, `}` и `]` принимаются как закрывающая скобка (OQ-147:
|
без цифр) и пусто → `amount = null`, `is_recognized = true` (OQ-148); (б) крайние `|` удаляются (до замены
|
||||||
«(7744536}» → −7744536, `is_recognized = false`); прочерк (`-`, `–`, `—`, `−` без цифр) и пусто → `amount = null`,
|
двойников), затем двойники вплотную к цифре (§11.3); (в) **ядро числа** — от первого из «цифра, `(`, `-`, `−`, `+`»
|
||||||
`is_recognized = true` (OQ-148). Разделители:
|
до последнего из «цифра, `)`, `}`, `]`»; всё вне ядра — краевой мусор (`:`, `=`, `■`, `•`, кавычки, `{`, `[` …),
|
||||||
|
выбрасывается с `is_recognized = false` («: : 2856983 = ■» → 2856983, «| : 122477» → 122477); (г) в ядре допустимы
|
||||||
|
только цифры, пробелы (NBSP, тонкие), `,`, `.`, знак в начале и одна пара скобок вокруг всего числа (`(` первым, `)`,
|
||||||
|
`}` или `]` последним — `}` / `]` исправление, OQ-147: «(7744536}» → −7744536, `is_recognized = false`; `{` и `[`
|
||||||
|
открывающей скобкой не считаются); иначе — буква в ядре, скобка без пары или не на краю, знак не в начале, другой
|
||||||
|
символ между цифрами — `amount = null`, `string_value` = текст, `is_recognized = false` («211(1 |», «(Ю4)», «{38378)»);
|
||||||
|
(д) цифр нет — `amount = null`, `string_value` = текст. Разделители:
|
||||||
- `ocr`: запятая — тысячи, если весь текст (после `strip` и удаления пробелов, с учётом знака и пары скобок на всю
|
- `ocr`: запятая — тысячи, если весь текст (после `strip` и удаления пробелов, с учётом знака и пары скобок на всю
|
||||||
строку) — `[+-]?[1-9]\d{0,2}(,\d{3})+` (AURORA-1405, sg#35: `4,200` → 4200, `1,234,567` → 1234567); точка — тысячи
|
строку) — `[+-]?[1-9]\d{0,2}(,\d{3})+` (AURORA-1405, sg#35: `4,200` → 4200, `1,234,567` → 1234567); точка — тысячи
|
||||||
при `[+-]?[1-9]\d*(\.\d{3})+` (sg#37: `837.994` → 837994, `1.234.567` → 1234567, `1 234.567` → 1234567); удаление
|
при `[+-]?[1-9]\d*(\.\d{3})+` (sg#37: `837.994` → 837994, `1.234.567` → 1234567, `1 234.567` → 1234567); удаление
|
||||||
@ -1363,12 +1407,14 @@ parent_ref, needs_review, values}`. `line_code` — разобранный ко
|
|||||||
`type_hint = DISCLOSURE`: подпись «Пояснения» или имя листа «Расшифровка» над таблицей с кодами 1110–1700 не должны
|
`type_hint = DISCLOSURE`: подпись «Пояснения» или имя листа «Расшифровка» над таблицей с кодами 1110–1700 не должны
|
||||||
превратить баланс в пустое пояснение — большинство кодов в отрезке ББ или ОФР даёт тип по кодам и `TYPE_CONFLICT`
|
превратить баланс в пустое пояснение — большинство кодов в отрезке ББ или ОФР даёт тип по кодам и `TYPE_CONFLICT`
|
||||||
(`error`). Без столбца кодов: `type_hint` есть → документ без строк (`DISCLOSURE` — `done`, иначе `needs_check` с
|
(`error`). Без столбца кодов: `type_hint` есть → документ без строк (`DISCLOSURE` — `done`, иначе `needs_check` с
|
||||||
`CODE_COLUMN_NOT_FOUND` `error`); `type_hint` нет → у OCR таблица ИС с содержимым — предупреждение **файла**
|
`CODE_COLUMN_NOT_FOUND` `error`); `type_hint` нет → у OCR таблица ИС с содержимым (хотя бы одна ячейка с цифрой
|
||||||
`TABLE_LOST` (`error`, `locator = file:<имя json>;table:<i>`), у Excel — лист «прочее», предупреждение **файла**
|
или «цифрой под видом буквы») — предупреждение **файла** `TABLE_LOST` (`error`, `locator = file:<имя json>;table:<i>`),
|
||||||
`SHEET_OTHER` (`info`, `locator = sheet:<имя>`); документа в обоих случаях нет. `DISCLOSURE` (по `type_hint` без
|
таблица только из текста — `TABLE_SKIPPED` (`info`, файл, S-71); у Excel — лист «прочее», предупреждение **файла**
|
||||||
|
`SHEET_OTHER` (`info`, `locator = sheet:<имя>`); документа во всех случаях нет. `DISCLOSURE` (по `type_hint` без
|
||||||
кодов форм или по кодам прочих форм) — шаги 3–7 не выполняются, `columns` и `lines` — пустые массивы (ядро строки
|
кодов форм или по кодам прочих форм) — шаги 3–7 не выполняются, `columns` и `lines` — пустые массивы (ядро строки
|
||||||
пояснений не хранит), `checks` нет, документ `done`.
|
пояснений не хранит), `checks` нет, документ `done`.
|
||||||
2. **Вариант формы** `form_variant` — только по структуре кодов (ОКУД 0710096 — код КНД комплекта, на бланках
|
2. **Вариант формы** `form_variant` — только по структуре кодов всех строк `ok` (ядро определяет его заново при каждом
|
||||||
|
пересчёте после правки; ОКУД 0710096 — код КНД комплекта, на бланках
|
||||||
0710001 / 0710002, признаком не служит): ББ — есть 1600 и нет ни 1100, ни 1200 → `simplified`; ОФР — есть 2400 или
|
0710001 / 0710002, признаком не служит): ББ — есть 1600 и нет ни 1100, ни 1200 → `simplified`; ОФР — есть 2400 или
|
||||||
2110 и нет ни 2100, ни 2200, ни 2300 → `simplified`; иначе `full`; у `DISCLOSURE` / `OTHER` — `null`. Шаг 7 и перечень
|
2110 и нет ни 2100, ни 2200, ни 2300 → `simplified`; иначе `full`; у `DISCLOSURE` / `OTHER` — `null`. Шаг 7 и перечень
|
||||||
кодов берут набор варианта. Образец упрощённой формы — в fixtures при первой возможности (OQ-166).
|
кодов берут набор варианта. Образец упрощённой формы — в fixtures при первой возможности (OQ-166).
|
||||||
@ -1386,7 +1432,9 @@ parent_ref, needs_review, values}`. `line_code` — разобранный ко
|
|||||||
### §11.9 Шаг 8 — `needs_review`, статус, предупреждения
|
### §11.9 Шаг 8 — `needs_review`, статус, предупреждения
|
||||||
|
|
||||||
**Подтверждение проверкой** (Р-5, Р-7, Р-11, Р-17, Р-21). Проверка §7 — арбитр, уверенность — подсказка там, где
|
**Подтверждение проверкой** (Р-5, Р-7, Р-11, Р-17, Р-21). Проверка §7 — арбитр, уверенность — подсказка там, где
|
||||||
проверки нет.
|
проверки нет. Правила ниже — для значений строк `ok`, `unreadable`, `missing`; у значений строк `detail` `needs_review =
|
||||||
|
false` всегда (Р-23, решение владельца 18.10.2026): кода у них нет, покрыть их нельзя, в §7, агрегаты и статус они не
|
||||||
|
входят, сомнение видно в подсказке ячейки (`text`, уверенность).
|
||||||
|
|
||||||
- Значение **нуждается в покрытии**, если `confidence < 100` (Р-5: у ИС уверенность дискретная — 100, ~80, 71, 67, 60,
|
- Значение **нуждается в покрытии**, если `confidence < 100` (Р-5: у ИС уверенность дискретная — 100, ~80, 71, 67, 60,
|
||||||
50, 25, 0; в группе 70–89 есть настоящие ошибки, например «11 30» с уверенностью 80) или `is_recognized = false`
|
50, 25, 0; в группе 70–89 есть настоящие ошибки, например «11 30» с уверенностью 80) или `is_recognized = false`
|
||||||
@ -1396,11 +1444,13 @@ parent_ref, needs_review, values}`. `line_code` — разобранный ко
|
|||||||
допуска ±1, без «округления» (Р-21, решение владельца 18.10.2026). Мягким покрываются строки, у которых жёстких
|
допуска ±1, без «округления» (Р-21, решение владельца 18.10.2026). Мягким покрываются строки, у которых жёстких
|
||||||
соотношений нет: 2400, 2410, 2411, 2412, 2460 (ОФР полного набора; `2400` упрощённого — тоже мягкое, S-5), 2510, 2520,
|
соотношений нет: 2400, 2410, 2411, 2412, 2460 (ОФР полного набора; `2400` упрощённого — тоже мягкое, S-5), 2510, 2520,
|
||||||
2530, 2500 (`2500 = 2400 + 2510 + 2520 + 2530`), пятизначные подстроки («сумма подстрок = родитель»).
|
2530, 2500 (`2500 = 2400 + 2510 + 2520 + 2530`), пятизначные подстроки («сумма подстрок = родитель»).
|
||||||
- **Знак** члена `abs_terms`, нуждающегося в покрытии: подтверждён, если форма записи знака (скобки, минус, без знака —
|
- **Знак** члена `abs_terms`, нуждающегося в покрытии: подтверждён, если форма знака совпадает с формой строгого
|
||||||
по `text`) совпадает с формой у большинства членов `abs_terms` той же колонки с непустым `amount`, `is_recognized =
|
большинства **остальных** членов `abs_terms` той же колонки с непустым `amount`, `is_recognized = true` и без правки
|
||||||
true` и без правки аналитика; не совпадает или сравнивать не с чем — не подтверждён (то же правило — при покрытии
|
аналитика (сама ячейка не голосует); форма знака — по результату разбора `parse_amount`: «скобки» (пара, в том числе с
|
||||||
мягким соотношением, например 2410 в `2400 = 2300 − 2410 + 2420`). «(7744536}» подтверждается, если 2210 и 2220 той же
|
`}` / `]`), «минус», «без знака»; голосующих нет или голоса поровну — не подтверждён (S-66; то же правило — при
|
||||||
колонки в скобках; «{38378)» при остальных в скобках — подсвечивается.
|
покрытии мягким соотношением, например 2410 в `2400 = 2300 − 2410 + 2420`). «(7744536}» подтверждается, если 2210 и
|
||||||
|
2220 той же колонки в скобках; сомнительное «38378» без знака при остальных в скобках — подсвечивается; «{38378)»
|
||||||
|
разбором даёт `null` (§11.5) и подсвечивается как непокрытое.
|
||||||
- `needs_review = true`, если значение нуждается в покрытии и не покрыто или его знак не подтверждён; а также у всех
|
- `needs_review = true`, если значение нуждается в покрытии и не покрыто или его знак не подтверждён; а также у всех
|
||||||
ячеек жёсткого соотношения, не сошедшегося вне допуска и округления, независимо от уверенности. Иначе `false`: сырой
|
ячеек жёсткого соотношения, не сошедшегося вне допуска и округления, независимо от уверенности. Иначе `false`: сырой
|
||||||
текст и уверенность остаются в подсказке ячейки. Мягкое соотношение, не сошедшееся точно, подсветки не даёт.
|
текст и уверенность остаются в подсказке ячейки. Мягкое соотношение, не сошедшееся точно, подсветки не даёт.
|
||||||
@ -1417,15 +1467,19 @@ parent_ref, needs_review, values}`. `line_code` — разобранный ко
|
|||||||
наименований, в порядке бланка (итоги после слагаемых: 1110 … 1190, 1100; 2110, 2120, 2100). **Данные перечня —
|
наименований, в порядке бланка (итоги после слагаемых: 1110 … 1190, 1100; 2110, 2120, 2100). **Данные перечня —
|
||||||
`contracts/statement-code-orders.yaml`** (источник; в пакете — его байтовая копия, сверяет `sync-statements --check`):
|
`contracts/statement-code-orders.yaml`** (источник; в пакете — его байтовая копия, сверяет `sync-statements --check`):
|
||||||
объединение кодов шаблонов банка (словарь ядра) и кодов печатных форм 1С и сканов из `fixtures/` (1105, 1130, 1140, 1215,
|
объединение кодов шаблонов банка (словарь ядра) и кодов печатных форм 1С и сканов из `fixtures/` (1105, 1130, 1140, 1215,
|
||||||
1231, 1530, 2420, 2460, 2530, 2930) с источником у каждого; код без образца в перечень не входит (кандидаты — OQ-168),
|
1231, 1530, 2420, 2460, 2530, 2930) с источником у каждого; код без образца в перечень не входит (кандидаты — OQ-168;
|
||||||
|
временное правило утверждено владельцем 18.10.2026, Р-26: неуверенный такой код на скане — `needs_review` строки),
|
||||||
упрощённые варианты — по бланку (OQ-166). **Законные повторы кода** (Р-20, решение владельца 18.10.2026) — часть перечня
|
упрощённые варианты — по бланку (OQ-166). **Законные повторы кода** (Р-20, решение владельца 18.10.2026) — часть перечня
|
||||||
(`repeats`): у `fsbu4` — 1220 «НДС, начисленный по отгрузке», 1520 «Обязательство по аренде», 2410 «Налог при упрощённой
|
(`repeats`): у `fsbu4` — 1220 «НДС, начисленный по отгрузке», 1520 «Обязательство по аренде», 2410 «Налог при упрощённой
|
||||||
системе налогообложения» (печатная форма 1С, AURORA-1431), у `66n` — раздел III некоммерческой организации после раздела
|
системе налогообложения» (печатная форма 1С, AURORA-1431), у `66n` — раздел III некоммерческой организации после раздела
|
||||||
III (aep_3). Алгоритм: четырёхзначные коды строк `ok` документа — последовательность; для **каждой** редакции перечня
|
III (aep_3). Алгоритм: четырёхзначные коды строк `ok` документа — последовательность (коды, которых нет в перечне
|
||||||
(66н и ФСБУ 4 — всегда обе: Межениновская за 3 кв. 2025 сдана на бланке 66н) — наибольшая подпоследовательность,
|
редакции, в её последовательность не входят); для **каждой** редакции перечня (66н и ФСБУ 4 — всегда обе: Межениновская
|
||||||
согласованная с порядком перечня варианта формы документа (повтор допустим столько раз, сколько код стоит в перечне);
|
за 3 кв. 2025 сдана на бланке 66н) — **лучшая** подпоследовательность, согласованная с порядком перечня варианта формы
|
||||||
редакция — с более длинной (при равенстве — по эффективной дате: `fsbu4` после 2024-12-31, OQ-145); уверенные коды —
|
документа (повтор допустим столько раз, сколько код стоит в перечне): сначала наибольшее число уверенных кодов (якоря;
|
||||||
подтверждённые якоря; при нескольких наибольших подпоследовательностях подтверждены только общие коды. Пятизначный код
|
противоречащие по порядку якоря все войти не могут — входит наибольшее согласованное множество), затем наибольшая
|
||||||
|
длина; редакция — с лучшей (при равенстве — по эффективной дате: `fsbu4` после 2024-12-31, OQ-145); неуверенный код
|
||||||
|
подтверждён, только если входит во все лучшие подпоследовательности выбранной редакции (S-65). Вариант формы — по всем
|
||||||
|
строкам `ok` (§11.7); ядро пересчитывает вариант и проверку после правки кода. Пятизначный код
|
||||||
подтверждён, если подтверждён его родитель (§7) и подстрока стоит после него. Повтор кода, которого **нет в перечне**
|
подтверждён, если подтверждён его родитель (§7) и подстрока стоит после него. Повтор кода, которого **нет в перечне**
|
||||||
(или копий больше, чем в перечне), у которого хотя бы одна копия неуверенна, — `needs_review` у неуверенной копии (Р-16);
|
(или копий больше, чем в перечне), у которого хотя бы одна копия неуверенна, — `needs_review` у неуверенной копии (Р-16);
|
||||||
повтор из перечня подтверждается структурой, как любой код. Неподтверждённый неуверенный код — `needs_review` строки,
|
повтор из перечня подтверждается структурой, как любой код. Неподтверждённый неуверенный код — `needs_review` строки,
|
||||||
@ -1434,15 +1488,20 @@ III (aep_3). Алгоритм: четырёхзначные коды строк
|
|||||||
наименования со справочным (§9.3).
|
наименования со справочным (§9.3).
|
||||||
|
|
||||||
**Совпадение наименований** (ядро; S-4 с оговорками ревью, решение владельца 18.10.2026): обе строки — нижний регистр,
|
**Совпадение наименований** (ядро; S-4 с оговорками ревью, решение владельца 18.10.2026): обе строки — нижний регистр,
|
||||||
`ё` → `е`, всё кроме букв и цифр → пробел, пробелы схлопываются; совпало, если строки равны, или более короткая (≥ 5
|
`ё` → `е`, всё кроме букв и цифр → пробел, пробелы схлопываются, крайние обрезаются (`strip`); совпало, если строки
|
||||||
символов) входит в более длинную, или `difflib.SequenceMatcher` ≥ 0,8 — с **любым** справочным именем кода в любой
|
равны, или более короткая (≥ 5 символов) входит в более длинную, или `difflib.SequenceMatcher` ≥ 0,8 — с **любым**
|
||||||
редакции словаря ядра (у кода может быть несколько имён: синонимы и законные повторы — Р-20). Исключение: если хотя бы
|
справочным именем кода в любой редакции словаря ядра (у кода может быть несколько имён: синонимы и законные повторы —
|
||||||
одно из сравниваемых имён начинается с «итого по разделу», совпадение — **только равенство** («итого по разделу i»
|
Р-20). Исключение: если хотя бы одно из сравниваемых имён начинается с «итого по разделу», сравниваются **номера
|
||||||
входит в «итого по разделу ii» и похоже на него — без исключения подмена 1100 ↔ 1200 ↔ 1300 не ловилась бы именем).
|
раздела** — начальные буквы `i` / `v` / `x` первого слова после фразы (кириллические «і», «х» приводятся к латинским,
|
||||||
|
хвост слова отбрасывается: «Итого по разделу V1» Счастливочной и «Итого по разделу V /» РБ → V); совпало ⇔ номера равны;
|
||||||
|
номер не выделен — только равенство строк (S-60). Без исключения «итого по разделу i» входило бы в «итого по разделу ii»
|
||||||
|
— подмена 1100 ↔ 1200 ↔ 1300 не ловилась бы именем. В словарь имён ядра `fsbu4` в PR перехода добавляются вторые имена
|
||||||
|
законных повторов 1220, 1520, 2410 и коды 1105 «Гудвил», 1130, 1140, 1530, 2460 (спека `arm-core`).
|
||||||
Кода нет в словаре или наименование пусто — не совпало; несовпадение — `NAME_MISMATCH` (`info`) у строки. По
|
Кода нет в словаре или наименование пусто — не совпало; несовпадение — `NAME_MISMATCH` (`info`) у строки. По
|
||||||
`fixtures/ocr` коды часто приходят с уверенностью 50 и 25 — требовать 100 нельзя.
|
`fixtures/ocr` коды часто приходят с уверенностью 50 и 25 — требовать 100 нельзя.
|
||||||
|
|
||||||
**Поля шапки.** Дата отчёта с уверенностью ниже 100 подтверждена, если совпала с самой поздней абсолютной датой колонок;
|
**Поля шапки.** Дата отчёта с уверенностью ниже 100 подтверждена, если совпала с самой поздней абсолютной датой колонок
|
||||||
|
с подписью уверенности 100 (неуверенная подпись дату отчёта не подтверждает, S-64);
|
||||||
иначе `report_date` попадает в `needs_review_fields` и `REPORT_DATE_UNCERTAIN` (`error`) — неверно прочитанный год давал
|
иначе `report_date` попадает в `needs_review_fields` и `REPORT_DATE_UNCERTAIN` (`error`) — неверно прочитанный год давал
|
||||||
бы неверный период и автоподтверждение. **У бланка 66н только с относительными подписями** («На отчётную дату», …)
|
бы неверный период и автоподтверждение. **У бланка 66н только с относительными подписями** («На отчётную дату», …)
|
||||||
абсолютных дат колонок нет, и неуверенная дата отчёта структурой не подтверждается никогда — такой документ всегда
|
абсолютных дат колонок нет, и неуверенная дата отчёта структурой не подтверждается никогда — такой документ всегда
|
||||||
@ -1463,14 +1522,15 @@ III (aep_3). Алгоритм: четырёхзначные коды строк
|
|||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| `JSON_FAILED` | error, файл | json набора ИС ошибочен при успешном соседе | перезагрузка |
|
| `JSON_FAILED` | error, файл | json набора ИС ошибочен при успешном соседе | перезагрузка |
|
||||||
| `TABLE_PARSE_ERROR` | error, файл | исключение при разборе таблицы ИС (`locator` таблицы; документа нет) | перезагрузка |
|
| `TABLE_PARSE_ERROR` | error, файл | исключение при разборе таблицы ИС (`locator` таблицы; документа нет) | перезагрузка |
|
||||||
| `TABLE_LOST` | error, файл | таблица ИС с содержимым без столбца кодов и без типа | перезагрузка |
|
| `TABLE_LOST` | error, файл | таблица ИС с содержимым (ячейка с цифрой или «цифрой под видом буквы») без столбца кодов и без типа | перезагрузка |
|
||||||
|
| `TABLE_SKIPPED` | info, файл | таблица ИС только из текста без столбца кодов и без типа (документа нет) | — |
|
||||||
| `CODE_COLUMN_NOT_FOUND` | error | форма `BALANCE` / `INCOME` без столбца кодов | перезагрузка |
|
| `CODE_COLUMN_NOT_FOUND` | error | форма `BALANCE` / `INCOME` без столбца кодов | перезагрузка |
|
||||||
| `SEGMENT_WITHOUT_CODES` | error | сегмент с содержимым без столбца кодов (в т. ч. лист-продолжение) | перезагрузка |
|
| `SEGMENT_WITHOUT_CODES` | error | сегмент с содержимым без столбца кодов (в т. ч. лист-продолжение) | перезагрузка |
|
||||||
| `SHEET_NOT_JOINED` | error | лист с ≥ 3 кодами и другим числом колонок не присоединился | перезагрузка |
|
| `SHEET_NOT_JOINED` | error | лист с ≥ 3 кодами и другим числом колонок не присоединился | перезагрузка |
|
||||||
| `CONTINUATION_TYPE_CONFLICT` | error | тип маркера продолжения противоречит форме | перезагрузка |
|
| `CONTINUATION_TYPE_CONFLICT` | error | тип маркера продолжения противоречит форме | перезагрузка |
|
||||||
| `SEGMENT_COLUMNS_MISMATCH` | error | колонки сегмента не совпали с первым | перезагрузка |
|
| `SEGMENT_COLUMNS_MISMATCH` | error | при сопоставлении по порядку число колонок сегмента не совпало с первым | перезагрузка |
|
||||||
| `COLUMN_WITHOUT_DATE` | error | числовая колонка без разобранной подписи | PATCH `columns[].period_date` |
|
| `COLUMN_WITHOUT_DATE` | error | числовая колонка без разобранной подписи | PATCH `columns[].period_date` |
|
||||||
| `COLUMN_DATE_UNCERTAIN` | error | неуверенная подпись не попала в ожидаемые даты | PATCH `columns[].period_date` |
|
| `COLUMN_DATE_UNCERTAIN` | error | неуверенная подпись не попала в ожидаемые даты | PATCH `columns[].period_date` (в том числе той же датой — подтверждение) |
|
||||||
| `HEADER_ROW_NOT_FOUND` | error | заголовка нет ни у одного сегмента | перезагрузка |
|
| `HEADER_ROW_NOT_FOUND` | error | заголовка нет ни у одного сегмента | перезагрузка |
|
||||||
| `TYPE_CONFLICT` | error | тип по кодам противоречит `type_hint` | перезагрузка |
|
| `TYPE_CONFLICT` | error | тип по кодам противоречит `type_hint` | перезагрузка |
|
||||||
| `NO_ORGANIZATION_NO_INN` | error | нет ни организации, ни ИНН | PATCH `header.inn` / `organization_name` |
|
| `NO_ORGANIZATION_NO_INN` | error | нет ни организации, ни ИНН | PATCH `header.inn` / `organization_name` |
|
||||||
@ -1500,8 +1560,9 @@ III (aep_3). Алгоритм: четырёхзначные коды строк
|
|||||||
|
|
||||||
**Статус документа** — одна функция общего кода: `done` ⇔ раскладка найдена ∧ (организация или ИНН) ∧ период определён
|
**Статус документа** — одна функция общего кода: `done` ⇔ раскладка найдена ∧ (организация или ИНН) ∧ период определён
|
||||||
(дата отчёта или самая поздняя абсолютная дата колонок) ∧ все строки `ok` или `detail` ∧ нет значений, строк и полей
|
(дата отчёта или самая поздняя абсолютная дата колонок) ∧ все строки `ok` или `detail` ∧ нет значений, строк и полей
|
||||||
шапки с `needs_review` ∧ нет жёстких нарушений §7 вне допуска и округления ∧ нет предупреждений документа уровня
|
шапки с `needs_review` (значения строк `detail` не учитываются, Р-23) ∧ нет жёстких нарушений §7 вне допуска и
|
||||||
`error`; иначе `needs_check`. `DISCLOSURE` — `done` без условий о раскладке, строках и проверках. Квартал периода,
|
округления ∧ нет предупреждений документа уровня `error`; иначе `needs_check`. `DISCLOSURE` — `done` без условий о
|
||||||
|
раскладке, строках и проверках. Квартал периода,
|
||||||
сверка ИНН и наименований — знание ядра (§9.3), в `done` не входят. У ОСВ `document.status = done`, когда контрольные
|
сверка ИНН и наименований — знание ядра (§9.3), в `done` не входят. У ОСВ `document.status = done`, когда контрольные
|
||||||
суммы §1.4 листа сошлись (§1.6, S-22), её предупреждения — `info`.
|
суммы §1.4 листа сошлись (§1.6, S-22), её предупреждения — `info`.
|
||||||
|
|
||||||
@ -1509,19 +1570,23 @@ III (aep_3). Алгоритм: четырёхзначные коды строк
|
|||||||
`document_type`; `layout_found` (сервис — столбец кодов найден хотя бы у одного сегмента; ядро — у формы есть хотя бы
|
`document_type`; `layout_found` (сервис — столбец кодов найден хотя бы у одного сегмента; ядро — у формы есть хотя бы
|
||||||
одна строка из источника, `row_index` не `null`); `has_organization_or_inn`; `period_known` (дата отчёта или абсолютная
|
одна строка из источника, `row_index` не `null`); `has_organization_or_inn`; `period_known` (дата отчёта или абсолютная
|
||||||
дата хотя бы одной колонки — у ядра `document.reporting_date` не `null`); строки `{line_status, needs_review}`; значения
|
дата хотя бы одной колонки — у ядра `document.reporting_date` не `null`); строки `{line_status, needs_review}`; значения
|
||||||
`{needs_review}`; число жёстких нарушений вне допуска и округления; `accepted` (акцепт — жёсткие нарушения не
|
`{line_status строки, needs_review}` (значения строк `detail` функция не учитывает); число жёстких нарушений вне допуска и округления; `accepted` (акцепт — жёсткие нарушения не
|
||||||
учитываются); уровни предупреждений документа (у ядра — записи `document_issue` вида `warning` с `severity = error`).
|
учитываются); уровни предупреждений документа (у ядра — записи `document_issue` вида `warning` с `severity = error`).
|
||||||
`needs_review` строк и значений на входе уже учитывает подтверждения: у ядра правленое (`is_modified`,
|
`needs_review` строк и значений на входе уже учитывает подтверждения: у ядра правленое (`is_modified`,
|
||||||
`line_code_modified`) или подтверждённое (`is_confirmed`) — `false`. Сервис собирает вход из `FormCandidate` и
|
`line_code_modified`) или подтверждённое (`is_confirmed`) — `false`. Сервис собирает вход из `FormCandidate` и
|
||||||
результата шагов 2–7, ядро — из строк БД; так один код даёт один статус.
|
результата шагов 2–7, ядро — из строк БД (после правки — точкой входа пакета для пересчёта по сохранённым данным,
|
||||||
|
спека `statement-normalization`; при приёме результата — для проверки согласованности `done`, §9.3); так один код даёт
|
||||||
|
один статус.
|
||||||
|
|
||||||
**Ожидание на реальных сканах** (`fixtures/ocr`, оценка ревью спеки 18.10.2026, K-4): после Р-20 (повторы 1220 / 1520 /
|
**Ожидание на реальных сканах** (`fixtures/ocr`, оценки ревью спеки 18.10.2026, K-4 и L-1): после Р-20 (повторы 1220 /
|
||||||
2410 в перечне `fsbu4` и синонимы в словаре имён) и Р-21 (мягкие соотношения ОФР) законные повторы и строки 24xx / 25xx
|
1520 / 2410 в перечне `fsbu4` и синонимы в словаре имён), Р-21 (мягкие соотношения ОФР) и Р-23 (значения `detail` не
|
||||||
перестают держать документ. `needs_check` остаётся там, где есть что смотреть: строки `unreadable` / `missing` с суммами
|
держат статус) законные повторы, строки 24xx / 25xx и ячейки расшифровок документ не держат. По прогону ревьюера ББ и
|
||||||
(aep_3), значения с уверенностью ниже 100 вне соотношений и в несошедшихся соотношениях, разлепленные строки без
|
ОФР РБ («Бухгалтерская отчетность 1 кв.25») и Счастливочной («БО 1 кв 2025») могут стать `done`: все их неуверенные
|
||||||
покрытия, неуверенная дата отчёта. Доля `done` на текущих шести документах ожидается небольшой; golden-эталоны
|
значения строк с кодом покрыты сошедшимися соотношениями, неуверенные коды подтверждаются перечнем `fsbu4`, а держали
|
||||||
`fixtures/ocr/expected_normalize/` пересчитываются в PR scan-gateway (шаг 5 выкладки), фактическая доля — в его
|
их только ячейки расшифровок. `needs_check` остаётся там, где есть что смотреть: строки `unreadable` / `missing` с
|
||||||
описании.
|
суммами (aep_3), несошедшиеся соотношения и мусор в значениях (aep_6: «(Ю4)» в 2350), неуверенная дата отчёта.
|
||||||
|
Golden-эталоны `fixtures/ocr/expected_normalize/` пересчитываются в PR scan-gateway (шаг 5 выкладки), фактическая доля
|
||||||
|
`done` — в его описании.
|
||||||
|
|
||||||
**Статус конверта:** `done` ⇔ все документы `done` ∧ нет предупреждений файла уровня `error`; иначе `needs_check`;
|
**Статус конверта:** `done` ⇔ все документы `done` ∧ нет предупреждений файла уровня `error`; иначе `needs_check`;
|
||||||
документов нет — `error` (`RECOGNITION_FAILED` у scan-gateway, `INVALID_DOCUMENT` у tabular-parser — коды описывают
|
документов нет — `error` (`RECOGNITION_FAILED` у scan-gateway, `INVALID_DOCUMENT` у tabular-parser — коды описывают
|
||||||
|
|||||||
@ -173,4 +173,4 @@
|
|||||||
| OQ-165 | Склейка двух половинок одной формы из **одного** файла (Р-15): Excel с листом-продолжением другого типа, двойная печать формы, две таблицы ИС одного типа и периода. Сейчас вторая форма того же типа и периода хранится отдельным документом (Р-10), ни одна не автоподтверждается (Р-18), подтверждение второй при подтверждённой первой — 409 `DUPLICATE_FORM`; склейки нет — файл перезагружают. Нужна ли склейка (кнопка «объединить формы») и по каким правилам | change `unified-statement-normalization`, Р-15, Р-18 | arm-core (`document.file_ordinal`, подтверждение), arm-web, §9.5 п. 2 | открыт: от ИС Сканирование такой случай не ожидается (владелец 17.10.2026), основной источник — Excel; допущение — склейки нет, файл перезагружают |
|
| OQ-165 | Склейка двух половинок одной формы из **одного** файла (Р-15): Excel с листом-продолжением другого типа, двойная печать формы, две таблицы ИС одного типа и периода. Сейчас вторая форма того же типа и периода хранится отдельным документом (Р-10), ни одна не автоподтверждается (Р-18), подтверждение второй при подтверждённой первой — 409 `DUPLICATE_FORM`; склейки нет — файл перезагружают. Нужна ли склейка (кнопка «объединить формы») и по каким правилам | change `unified-statement-normalization`, Р-15, Р-18 | arm-core (`document.file_ordinal`, подтверждение), arm-web, §9.5 п. 2 | открыт: от ИС Сканирование такой случай не ожидается (владелец 17.10.2026), основной источник — Excel; допущение — склейки нет, файл перезагружают |
|
||||||
| OQ-166 | Упрощённая форма ББ/ОФР (приказ 66н, прил. 5): признак — по структуре кодов (ББ: есть 1600, нет 1100 и 1200; ОФР: есть 2400 или 2110, нет 2100, 2200, 2300), ОКУД 0710096 признаком не служит. Набор соотношений §7 и перечень кодов упрощённой формы заданы по бланку (`05-domain-rules.md` §7, решение S-5 спеки; ББ — жёсткие, ОФР `2400 = 2110 − 2120 − 2330 + 2340 − 2350 − 2410` — мягкое, как `2400` полного набора, решение владельца 18.10.2026, Р-22; перечни — `contracts/statement-code-orders.yaml`, у `fsbu4` — как у 66н) без образца в `fixtures/` | change `unified-statement-normalization`, design (Decisions № 14) | `arm_statements` `statement_checks.yaml`, перечень кодов, `form_variant` | открыт: допущение — формулы §7 «Упрощённый набор»; нужен реальный образец упрощённой отчётности (Excel и скан) в `fixtures/` для проверки признака и формул |
|
| OQ-166 | Упрощённая форма ББ/ОФР (приказ 66н, прил. 5): признак — по структуре кодов (ББ: есть 1600, нет 1100 и 1200; ОФР: есть 2400 или 2110, нет 2100, 2200, 2300), ОКУД 0710096 признаком не служит. Набор соотношений §7 и перечень кодов упрощённой формы заданы по бланку (`05-domain-rules.md` §7, решение S-5 спеки; ББ — жёсткие, ОФР `2400 = 2110 − 2120 − 2330 + 2340 − 2350 − 2410` — мягкое, как `2400` полного набора, решение владельца 18.10.2026, Р-22; перечни — `contracts/statement-code-orders.yaml`, у `fsbu4` — как у 66н) без образца в `fixtures/` | change `unified-statement-normalization`, design (Decisions № 14) | `arm_statements` `statement_checks.yaml`, перечень кодов, `form_variant` | открыт: допущение — формулы §7 «Упрощённый набор»; нужен реальный образец упрощённой отчётности (Excel и скан) в `fixtures/` для проверки признака и формул |
|
||||||
| OQ-167 | Несколько отчётных периодов в одном файле, отданном в ИС Сканирование (например, ББ за два квартала в одном PDF): шапка в json ИС одна на json и копируется во все его таблицы | change `unified-statement-normalization`, design шаг 1 (допущение владельца 17.10.2026) | scan-gateway (адаптер шага 1), период документа | допущение владельца: ИС отдаёт разные периоды разными json; образца нет — проверить на первом таком файле, при расхождении — шапка из json неверна для части таблиц |
|
| OQ-167 | Несколько отчётных периодов в одном файле, отданном в ИС Сканирование (например, ББ за два квартала в одном PDF): шапка в json ИС одна на json и копируется во все его таблицы | change `unified-statement-normalization`, design шаг 1 (допущение владельца 17.10.2026) | scan-gateway (адаптер шага 1), период документа | допущение владельца: ИС отдаёт разные периоды разными json; образца нет — проверить на первом таком файле, при расхождении — шапка из json неверна для части таблиц |
|
||||||
| OQ-168 | Коды упорядоченного перечня форм без образца в `fixtures/` (`contracts/statement-code-orders.yaml`, §11.9): в перечень вошли только коды шаблонов банка и коды печатных форм 1С и сканов, найденные в `fixtures/` (1105, 1130, 1140, 1215, 1231, 1530, 2420, 2460, 2530, 2930, повторы 1220 / 1520 / 2410 у `fsbu4`, раздел III НКО у `66n`). Не подтверждены образцом: 1120 в `fsbu4`; 1105, 1215, 2420 в `66n`; 2421, 2430, 2450 (прежние строки ОФР 66н); 2930 и раздел III НКО в `fsbu4`; клиентский 1231 в `fsbu4`; упрощённые перечни обеих редакций (OQ-166). Добавлять ли их без образца | change `unified-statement-normalization`, ревью спеки K-5 (18.10.2026) | `arm_statements` (перечень кодов, структурное подтверждение неуверенного кода), ядро (`CODE_NOT_IN_EDITION`) | открыт: допущение — код без образца в перечень не входит; на скане такой неуверенный код не подтверждается структурой (`needs_review`, `needs_check`), у Excel и уверенного кода — только `info` ядра `CODE_NOT_IN_EDITION`. Пополнение — строкой в yaml с источником (файл fixtures) |
|
| OQ-168 | Коды упорядоченного перечня форм без образца в `fixtures/` (`contracts/statement-code-orders.yaml`, §11.9): в перечень вошли только коды шаблонов банка и коды печатных форм 1С и сканов, найденные в `fixtures/` (1105, 1130, 1140, 1215, 1231, 1530, 2420, 2460, 2530, 2930, повторы 1220 / 1520 / 2410 у `fsbu4`, раздел III НКО у `66n`). Не подтверждены образцом: 1120 в `fsbu4`; 1105, 1215, 2420 в `66n`; 2421, 2430, 2450 (прежние строки ОФР 66н); 2930 и раздел III НКО в `fsbu4`; клиентский 1231 в `fsbu4`; упрощённые перечни обеих редакций (OQ-166). Добавлять ли их без образца | change `unified-statement-normalization`, ревью спеки K-5 (18.10.2026) | `arm_statements` (перечень кодов, структурное подтверждение неуверенного кода), ядро (`CODE_NOT_IN_EDITION`) | открыт до появления образцов; **временное правило принято владельцем 18.10.2026 (Р-26):** коды без образца (2421 / 2430 / 2450 в `66n`, 1120 и другие в `fsbu4`) в перечень не входят; неуверенный такой код на скане не подтверждается структурой — `needs_review` строки, `needs_check`; у Excel и уверенного кода — только `info` ядра `CODE_NOT_IN_EDITION`. Пополнение — строкой в yaml с источником (файл fixtures), когда образец появится |
|
||||||
|
|||||||
@ -79,13 +79,20 @@ sync-statements` и цель `statements-check` сервисов появляю
|
|||||||
`--source <dir>`, ссылка — `origin/main` или `--ref <ref>`; хост — `--host arm-core|tabular-parser|scan-gateway` или
|
`--source <dir>`, ссылка — `origin/main` или `--ref <ref>`; хост — `--host arm-core|tabular-parser|scan-gateway` или
|
||||||
`--host-dir <dir>`. Без `--check` — раскладывает копию нужного коммита в новый worktree хоста на ветке фичи и пишет
|
`--host-dir <dir>`. Без `--check` — раскладывает копию нужного коммита в новый worktree хоста на ветке фичи и пишет
|
||||||
`VERSION`; issue и PR — как у `sync-web.sh`.
|
`VERSION`; issue и PR — как у `sync-web.sh`.
|
||||||
- **`--check`, коды возврата:** `0` — копия равна дереву `arm_statements/` коммита из `VERSION`, и это коммит `--ref`;
|
- **`--check`, коды возврата** (файл `VERSION` в сравнении не участвует — в источнике его нет): `0` — копия равна дереву
|
||||||
`10` — копия равна дереву коммита из `VERSION`, но в `--ref` есть более новый (копия отстала; предупреждение, не
|
`arm_statements/` коммита из `VERSION`, это коммит `--ref`, и копия перечня кодов равна docs; `10` — копия равна дереву
|
||||||
ошибка); `20` — копия отличается от дерева коммита из `VERSION` (прямая правка) или `VERSION` нет в истории источника,
|
коммита из `VERSION`, но в `--ref` есть более новый (копия отстала), или байтовая копия перечня кодов
|
||||||
либо байтовая копия перечня кодов `arm_statements/rules/statement_code_orders.yaml` отличается от
|
`arm_statements/rules/statement_code_orders.yaml` отличается от `docs/contracts/statement-code-orders.yaml` (перечень в
|
||||||
`docs/contracts/statement-code-orders.yaml`, либо (только `arm-core`) код словаря имён `api/rules/statement_lines.yaml`
|
docs правился раньше `arm-statements` или пересинхронизации хоста) — предупреждение, не ошибка; `20` — копия отличается
|
||||||
отсутствует в перечне своей редакции; `2` — нет checkout `arm-statements` или хоста, неизвестный аргумент. Файлы
|
от дерева коммита из `VERSION` (прямая правка), `VERSION` нет в истории источника, зависимости пакета (список
|
||||||
расхождения перечисляются в выводе.
|
`dependencies` его `pyproject`) отсутствуют в зависимостях хоста, либо (только `arm-core`) код словаря имён
|
||||||
|
`api/rules/statement_lines.yaml` отсутствует в перечне своей редакции копии пакета; `2` — нет checkout
|
||||||
|
`arm-statements` или хоста, неизвестный аргумент. Файлы расхождения перечисляются в выводе. Порядок правки перечня
|
||||||
|
кодов: PR в docs → PR в `arm-statements` → `sync-statements` в хостах («сначала ядро»); до конца цепочки хосты получают
|
||||||
|
`10`.
|
||||||
|
- **Конфиг линтеров и тесты пакета:** пакет проходит самый строгий конфиг `ruff` / `mypy` среди хостов (в своём
|
||||||
|
репозитории), потому что конфиг хоста применяется к копии, а править копию нельзя; тесты пакета — со своим
|
||||||
|
`conftest.py`, `autouse`-фикстуры `conftest` хоста ограничены каталогом `tests/` хоста.
|
||||||
- **`statements-check` в `make check-full`:** `../docs/scripts/sync-statements.sh --check --host-dir .` (docs — `../docs`
|
- **`statements-check` в `make check-full`:** `../docs/scripts/sync-statements.sh --check --host-dir .` (docs — `../docs`
|
||||||
или `ARM_KA_DOCS`); код `0` и `10` — цель зелёная (при `10` предупреждение печатается, в описание PR вписывается, что копия
|
или `ARM_KA_DOCS`); код `0` и `10` — цель зелёная (при `10` предупреждение печатается, в описание PR вписывается, что копия
|
||||||
отстала), `20` и `2` — цель красная. В `make check` и в пайплайн платформы не входит: тест, читающий соседний
|
отстала), `20` и `2` — цель красная. В `make check` и в пайплайн платформы не входит: тест, читающий соседний
|
||||||
@ -167,16 +174,20 @@ PR убедитесь, что `docs` и `fixtures` доступны (пропу
|
|||||||
| Цель | Что делает |
|
| Цель | Что делает |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `make venv` | `.venv` (Python 3.11, `uv`) с `jsonschema` и `PyYAML` — зависимостями скриптов (после обновления репозитория — перезапустить) |
|
| `make venv` | `.venv` (Python 3.11, `uv`) с `jsonschema` и `PyYAML` — зависимостями скриптов (после обновления репозитория — перезапустить) |
|
||||||
| `make validate-contracts` | `python scripts/validate_contracts.py`: примеры `contracts/examples/` против текущих схем JSON Schema 2020-12, примеры `contracts/examples/v1/` — против схем прежнего MAJOR `*.v1.schema.json` (окно перехода на контракт 2.0), форма упорядоченных перечней кодов `contracts/statement-code-orders.yaml`; код 1 при любой ошибке |
|
| `make validate-contracts` | `python scripts/validate_contracts.py`: примеры `contracts/examples/` против текущих схем JSON Schema 2020-12, примеры `contracts/examples/v1/` — против схем прежнего MAJOR `*.v1.schema.json` (окно перехода на контракт 2.0), форма упорядоченных перечней кодов `contracts/statement-code-orders.yaml` (коды — 4 цифры формы; в варианте `full` код стоит 1 + (вхождений в `repeats`) раз, в `simplified` — не чаще); код 1 при любой ошибке |
|
||||||
| `make validate-openspec` | `openspec validate slice-0-skeleton --strict` (другое изменение: `make validate-openspec CHANGE=<id>`) |
|
| `make validate-openspec` | `openspec validate slice-0-skeleton --strict` (другое изменение: `make validate-openspec CHANGE=<id>`) |
|
||||||
| `make check` | обе проверки выше |
|
| `make check` | обе проверки выше |
|
||||||
|
|
||||||
Без `make`: `python scripts/validate_contracts.py` (нужен `jsonschema`) и `openspec validate <id> --strict`.
|
Без `make`: `python scripts/validate_contracts.py` (нужен `jsonschema`) и `openspec validate <id> --strict`.
|
||||||
Менять `contracts/**` нужно осознанно: байтовые копии схем лежат в `api/contracts/` каждого сервиса, после правки их
|
Менять `contracts/**` нужно осознанно: байтовые копии схем лежат в `api/contracts/` каждого сервиса, после правки их
|
||||||
нужно пересинхронизировать (`cp`), иначе тест идентичности в сервисах упадёт. На окне перехода между MAJOR (сейчас 1 → 2)
|
нужно пересинхронизировать (`cp`), иначе тест идентичности в сервисах упадёт. Правило теста идентичности (у ядра и
|
||||||
тест идентичности сервиса сверяет свою копию со схемой **того MAJOR, который сервис пишет**: копия с `$id` `…/v1` — с
|
сервисов одно, верно до, во время и после окна перехода между MAJOR — сейчас 1 → 2): схема копии с `$id` `…/v<M>`
|
||||||
`*.v1.schema.json`, иначе — с основным файлом (`openspec/specs/recognition-contract`, требование «Копия контракта в
|
сверяется с `<имя>.v<M>.schema.json`, **если такой файл есть в docs**, иначе с основным (у `recognition-request` файла
|
||||||
сервисе идентична источнику»). Ядро держит копии обоих MAJOR. Перечень кодов `contracts/statement-code-orders.yaml`
|
`.v1` нет — копия v1.0 сверяется с основным); пример `examples/<имя>.json` копии — с `examples/v<M>/<имя>.json`, если он
|
||||||
|
есть, иначе с `examples/<имя>.json` (`M` — MAJOR копии схемы этого вида, вид — по префиксу имени); прочие файлы копии
|
||||||
|
(`*.v<N>.schema.json`, `examples/v<N>/` у ядра) — с одноимёнными; файлы docs другого MAJOR сервис не сверяет. Правило
|
||||||
|
вводится во все три репозитория до слияния контракта v2.0 в docs (шаг 0 выкладки; спека `recognition-contract`,
|
||||||
|
требование «Копия контракта в сервисе идентична источнику»). Ядро держит копии обоих MAJOR. Перечень кодов `contracts/statement-code-orders.yaml`
|
||||||
копируется не в сервисы, а в пакет `arm_statements` (сверяет `sync-statements --check`).
|
копируется не в сервисы, а в пакет `arm_statements` (сверяет `sync-statements --check`).
|
||||||
|
|
||||||
## Порядок перед PR
|
## Порядок перед PR
|
||||||
|
|||||||
@ -416,8 +416,9 @@ UI прячет элементы по `GET /me → permissions` (объедин
|
|||||||
> **Пока не работает на `main`.** Раздел описывает API после change `unified-statement-normalization`
|
> **Пока не работает на `main`.** Раздел описывает API после change `unified-statement-normalization`
|
||||||
> (RSHB/arm-ka-docs#116; контракт распознавания v2.0, DBML v5.19; дочерние PR arm-core и arm-web ещё не созданы).
|
> (RSHB/arm-ka-docs#116; контракт распознавания v2.0, DBML v5.19; дочерние PR arm-core и arm-web ещё не созданы).
|
||||||
> Правила — `context/05-domain-rules.md` §7, §9.3, §9.5 пп. 2, 4, 9, §11; экран — спека `arm-web` того же change.
|
> Правила — `context/05-domain-rules.md` §7, §9.3, §9.5 пп. 2, 4, 9, §11; экран — спека `arm-web` того же change.
|
||||||
> Поля добавляются, старые не удаляются: клиент, написанный по §5.6, продолжит читать ответ. После слияния PR раздел
|
> В `…/statement` поля добавляются, старые не удаляются: клиент, написанный по §5.6, продолжит читать ответ.
|
||||||
> переедет в §5.6 и §1.
|
> **Исключение — карточка файла:** `FileOut.job.warnings` меняет тип со строк на объекты (ниже, «Карточка файла»), это
|
||||||
|
> несовместимо — экран файлов обновляется в той же выкладке, что и ядро. После слияния PR раздел переедет в §5.6 и §1.
|
||||||
|
|
||||||
**`GET /documents/{id}/statement` — новые поля `StatementOut`:**
|
**`GET /documents/{id}/statement` — новые поля `StatementOut`:**
|
||||||
|
|
||||||
@ -438,7 +439,9 @@ UI прячет элементы по `GET /me → permissions` (объедин
|
|||||||
словарь «код → строка», ключ строки — `id` (у достроенных шаблонных — `line_code`, их у кода с реальной строкой `ok` нет).
|
словарь «код → строка», ключ строки — `id` (у достроенных шаблонных — `line_code`, их у кода с реальной строкой `ok` нет).
|
||||||
- `values[]` — `text` (сырой текст источника; в подсказку ячейки вместе с `confidence`), `needs_review`, `is_confirmed`.
|
- `values[]` — `text` (сырой текст источника; в подсказку ячейки вместе с `confidence`), `needs_review`, `is_confirmed`.
|
||||||
**Подсветка ячейки — только по `needs_review`**; по `confidence < 100` и `is_recognized = false` больше не подсвечивать
|
**Подсветка ячейки — только по `needs_review`**; по `confidence < 100` и `is_recognized = false` больше не подсвечивать
|
||||||
(сервис уже свёл их с проверками §7: догадка, подтверждённая сошедшимся соотношением, не подсвечивается).
|
(сервис уже свёл их с проверками §7: догадка, подтверждённая сошедшимся соотношением, не подсвечивается). У значений
|
||||||
|
строк `detail` `needs_review` всегда `false` (расшифровки статус не держат) — подсказку с `text` и уверенностью
|
||||||
|
показывать и у них.
|
||||||
- `issues[]` — предупреждения и нарушения документа: `{id, kind: "warning" | "relation" | "soft", code, severity: "error" |
|
- `issues[]` — предупреждения и нарушения документа: `{id, kind: "warning" | "relation" | "soft", code, severity: "error" |
|
||||||
"info", message, line_ids[], column_id?, column_index?, field?, source: "service" | "core", relation?, codes?, expected?,
|
"info", message, line_ids[], column_id?, column_index?, field?, source: "service" | "core", relation?, codes?, expected?,
|
||||||
actual?}`; показывать у места (строки, ячейки, поле шапки) или у документа; `error` отличать от `info`. `column_index` без
|
actual?}`; показывать у места (строки, ячейки, поле шапки) или у документа; `error` отличать от `info`. `column_index` без
|
||||||
@ -456,7 +459,7 @@ UI прячет элементы по `GET /me → permissions` (объедин
|
|||||||
| Часть | Тело | Эффект | Ошибки |
|
| Часть | Тело | Эффект | Ошибки |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| подтвердить ячейку / снять подтверждение | `values: [{line_id, column_id, confirmed: true \| false}]` | `is_confirmed`, `needs_review = false` (при `false` — пересчитывается), сумма не меняется, акцепт не сбрасывается | 422 `UNKNOWN_CELL` |
|
| подтвердить ячейку / снять подтверждение | `values: [{line_id, column_id, confirmed: true \| false}]` | `is_confirmed`, `needs_review = false` (при `false` — пересчитывается), сумма не меняется, акцепт не сбрасывается | 422 `UNKNOWN_CELL` |
|
||||||
| дата колонки | `columns: [{column_id, period_date}]` (дата обязательна) | дата колонки, `is_current`, период документа; снимает `COLUMN_WITHOUT_DATE` / `COLUMN_DATE_UNCERTAIN` | 422 `UNKNOWN_COLUMN`; `period_date: null` — 422 массивом |
|
| дата колонки | `columns: [{column_id, period_date}]` (дата обязательна) | дата колонки, `is_current`, период документа, `period_kind = absolute` (правка даты отчёта её больше не пересчитывает); снимает `COLUMN_WITHOUT_DATE` / `COLUMN_DATE_UNCERTAIN`; та же дата у колонки с таким предупреждением — подтверждение (акцепт не сбрасывает) | 422 `UNKNOWN_COLUMN`; `period_date: null` — 422 массивом |
|
||||||
| назначить / сменить код | `lines: [{line_id, line_code}]` | `unreadable` / `missing` / `detail` / `ok` → `ok` с этим кодом, `line_code_confidence = 100` | 422 `INVALID_LINE_CODE` (не 4–5 цифр), 409 `LINE_CODE_TAKEN` (код занят строкой, созданной правкой, с суммами — сначала очистить её) |
|
| назначить / сменить код | `lines: [{line_id, line_code}]` | `unreadable` / `missing` / `detail` / `ok` → `ok` с этим кодом, `line_code_confidence = 100` | 422 `INVALID_LINE_CODE` (не 4–5 цифр), 409 `LINE_CODE_TAKEN` (код занят строкой, созданной правкой, с суммами — сначала очистить её) |
|
||||||
| отметить расшифровкой | `lines: [{line_id, line_status: "detail"}]` | строка выходит из сумм и не блокирует подтверждение; обратно — только назначением кода | 422 `LINE_CODE_REQUIRED` (`line_status: "ok"` — в `ok` переводит только `line_code`) |
|
| отметить расшифровкой | `lines: [{line_id, line_status: "detail"}]` | строка выходит из сумм и не блокирует подтверждение; обратно — только назначением кода | 422 `LINE_CODE_REQUIRED` (`line_status: "ok"` — в `ok` переводит только `line_code`) |
|
||||||
| подтвердить код / снять подтверждение | `lines: [{line_id, confirmed: true \| false}]` | `is_confirmed`, `needs_review` строки = `false` (при `false` — пересчитывается) | — |
|
| подтвердить код / снять подтверждение | `lines: [{line_id, confirmed: true \| false}]` | `is_confirmed`, `needs_review` строки = `false` (при `false` — пересчитывается) | — |
|
||||||
@ -476,9 +479,17 @@ line_code_text}` — строки `unreadable` / `missing`, у которых в
|
|||||||
(`detail.document_id` — в этом файле уже подтверждена форма того же типа, года и квартала) → `CHECKS_NOT_PASSED`.
|
(`detail.document_id` — в этом файле уже подтверждена форма того же типа, года и квартала) → `CHECKS_NOT_PASSED`.
|
||||||
|
|
||||||
**Статус документа.** Форма из OCR может стать `RECOGNIZED` (и сразу автоподтвердиться), если сервис всё подтвердил
|
**Статус документа.** Форма из OCR может стать `RECOGNIZED` (и сразу автоподтвердиться), если сервис всё подтвердил
|
||||||
проверками; на текущих реальных сканах это редкость — `NEEDS_CHECK` остаётся, когда есть что смотреть: подсвеченные ячейки
|
проверками: сомнительные ячейки расшифровок (`detail`) статус не держат, поэтому чистые сканы 1С (например, ББ и ОФР за
|
||||||
и строки, `issues` уровня `error`, неразобранные строки. Две формы одного типа в одном файле приходят двумя документами;
|
1 кв. 2025 в fixtures) могут прийти `RECOGNIZED`. `NEEDS_CHECK` остаётся, когда есть что смотреть: подсвеченные ячейки и
|
||||||
ни одна не автоподтверждается.
|
строки, `issues` уровня `error`, неразобранные строки. Две формы одного типа и периода в одном файле приходят двумя
|
||||||
|
документами; ни одна не автоподтверждается.
|
||||||
|
|
||||||
|
**Карточка файла и документа (несовместимое изменение).** `FileOut.job.warnings` — список объектов `{code, severity:
|
||||||
|
"error" | "info", message, locator}` вместо строк (у заданий до выкладки ядро отдаёт строки, обёрнутые в `{code:
|
||||||
|
"LEGACY_WARNING", severity: "info", message, locator: null}`); показывать `message` и `locator`, `error` отличать от
|
||||||
|
`info`. `FileOut.job.mapping_warnings` — по-прежнему строки. `DocumentOut.issues[]` — те же записи, что `issues` формы,
|
||||||
|
у любого документа, в том числе ОСВ (предупреждения листа `OSV_*`); `DocumentOut.warnings` (строки маппинга) остаётся на
|
||||||
|
окно перехода.
|
||||||
|
|
||||||
**ГСК/ГСЗ.** `crm_id` в `POST …/related-groups` и `PATCH /directories/related-groups/{id}` сохраняется и возвращается в
|
**ГСК/ГСЗ.** `crm_id` в `POST …/related-groups` и `PATCH /directories/related-groups/{id}` сохраняется и возвращается в
|
||||||
`RelatedGroupOut` / `RelatedGroupDetail`; повтор — 409 `CRM_ID_EXISTS` (arm-core#112, DBML v5.19).
|
`RelatedGroupOut` / `RelatedGroupDetail`; повтор — 409 `CRM_ID_EXISTS` (arm-core#112, DBML v5.19).
|
||||||
@ -733,7 +744,7 @@ async function download(path: string, fallbackName: string): Promise<void> {
|
|||||||
| Задача | Что поменяется для фронта | Как подготовиться |
|
| Задача | Что поменяется для фронта | Как подготовиться |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| OQ-54 — SSO РАИСА | Меняется только получение токена (вместо dev-формы) | держать слой получения токена изолированным (`frontend-auth-guide.md` §9) |
|
| OQ-54 — SSO РАИСА | Меняется только получение токена (вместо dev-формы) | держать слой получения токена изолированным (`frontend-auth-guide.md` §9) |
|
||||||
| arm-ka-docs#116 — единая нормализация ББ/ОФР (контракт v2.0, DBML v5.19) | `GET/PATCH …/statement`: строки со статусами и повторами кода, `needs_review`, `issues[]`, `lines`/`columns`/`confirmed` в PATCH; подтверждение — 409 `LINES_UNRESOLVED`, `DUPLICATE_FORM`; `crm_id` ГСК/ГСЗ сохраняется | §5.6.1; не держать словарь «код → строка», подсветку строить по `needs_review` |
|
| arm-ka-docs#116 — единая нормализация ББ/ОФР (контракт v2.0, DBML v5.19) | `GET/PATCH …/statement`: строки со статусами и повторами кода, `needs_review`, `issues[]`, `lines`/`columns`/`confirmed` в PATCH; подтверждение — 409 `LINES_UNRESOLVED`, `DUPLICATE_FORM`; `crm_id` ГСК/ГСЗ сохраняется; **`FileOut.job.warnings` — объекты вместо строк**, `DocumentOut.issues[]` | §5.6.1; не держать словарь «код → строка», подсветку строить по `needs_review`; тип `JobBrief.warnings` в `types/api.ts` поменять вместе с выкладкой ядра |
|
||||||
|
|
||||||
Состояние этих задач — в Gitea (`RSHB/arm-core`, `RSHB/arm-ka-docs`); после слияния каждой этот раздел переезжает в §1.
|
Состояние этих задач — в Gitea (`RSHB/arm-core`, `RSHB/arm-ka-docs`); после слияния каждой этот раздел переезжает в §1.
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user