docs(context): Срез 3a — раскладка горячих папок, §11.1 п. 6/9/10, OQ по редакциям и очистке, compose (#46)
- 03-architecture: scan-gateway — .part остаётся в Tmp, очистка под SCAN_CLEANUP, OcrNormalizer, эмулятор по sha256 - 05-domain-rules §11.1: п. 1 (двойники только для дат), п. 4 (повтор кода, пропуск строк с мусорным кодом), п. 6 (свой statement_lines.yaml с редакциями), п. 7 (string_value, скобки, тире), п. 9 (BACKEND_PARSING_ERROR на таблицу), новый п. 10 «Редакции форм» - 07-open-questions: OQ-137 уточнён; OQ-12, OQ-139 дополнены; новые OQ-144…OQ-151 - 09-delivery-plan: Срез 3 разделён на 3a (scan-gateway) и 3b (arm-core) - dev: SCAN_CLEANUP и SCAN_EMULATOR_DEFAULT в compose, комментарий про <uuid>.<ext> и пары по sha256; qa-guide §6.8 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
This commit is contained in:
parent
830d0ce50c
commit
a3f33fbe40
@ -34,13 +34,19 @@
|
|||||||
`docs/contracts/recognition-result.schema.json` (контракт v1.2: `DISCLOSURE` в `document_type`, `statementDocument.checks`,
|
`docs/contracts/recognition-result.schema.json` (контракт v1.2: `DISCLOSURE` в `document_type`, `statementDocument.checks`,
|
||||||
границы периода ОСВ `period_start/period_end/is_from_year_start`). Правила распознавания
|
границы периода ОСВ `period_start/period_end/is_from_year_start`). Правила распознавания
|
||||||
заголовков/группировок — `rules/*.yaml`.
|
заголовков/группировок — `rules/*.yaml`.
|
||||||
- **scan-gateway** — адаптер ИС Сканирование: положить файл в горячую папку `Input/<job_id>.<ext>` (временный
|
- **scan-gateway** — адаптер ИС Сканирование: положить файл в горячую папку `Input/<job_id>.<ext>` (расширение —
|
||||||
`<имя>.<uuid>.tmp` рядом, затем атомарный rename; ИС игнорирует `*.tmp`), дождаться батча `Output/<job_id>/*.json`
|
суффикс `original_name` в нижнем регистре; временный `.part` пишется в `Tmp`, затем публикуется во входящий каталог
|
||||||
(появляется целиком и больше не меняется), сохранить сырой ответ в S3 `raw/{job_id}.json` (в БД не регистрируется),
|
без замены; временный файл остаётся в `Tmp`, в `Input` временных файлов не кладём — решение владельца 06.10.2026),
|
||||||
очистить `Output/<job_id>/` и входящий файл, нормализовать в тот же контракт результата, опубликовать
|
дождаться набора `Output/<job_id>/*.json` (по одному json на документ; набор готов, когда имена и размеры не менялись
|
||||||
`recognition.result`. Таймаут 5 мин → `timeout`. Раскладка папок — как у проверенного в контуре PDFWorker старого АРМ
|
`SCAN_SETTLE_SECONDS`), сохранить склеенный сырой ответ в S3 `raw/{job_id}.json` (в БД не регистрируется), очистить
|
||||||
(`RSHB/armka_file_worker`, решение владельца 06.10.2026, OQ-137); текущий код шлюза именует файл `<job_id>_<original_name>`
|
`Output/<job_id>/` и входящий файл — **только под флагом `SCAN_CLEANUP`, по умолчанию выключен** (права на удаление
|
||||||
и пишет временный файл в `Tmp/` — приводится к эталону в arm-scan-gateway #6.
|
на SMB-шаре контура не подтверждены, OQ-144), нормализовать в тот же контракт результата (`OcrNormalizer` по
|
||||||
|
`05-domain-rules.md` §11/§11.1: один `statementDocument` на таблицу ответа, справочники `api/rules/` с редакциями
|
||||||
|
форм), опубликовать `recognition.result`. Таймаут 5 мин → `TIMEOUT`. Раскладка папок — как у проверенного в контуре
|
||||||
|
PDFWorker старого АРМ (`RSHB/armka_file_worker`, решение владельца 06.10.2026, OQ-137) с двумя отличиями: временный
|
||||||
|
файл лежит в `Tmp`, а не рядом с назначением, очистка под флагом. Эмулятор ИС Сканирование (dev) ищет пару PDF + JSON
|
||||||
|
по sha256 содержимого входящего файла, а не по имени. Реализация — arm-scan-gateway #6 (change
|
||||||
|
`slice-3a-ocr-normalizer`).
|
||||||
- **Frontend** — React + TS по шаблону команды; экраны по макету `dkb_24.09.26` (справочник ГК, консолидации,
|
- **Frontend** — React + TS по шаблону команды; экраны по макету `dkb_24.09.26` (справочник ГК, консолидации,
|
||||||
первичные документы, проверка распознавания, сводная ОСВ, шахматка, корректировки, гудвилл, КФО, ЕХ, журнал, роли).
|
первичные документы, проверка распознавания, сводная ОСВ, шахматка, корректировки, гудвилл, КФО, ЕХ, журнал, роли).
|
||||||
|
|
||||||
@ -382,8 +388,9 @@ JetStream** (3 subject `recognition.*`; аутентификация nkeys/JWT,
|
|||||||
FileWorker — `fastapi-dkb-armka-pdf-worker`); наши сервисы держат `ROOT_PATH = /` по умолчанию и получают префикс из
|
FileWorker — `fastapi-dkb-armka-pdf-worker`); наши сервисы держат `ROOT_PATH = /` по умолчанию и получают префикс из
|
||||||
окружения. Healthcheck `GET /healthcheck` отвечает `503`, если NATS отключён или воркер остановлен — тот же принцип,
|
окружения. Healthcheck `GET /healthcheck` отвечает `503`, если NATS отключён или воркер остановлен — тот же принцип,
|
||||||
что у нас.
|
что у нас.
|
||||||
- Горячие папки ИС Сканирование: эталон — раскладка FileWorker (`Input/<job_id>.<ext>`, `*.tmp` рядом,
|
- Горячие папки ИС Сканирование: эталон — раскладка FileWorker (`Input/<job_id>.<ext>`, `Output/<job_id>/*.json`,
|
||||||
`Output/<job_id>/*.json`, очистка после чтения), см. описание scan-gateway выше и OQ-137.
|
очистка после чтения). Уточнение 06.10.2026: временный `.part` остаётся в `Tmp` (в `Input` временных файлов не
|
||||||
|
класть), очистка — под `SCAN_CLEANUP` (по умолчанию выключена); см. описание scan-gateway выше, OQ-137, OQ-144.
|
||||||
- FileWorker **не** реализует наш контракт DECISION-4: subject'ы `files.*`, конверт `task_id`/`file_id`/`source_s3_key`/
|
- FileWorker **не** реализует наш контракт DECISION-4: subject'ы `files.*`, конверт `task_id`/`file_id`/`source_s3_key`/
|
||||||
`expires_at` → `status ready|timeout|error` + `error_code`, без ack `processing`, без `Nats-Msg-Id`, без queue group
|
`expires_at` → `status ready|timeout|error` + `error_code`, без ack `processing`, без `Nats-Msg-Id`, без queue group
|
||||||
(один экземпляр), дедуп только в памяти процесса — доставка at-most-once. Это параллельный сервис старого АРМ, а не
|
(один экземпляр), дедуп только в памяти процесса — доставка at-most-once. Это параллельный сервис старого АРМ, а не
|
||||||
|
|||||||
@ -780,6 +780,10 @@ OQ-92 (пустые «Организация»/«Группа компаний»
|
|||||||
`l/I/|/і → 1`, `б → 6`, `S/s → 5`, `Ч → 4` — заменяются **только если символ стоит вплотную к цифре**, в несколько
|
`l/I/|/і → 1`, `б → 6`, `S/s → 5`, `Ч → 4` — заменяются **только если символ стоит вплотную к цифре**, в несколько
|
||||||
проходов («2ОО5» → «2005»); слова («Общество») не портятся. Если первая строка значения состоит только из цифр,
|
проходов («2ОО5» → «2005»); слова («Общество») не портятся. Если первая строка значения состоит только из цифр,
|
||||||
пробелов, `|`, точек и двоеточий — `|` считается разделителем клеток формы («2|0|2|4|» → «2 0 2 4»), а не единицей.
|
пробелов, `|`, точек и двоеточий — `|` считается разделителем клеток формы («2|0|2|4|» → «2 0 2 4»), а не единицей.
|
||||||
|
**Применяется только к датам** — дате отчёта (шапка) и подписям колонок значений; коды строк очищаются п. 6
|
||||||
|
(`clean_value`), суммы — п. 7 (`parse_amount`), ИНН — только цифры (п. 8). Замена двойников внутри кодов и сумм на
|
||||||
|
стенде не проверялась, поэтому не делается (решение №22 спеки 3a): код с латинской `O` вместо нуля не распознаётся
|
||||||
|
как целое и остаётся только в `raw/`.
|
||||||
2. **Даты из OCR-текста** (`date_utils._parse_date`): сначала текстовые формы по всей строке в порядке приоритета —
|
2. **Даты из OCR-текста** (`date_utils._parse_date`): сначала текстовые формы по всей строке в порядке приоритета —
|
||||||
диапазон «с 01.01.2025 по 30.09.2025» (берётся конец), «30 сентября 2025 г./r./года» (латинская `r` вместо `г`),
|
диапазон «с 01.01.2025 по 30.09.2025» (берётся конец), «30 сентября 2025 г./r./года» (латинская `r` вместо `г`),
|
||||||
«I квартал 2026» / «1-й кв.» / «первый квартал», «1 полугодие» / «1 п/г», «9 месяцев 2025», «январь – сентябрь 2025»
|
«I квартал 2026» / «1-й кв.» / «первый квартал», «1 полугодие» / «1 п/г», «9 месяцев 2025», «январь – сентябрь 2025»
|
||||||
@ -800,18 +804,31 @@ OQ-92 (пустые «Организация»/«Группа компаний»
|
|||||||
ядре, но документ без шапки всё равно отдавать `needs_check` с причиной в `metrics.warnings` — колонки без `period_date`
|
ядре, но документ без шапки всё равно отдавать `needs_check` с причиной в `metrics.warnings` — колонки без `period_date`
|
||||||
аналитик подтверждает руками.
|
аналитик подтверждает руками.
|
||||||
4. **Классы строк** (`_classify_row`): пустой код → строка пропускается; нечисловой код → заголовок (не данные);
|
4. **Классы строк** (`_classify_row`): пустой код → строка пропускается; нечисловой код → заголовок (не данные);
|
||||||
наименование «Баланс» → `BALANCE`, начинается с «итого» → `TOTAL`, иначе обычная строка. Берутся только строки с кодом
|
наименование «Баланс» → `BALANCE`, начинается с «итого» → `TOTAL`, иначе обычная строка. Берутся только строки с
|
||||||
(как в §11 выше); расшифровки без кода — OQ-36.
|
целым кодом (как в §11 выше); расшифровки без кода — OQ-36. Строка с **непустым нечисловым** (`1150 :`, `211(1 |`) или
|
||||||
|
**пустым** кодом пропускается, как в источнике; на момент Среза 3a **предупреждения об этом нет** (строка с суммами,
|
||||||
|
но с мусорным кодом, теряется молча и остаётся только в `raw/`) — OQ-149. **Повтор кода** в таблице: остаётся первая
|
||||||
|
строка, остальные не попадают в документ, выдаётся предупреждение «код N повторяется: оставлена первая строка», документ
|
||||||
|
`needs_check` (как §12 п. 5: в БД `financial_statement_line` уникален по `(meta_id, line_code)`; старое ядро дубли не
|
||||||
|
убирало — решение №15 спеки 3a).
|
||||||
5. **Тип таблицы** (`_resolve_table_type`): по первой цифре кодов строк данных — больше `1xxx` → BALANCE, больше `2xxx` →
|
5. **Тип таблицы** (`_resolve_table_type`): по первой цифре кодов строк данных — больше `1xxx` → BALANCE, больше `2xxx` →
|
||||||
INCOME; это запасной путь, когда `label.form_name` зашумлён; при расхождении с `label` — `needs_check`.
|
INCOME; это запасной путь, когда `label.form_name` зашумлён; при расхождении с `label` — `needs_check`.
|
||||||
6. **Код и наименование строки**: код очищается до букв, цифр, пробела и точки (`_clear_value`: переносы строк и
|
6. **Код и наименование строки**: код очищается до букв, цифр, пробела и точки (`_clear_value`: переносы строк и
|
||||||
табуляции → пробел, остальное выбрасывается); наименование берётся **из справочника по коду** (`consts.codes_mapping`,
|
табуляции → пробел, остальное выбрасывается); наименование берётся **из справочника по коду** (`consts.codes_mapping`;
|
||||||
у нас — `rules/statement_lines.yaml` из §12 п. 2), а из OCR — только если код не найден в справочнике (`line_code_confidence
|
у нас — `api/rules/statement_lines.yaml` **scan-gateway** с редакциями форм `66n` / `fsbu4`, см. п. 10, а не
|
||||||
< 100`). Код считается распознанным (`line_code_recognized`), если `confidence == 100`, очищенный текст равен сырому и код
|
`rules/statement_lines.yaml` из §12 — в tabular-parser такого файла нет), а из OCR — только если кода нет в справочнике
|
||||||
|
выбранной редакции (`line_code_confidence < 100`, не выше 99). Код считается распознанным (`line_code_recognized`), если `confidence == 100`, очищенный текст равен сырому и код
|
||||||
есть в справочнике.
|
есть в справочнике.
|
||||||
7. **Суммы** (`_parse_amount`): запятая → точка, пробелы убираются; содержимое скобок — отрицательное; из строки
|
7. **Суммы** (`_parse_amount`): запятая → точка, пробелы убираются; содержимое скобок — отрицательное; из строки
|
||||||
удаляется всё, кроме цифр, `+`, `-`, `.`; несколько точек — разделители тысяч, остаётся последняя; `Decimal`, иначе
|
удаляется всё, кроме цифр, `+`, `-`, `.`; несколько точек — разделители тысяч, остаётся последняя; `Decimal`, иначе
|
||||||
`None`. Прочерк, «–», «—», пусто — пустое значение (NULL, не 0). `string_value` — очищенный текст (`_clear_value`).
|
`None`. Прочерк, «–», «—», пусто — пустое значение (NULL, не 0). `string_value` — текст ячейки как распознан
|
||||||
|
(`\n`/`\t` → пробел), **только если сумма не разобрана** (`amount = null`) и ячейка не пуста и не прочерк (DBML:
|
||||||
|
«как распознано, если не число»); у разобранных сумм — `null` (решение №16 спеки 3a).
|
||||||
|
**Перенесено дословно (расхождения с желаемым — OQ-147, OQ-148):** закрывающая скобка, распознанная как `}` или `]`
|
||||||
|
(`(7744536}`), минус не ставит — сумма положительная, `is_recognized = false` (решение №12); длинное/среднее тире
|
||||||
|
«—»/«–» при `confidence = 100` даёт `is_recognized = false` (тире вырезается как «лишний» символ), дефис «-» — `true`
|
||||||
|
(решение №20). Скобки сами `is_recognized` не портят: признак считается до постановки минуса (решение №13; в источнике
|
||||||
|
любая сумма в скобках получала `false`).
|
||||||
**`is_recognized`** значения = `confidence == 100` **и** разбор суммы не потребовал выбрасывать символы (сырой текст без
|
**`is_recognized`** значения = `confidence == 100` **и** разбор суммы не потребовал выбрасывать символы (сырой текст без
|
||||||
скобок равен очищенному). Для ячеек шапки: `is_recognized` = `confidence == 100` и в тексте не было мусора; если дата
|
скобок равен очищенному). Для ячеек шапки: `is_recognized` = `confidence == 100` и в тексте не было мусора; если дата
|
||||||
разобралась — `period_text` нормализуется в «На ДД.ММ.ГГГГ», `period_date` — результат п. 2. Ячейки под колонкой
|
разобралась — `period_text` нормализуется в «На ДД.ММ.ГГГГ», `period_date` — результат п. 2. Ячейки под колонкой
|
||||||
@ -819,9 +836,21 @@ OQ-92 (пустые «Организация»/«Группа компаний»
|
|||||||
8. **Шапка документа**: ИНН — только цифры из `inn.form_name`, «распознан», если `confidence == 100` и в сыром тексте не
|
8. **Шапка документа**: ИНН — только цифры из `inn.form_name`, «распознан», если `confidence == 100` и в сыром тексте не
|
||||||
было ничего, кроме цифр; единицы — `_clear_value`, тот же критерий; дата отчёта — п. 2, «распознана», если
|
было ничего, кроме цифр; единицы — `_clear_value`, тот же критерий; дата отчёта — п. 2, «распознана», если
|
||||||
`confidence == 100` и дата разобралась. Нераспознанное → `needs_check` (как в §11).
|
`confidence == 100` и дата разобралась. Нераспознанное → `needs_check` (как в §11).
|
||||||
9. **Ошибка разбора одного документа** (исключение в разборе `data_ocr`) не роняет файл: документ помечается ошибкой
|
9. **Ошибка разбора одной таблицы** (исключение в разборе `table_structure[i]`) не роняет файл: выдаётся предупреждение
|
||||||
(`BACKEND_PARSING_ERROR` + текст исключения), остальные документы файла обрабатываются; хотя бы один документ есть —
|
уровня таблицы `BACKEND_PARSING_ERROR: <класс исключения>: <сообщение>`, остальные таблицы файла разбираются;
|
||||||
конверт `needs_check`, ни одного — `error` (в терминах `recognition-result-message`).
|
хотя бы один документ есть — конверт `needs_check` (статус не бывает `done`, пока есть предупреждение уровня таблицы),
|
||||||
|
ни одной таблицы не разобрано — `error` с кодом `RECOGNITION_FAILED` («ни одной таблицы не разобрано», причины — в
|
||||||
|
`metrics.warnings`). Ошибочный файл набора ответа при успешном соседе (например, `<job_id>_ofr.json` с
|
||||||
|
`INVALID_DOCUMENT`) документы успешных файлов не отменяет: ошибка попадает в `metrics.warnings` строкой
|
||||||
|
`<имя json>: <код ИС>: <сообщение>`, статус результата — не выше `needs_check`, если часть файлов набора ошибочна.
|
||||||
|
10. **Редакции форм** (`api/rules/statement_lines.yaml`): `66n` (Приказ Минфина № 66н, отчётность по 2024 год включительно)
|
||||||
|
и `fsbu4` (ФСБУ 4/2023, с 2025 года; **пока пуста** — состав кодов даёт владелец, OQ-150). Редакция документа выбирается
|
||||||
|
**по дате отчёта** шапки, если её нет — по **самой поздней `period_date`** колонок значений, если нет и её — по
|
||||||
|
**числу совпадений кодов** строк со словарями редакций (с предупреждением «редакция определена по кодам»). Имя строки —
|
||||||
|
из словаря выбранной редакции по коду, из OCR — только если кода в нём нет. **Пустая** выбранная редакция → имена строк из
|
||||||
|
OCR, одно предупреждение «редакция `<id>` не заполнена», `needs_check`. Предупреждение «код N из редакции X»
|
||||||
|
(код есть в другой редакции, но не в выбранной) выдаётся только для **непустой** выбранной редакции — иначе каждая
|
||||||
|
строка формы 2025 года давала бы шум (решение №23 спеки 3a). Редакция формы в конверт не передаётся (OQ-145).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@ -17,7 +17,7 @@
|
|||||||
| OQ-09 | Правила элиминации ОФР (2110/2120/2310/2320/2330): точный состав субсчетов, источник средневзвешенной ставки НДС, выбор субсчетов «работником ПА самостоятельно» | FNR03 | срез 6 | не реализовывать авто-элиминацию ОФР до решения; только ручные `correction` |
|
| OQ-09 | Правила элиминации ОФР (2110/2120/2310/2320/2330): точный состав субсчетов, источник средневзвешенной ставки НДС, выбор субсчетов «работником ПА самостоятельно» | FNR03 | срез 6 | не реализовывать авто-элиминацию ОФР до решения; только ручные `correction` |
|
||||||
| OQ-10 | Гудвилл: входит ли стр. 1350 (добавочный капитал) в базу участия; УК из ББ (1310) или из ОСВ 80/83 | DBML, DOC-259025, FNR03 | срез 6 | допущение: 1310 из ББ; 1350 — настраиваемый флаг, по умолчанию выключен |
|
| OQ-10 | Гудвилл: входит ли стр. 1350 (добавочный капитал) в базу участия; УК из ББ (1310) или из ОСВ 80/83 | DBML, DOC-259025, FNR03 | срез 6 | допущение: 1310 из ББ; 1350 — настраиваемый флаг, по умолчанию выключен |
|
||||||
| OQ-11 | Строка «ППА» в КФО (правило № 1, когда у А нет ОСВ 03) — что это и куда | FNR03 | срез 6 | не реализовывать |
|
| OQ-11 | Строка «ППА» в КФО (правило № 1, когда у А нет ОСВ 03) — что это и куда | FNR03 | срез 6 | не реализовывать |
|
||||||
| OQ-12 | Формулы ОФР: знаки 2410/2420/2430 по ФСБУ 4/2023 vs 66н; состав 1100 (1120–1140) | DOC-221281 | срез 3 | допущение: формулы §7, строки без значения = 0 |
|
| OQ-12 | Формулы ОФР: знаки 2410/2420/2430 по ФСБУ 4/2023 vs 66н; состав 1100 (1120–1140) | DOC-221281 | срез 3 | допущение: формулы §7, строки без значения = 0; **расширение 07.10.2026:** формулы и справочники по редакциям форм (`66n` / `fsbu4`) — OQ-146 |
|
||||||
| OQ-13 | `confidence` от ИС Сканирование — доли 0..1 или проценты 0..100; имя поля организации (`full_name` vs `form_name`) | FNR02 vs `aep_*.json` | срез 3 (scan-gateway) | **закрыт для scan-gateway (04.10.2026, по `aep_3.json`, `aep_6.json`):** `confidence` — числа 0–100 (не доли); название организации — `data_ocr.organization.form_name` (шумный OCR-текст), ИНН — `inn.form_name`, единицы — `units.form_name`, дата — `date.form_name` (в `aep_6.json` мусор); `full_name`/`inn.inn`/`units.measurement` из ТЗ в реальных файлах нет. Нормализация — Срез 3 |
|
| OQ-13 | `confidence` от ИС Сканирование — доли 0..1 или проценты 0..100; имя поля организации (`full_name` vs `form_name`) | FNR02 vs `aep_*.json` | срез 3 (scan-gateway) | **закрыт для scan-gateway (04.10.2026, по `aep_3.json`, `aep_6.json`):** `confidence` — числа 0–100 (не доли); название организации — `data_ocr.organization.form_name` (шумный OCR-текст), ИНН — `inn.form_name`, единицы — `units.form_name`, дата — `date.form_name` (в `aep_6.json` мусор); `full_name`/`inn.inn`/`units.measurement` из ТЗ в реальных файлах нет. Нормализация — Срез 3 |
|
||||||
| OQ-14 | `reporting_date` в JSON ИС Сканирование — «ждём обновлённый JSON» | FNR01 | срез 3 | **уточнено (04.10.2026):** отдельного поля `reporting_date` в ответе нет; `date.form_name` ненадёжен (в `aep_6.json` — «Общество с ограниченной ответственностью», в `aep_3.json` — «2\|0\|2\|4\|»); даты колонок — в строке-шапке `rows[0]` таблицы («На 30 июня 2025 г» в `aep_6.json`), в `aep_3.json` шапки нет. Допущение прежнее: парсить из шапки таблицы, при её отсутствии — `NEEDS_CHECK`; решение — Срез 3 |
|
| OQ-14 | `reporting_date` в JSON ИС Сканирование — «ждём обновлённый JSON» | FNR01 | срез 3 | **уточнено (04.10.2026):** отдельного поля `reporting_date` в ответе нет; `date.form_name` ненадёжен (в `aep_6.json` — «Общество с ограниченной ответственностью», в `aep_3.json` — «2\|0\|2\|4\|»); даты колонок — в строке-шапке `rows[0]` таблицы («На 30 июня 2025 г» в `aep_6.json`), в `aep_3.json` шапки нет. Допущение прежнее: парсить из шапки таблицы, при её отсутствии — `NEEDS_CHECK`; решение — Срез 3 |
|
||||||
| OQ-15 | Таймаут OCR 5 мин / 0 повторов (ТЗ 3) vs outbox с повторами (DBML) | DOC-037736 | срез 3 | решено DBML: `max_attempts` OCR = 1, парсер = 3 |
|
| OQ-15 | Таймаут OCR 5 мин / 0 повторов (ТЗ 3) vs outbox с повторами (DBML) | DOC-037736 | срез 3 | решено DBML: `max_attempts` OCR = 1, парсер = 3 |
|
||||||
@ -142,13 +142,21 @@
|
|||||||
| OQ-134 | Шапка ОСВ без строки организации и с кодом/названием счёта в двух колонках («Счет: Код / Наименование», строка «Бухгалтерский \| 9 месяцев 2025 г.»; Новоросметалл ОСВ 63): парсер не находит организацию, счёт, период, тип данных. Новый вариант заголовка — словарь или код | пакет ДКБ 06.10.2026 | tabular-parser `detect.py`/`header.py`, `osv_headers.yaml` | **реализовано** (tabular-parser PR #13, arm-tabular-parser#11 п. 4): шапка без метки организации (организация — хвост названия ведомости), период и тип данных отдельными ячейками (`periods.yaml: standalone`, `data_types.yaml: standalone_header`), подписи «Код»/«Наименование» не группировки (`osv_headers.yaml: column_captions`), код счёта — из первой строки-счёта (с предупреждением); `05-domain-rules.md` §1.1; тест `test_header_without_labels_and_code_name_columns_are_found` |
|
| OQ-134 | Шапка ОСВ без строки организации и с кодом/названием счёта в двух колонках («Счет: Код / Наименование», строка «Бухгалтерский \| 9 месяцев 2025 г.»; Новоросметалл ОСВ 63): парсер не находит организацию, счёт, период, тип данных. Новый вариант заголовка — словарь или код | пакет ДКБ 06.10.2026 | tabular-parser `detect.py`/`header.py`, `osv_headers.yaml` | **реализовано** (tabular-parser PR #13, arm-tabular-parser#11 п. 4): шапка без метки организации (организация — хвост названия ведомости), период и тип данных отдельными ячейками (`periods.yaml: standalone`, `data_types.yaml: standalone_header`), подписи «Код»/«Наименование» не группировки (`osv_headers.yaml: column_captions`), код счёта — из первой строки-счёта (с предупреждением); `05-domain-rules.md` §1.1; тест `test_header_without_labels_and_code_name_columns_are_found` |
|
||||||
| OQ-135 | Отчёт 1С «Анализ счета» с отбором по контрагентам группы (`fixtures/analysis-reports/`, в именах файлов «ОСВ_…ПоКомпаниямГруппы»): не ОСВ, но содержит ВГО по корреспонденции счетов. Нужен ли отдельный тип документа для Среза 5, или ВГО считаются только из ОСВ | пакет ДКБ 06.10.2026 | `document_type`, Срез 5 | открыт: допущение — не разбирать; парсер отвечает `INVALID_DOCUMENT` |
|
| OQ-135 | Отчёт 1С «Анализ счета» с отбором по контрагентам группы (`fixtures/analysis-reports/`, в именах файлов «ОСВ_…ПоКомпаниямГруппы»): не ОСВ, но содержит ВГО по корреспонденции счетов. Нужен ли отдельный тип документа для Среза 5, или ВГО считаются только из ОСВ | пакет ДКБ 06.10.2026 | `document_type`, Срез 5 | открыт: допущение — не разбирать; парсер отвечает `INVALID_DOCUMENT` |
|
||||||
| OQ-136 | ИНН организаций пакета ДКБ (Цимлянский, НКЗ, Новоросметалл, Приманыческий, Агрокомплекс) в шапках ОСВ отсутствуют; для теста консолидации по этим группам нужны ИНН и состав ГК/ГСК (выгрузка ЕЦП или список от ДКБ) | пакет ДКБ 06.10.2026 | seed/фикстуры справочников, тесты Среза 4–5 на новых группах | открыт |
|
| OQ-136 | ИНН организаций пакета ДКБ (Цимлянский, НКЗ, Новоросметалл, Приманыческий, Агрокомплекс) в шапках ОСВ отсутствуют; для теста консолидации по этим группам нужны ИНН и состав ГК/ГСК (выгрузка ЕЦП или список от ДКБ) | пакет ДКБ 06.10.2026 | seed/фикстуры справочников, тесты Среза 4–5 на новых группах | открыт |
|
||||||
| OQ-137 | Раскладка горячих папок ИС Сканирование: scan-gateway кладёт `Input/<job_id>_<original_name>` через временный `Tmp/.<имя>.part`, ничего не удаляет; PDFWorker старого АРМ (`RSHB/armka_file_worker`, проверен в контуре) кладёт `Input/<task_id>.<ext>` через временный `<назначение>.<uuid>.tmp` рядом (ИС игнорирует `*.tmp`), читает батч `Output/<task_id>/*.json` и удаляет `Output/<task_id>/` и входящий файл после чтения. Какая раскладка актуальна? | сравнение `armka_file_worker` (README «Файлы», `clients.py`) и `scan-gateway/gateway/hotfolder.py`, `smb.py`; OQ-78, OQ-79 | scan-gateway (спека `image-recognition-gateway`, эмулятор, тесты) | **закрыт владельцем 06.10.2026: «правильно как в FileWorker»** — имя `<job_id>.<ext>`, временный `*.tmp` рядом с назначением (каталог `Tmp` из OQ-79 не нужен), очистка `Output/<job_id>/` и входящего файла после чтения; `Output/<job_id>/*.json` батчем и стабилизация набора (OQ-78) остаются. Реализация — arm-scan-gateway #6 |
|
| OQ-137 | Раскладка горячих папок ИС Сканирование: scan-gateway кладёт `Input/<job_id>_<original_name>` через временный `Tmp/.<имя>.part`, ничего не удаляет; PDFWorker старого АРМ (`RSHB/armka_file_worker`, проверен в контуре) кладёт `Input/<task_id>.<ext>` через временный `<назначение>.<uuid>.tmp` рядом (ИС игнорирует `*.tmp`), читает батч `Output/<task_id>/*.json` и удаляет `Output/<task_id>/` и входящий файл после чтения. Какая раскладка актуальна? | сравнение `armka_file_worker` (README «Файлы», `clients.py`) и `scan-gateway/gateway/hotfolder.py`, `smb.py`; OQ-78, OQ-79 | scan-gateway (спека `image-recognition-gateway`, эмулятор, тесты) | **закрыт владельцем 06.10.2026: «правильно как в FileWorker»** — имя `<job_id>.<ext>`, временный `*.tmp` рядом с назначением (каталог `Tmp` из OQ-79 не нужен), очистка `Output/<job_id>/` и входящего файла после чтения; `Output/<job_id>/*.json` батчем и стабилизация набора (OQ-78) остаются. Реализация — arm-scan-gateway #6. **Уточнение 07.10.2026 (Срез 3a, решение владельца 06.10.2026):** временный `.part` остаётся в `Tmp` (в `Input` временных файлов не класть — ИС может подхватить недописанный файл, гарантии игнора `*.tmp` у нас нет; OQ-79 остаётся в силе); очистка `Output/<job_id>/` и входящего файла — под флагом `SCAN_CLEANUP`, по умолчанию выключена (OQ-144) |
|
||||||
| OQ-138 | Единицы «тыс. руб.» в шапке против значений в рублях с копейками в выгрузках 1С (Унагранде Компани/Групп, НПЗ) и тысяч с дробями (Межениновская): кто и где приводит единицы. Примеры: книга НПЗ (`npz/2025-Q3`) — ББ `needs_check`, `1600 = 1700` расходится на 1.94 / 9.02 / 2.48 руб.; ОФР НПЗ и ОФР Унагранде (стр. 1) — `needs_check` по 2100/2200/2300 по той же причине; полные формы по факту сходятся только у Межениновской | `fixtures/statements-xlsx/README.md`, tabular-parser design 2b D-6, D-7, решения №22–24 (R-11, R-13, R-14) | Срез 3 (маппинг форм), §8 (КФО в тыс. руб.) | **закрыт решением владельца 06.10.2026:** единица «тыс. руб.» в шапке считается верной, значения отдаются как в файле; расхождения итогов в пределах округления (до 0,001 % наибольшего числа соотношения) статус не портят — мягкие нарушения «в пределах округления» (arm-tabular-parser#20). Книга НПЗ, ОФР стр. 1 Унагранде — `done`. Принимать ли расхождение и приводить ли единицы — решает КА в ядре (акцепт проверок, Срез 3) |
|
| OQ-138 | Единицы «тыс. руб.» в шапке против значений в рублях с копейками в выгрузках 1С (Унагранде Компани/Групп, НПЗ) и тысяч с дробями (Межениновская): кто и где приводит единицы. Примеры: книга НПЗ (`npz/2025-Q3`) — ББ `needs_check`, `1600 = 1700` расходится на 1.94 / 9.02 / 2.48 руб.; ОФР НПЗ и ОФР Унагранде (стр. 1) — `needs_check` по 2100/2200/2300 по той же причине; полные формы по факту сходятся только у Межениновской | `fixtures/statements-xlsx/README.md`, tabular-parser design 2b D-6, D-7, решения №22–24 (R-11, R-13, R-14) | Срез 3 (маппинг форм), §8 (КФО в тыс. руб.) | **закрыт решением владельца 06.10.2026:** единица «тыс. руб.» в шапке считается верной, значения отдаются как в файле; расхождения итогов в пределах округления (до 0,001 % наибольшего числа соотношения) статус не портят — мягкие нарушения «в пределах округления» (arm-tabular-parser#20). Книга НПЗ, ОФР стр. 1 Унагранде — `done`. Принимать ли расхождение и приводить ли единицы — решает КА в ядре (акцепт проверок, Срез 3) |
|
||||||
| OQ-139 | Форма без колонки кодов: сопоставление по именам строк из шаблонов 66н/ФСБУ (словарь `statement_lines.yaml`, `line_code_confidence < 100`)? Сейчас документ без строк и `needs_check` («колонка кодов не найдена») | решение владельца 06.10.2026 (вопрос 6), tabular-parser design 2b D-4 | §12 п. 2, Срез 3 | открыт: нужен реальный файл без колонки кодов; допущение — не сопоставлять по именам |
|
| OQ-139 | Форма без колонки кодов: сопоставление по именам строк из шаблонов 66н/ФСБУ (словарь `statement_lines.yaml`, `line_code_confidence < 100`)? Сейчас документ без строк и `needs_check` («колонка кодов не найдена») | решение владельца 06.10.2026 (вопрос 6), tabular-parser design 2b D-4 | §12 п. 2, Срез 3 | открыт: нужен реальный файл без колонки кодов; допущение — не сопоставлять по именам; **связано (Срез 3a):** то же для PDF-пути — справочник кодов по редакциям, имена из OCR при пустой редакции (OQ-146, OQ-150) |
|
||||||
| OQ-140 | Страница-продолжение в отдельном файле (1С печатает ББ/ОФР постранично, один файл = страница): склейка в ядре по ЮЛ + период + тип формы; признак `is_continuation` / `form_page` в контракте (MINOR, две необязательных поля) | решение владельца 06.10.2026 (вопрос 2), design 2b D-8 | §12 п. 9, Срез 3, контракт v1.x | допущение принято: страницы разных файлов не склеиваются; страница 2 без шапки — документ с пустой шапкой и `needs_check`, «загрузите форму одним файлом» |
|
| OQ-140 | Страница-продолжение в отдельном файле (1С печатает ББ/ОФР постранично, один файл = страница): склейка в ядре по ЮЛ + период + тип формы; признак `is_continuation` / `form_page` в контракте (MINOR, две необязательных поля) | решение владельца 06.10.2026 (вопрос 2), design 2b D-8 | §12 п. 9, Срез 3, контракт v1.x | допущение принято: страницы разных файлов не склеиваются; страница 2 без шапки — документ с пустой шапкой и `needs_check`, «загрузите форму одним файлом» |
|
||||||
| OQ-141 | Строка без кода, но с суммами в ОФР ФСБУ 4/2023 (НПЗ «Реализация иного имущества…»): хранить ли (OQ-36 расширяется с расшифровочных строк на строки с суммами) | `fixtures/statements-xlsx/npz`, design 2b D-6 | Срез 3, DBML `financial_statement_line` (хранит только строки с кодом) | допущение принято: строки без кода не отдаются, при наличии сумм — предупреждение «строк без кода с суммами пропущено: N» |
|
| OQ-141 | Строка без кода, но с суммами в ОФР ФСБУ 4/2023 (НПЗ «Реализация иного имущества…»): хранить ли (OQ-36 расширяется с расшифровочных строк на строки с суммами) | `fixtures/statements-xlsx/npz`, design 2b D-6 | Срез 3, DBML `financial_statement_line` (хранит только строки с кодом) | допущение принято: строки без кода не отдаются, при наличии сумм — предупреждение «строк без кода с суммами пропущено: N» |
|
||||||
| OQ-142 | Образец ФСБУ 4/2023 (`templates/ББ после 01.01.2025.xlsx`): «АКТИВ» и код 1110 в одной строке из-за объединённых ячеек; шаблоны (коды есть, сумм и шапки нет) не данные. Нужен ли их разбор вообще | `fixtures/statements-xlsx/templates`, design 2b D-8 | §12 п. 10, тесты | допущение принято: шаблоны дают `needs_check` (шапка пустая, значения `null`); разбор шаблонов как источника данных не планируется, пока владелец не скажет иначе |
|
| OQ-142 | Образец ФСБУ 4/2023 (`templates/ББ после 01.01.2025.xlsx`): «АКТИВ» и код 1110 в одной строке из-за объединённых ячеек; шаблоны (коды есть, сумм и шапки нет) не данные. Нужен ли их разбор вообще | `fixtures/statements-xlsx/templates`, design 2b D-8 | §12 п. 10, тесты | допущение принято: шаблоны дают `needs_check` (шапка пустая, значения `null`); разбор шаблонов как источника данных не планируется, пока владелец не скажет иначе |
|
||||||
| OQ-143 | Лист продолжения с **единственной** кодовой строкой (книга НПЗ: лист ОФР с одной строкой 2400): `min_codes` = 3 → лист не распознаётся как форма и пропускается, строка 2400 теряется при `needs_check` документа. Понизить порог / принимать лист по близости к открытому документу ОФР / маркеру продолжения | tabular-parser design 2b: решение №25, O-9 | §12 п. 2, п. 9; полнота ОФР НПЗ | **реализовано в 2b (финальная волна, I-1):** лист с 1–2 кодами форм после листа формы берётся как продолжение (`min_codes_continuation = 1` в словаре) и присоединяется при совпадении числа колонок сумм и типа формы; не присоединён — `needs_check` и предупреждение; первая страница с 1–2 кодами формой не считается. Строка 2400 НПЗ в результате есть |
|
| OQ-143 | Лист продолжения с **единственной** кодовой строкой (книга НПЗ: лист ОФР с одной строкой 2400): `min_codes` = 3 → лист не распознаётся как форма и пропускается, строка 2400 теряется при `needs_check` документа. Понизить порог / принимать лист по близости к открытому документу ОФР / маркеру продолжения | tabular-parser design 2b: решение №25, O-9 | §12 п. 2, п. 9; полнота ОФР НПЗ | **реализовано в 2b (финальная волна, I-1):** лист с 1–2 кодами форм после листа формы берётся как продолжение (`min_codes_continuation = 1` в словаре) и присоединяется при совпадении числа колонок сумм и типа формы; не присоединён — `needs_check` и предупреждение; первая страница с 1–2 кодами формой не считается. Строка 2400 НПЗ в результате есть |
|
||||||
|
| OQ-144 | Права на удаление на SMB-шаре контура (`Input`, `Output/<job_id>/`, `Tmp`): можно ли сервису `scan-gateway` удалять файлы, и не удаляет ли их сама ИС Сканирование после обработки. Пока `SCAN_CLEANUP=false` — сервис ничего не удаляет (кроме `.part`, ставшего целью публикации записью без замены), в `Tmp` и `Output` накапливаются остатки | решение владельца 06.10.2026, change `slice-3a-ocr-normalizer` D-2, решения №26/№27 | scan-gateway (горячие папки), эксплуатация стенда | открыт: допущение — очистка выключена; dev-compose включает `SCAN_CLEANUP=true`. Спросить платформу/владельцев ИС; при положительном ответе — включить флаг на стенде |
|
||||||
|
| OQ-145 | Редакция формы (`66n` / `fsbu4`) в контракте: нужна ли ядру редакция документа в конверте (необязательное поле `statementDocument`, кандидат MINOR 1.3), или ядро выбирает редакцию по `report_date` само, по тому же правилу §11.1 п. 10. Сейчас scan-gateway выбирает редакцию внутри и наружу её не отдаёт | change `slice-3a-ocr-normalizer`, открытый вопрос 2 | контракт `docs/contracts/` (v1.3), Срез 3b (маппинг форм, справочник кодов в ядре) | открыт: допущение — в конверт не передаётся, контракт остаётся v1.2; при положительном решении — MINOR 1.3 со ссылкой на этот OQ |
|
||||||
|
| OQ-146 | Словари §7 (проверки форм) и §12 (справочник кодов `statement_lines.yaml`, формулы ОФР) **по редакциям** 66н / ФСБУ 4/2023 для tabular-parser (2b) и ядра (3b): один набор правил на обе редакции или раздельные; как выбирать редакцию в ядре для Excel-пути | change `slice-3a-ocr-normalizer` (справочник с редакциями только в scan-gateway), OQ-12, OQ-139 | tabular-parser `rules/*.yaml`, ядро (Срез 3b), §7, §12 п. 2 | открыт: допущение — scan-gateway ведёт свой `api/rules/statement_lines.yaml`; tabular-parser и ядро словарь по редакциям не используют; расширяет OQ-12 и OQ-139 |
|
||||||
|
| OQ-147 | Закрывающая скобка, распознанная OCR как `}` или `]` (`(7744536}` в `aep_3.json`): дословный перенос `parse_amount` минус не ставит — сумма положительная, `is_recognized = false`. Расширить ли множество закрывающих скобок в §11.1 п. 7 после проверки на стенде | change `slice-3a-ocr-normalizer`, решение №12; владелец 06.10.2026: «пока устроит как есть» | scan-gateway `normalizer/ocr/amounts.py`, §11.1 п. 7 | открыт: допущение — перенос дословный; знак теряется, строка подсвечена `is_recognized = false`, исходный текст — в `raw/` |
|
||||||
|
| OQ-148 | Длинное/среднее тире «—»/«–» как прочерк: дословный `parse_amount` вырезает тире как «лишний» символ → сумма `null`, но `is_recognized = false` даже при `confidence = 100` (дефис «-» даёт `true`). Лишний `needs_check` на формах с длинным тире. Считать ли прочерк любого вида распознанным | change `slice-3a-ocr-normalizer`, решение №20 | scan-gateway `amounts.py`, §11.1 п. 7, доля `done` на реальных сканах | открыт: допущение — перенос дословный; правило уточняется после прогона на стенде |
|
||||||
|
| OQ-149 | Строки с непустым нечисловым (`1150 :`, `211(1 |`) или пустым кодом пропускаются нормализатором (поведение источника, §11.1 п. 4); предупреждения о пропущенной строке с данными в Срезе 3a нет — строка теряется молча (остаётся в `raw/`). Пример: в `aep_3.json` строка «БАЛАНС» с пустым кодом и строка «Выручка» с кодом `211(1 |` (вместо 2110). Восстанавливать ли строку по наименованию (словарь редакции) и выдавать ли предупреждение | `fixtures/ocr/aep_3.json`, change `slice-3a-ocr-normalizer`, §11.1 п. 4 | scan-gateway `rows.py`/`document.py`, полнота форм, OQ-36, OQ-139 | открыт: допущение — строка пропускается как в источнике; аналитик видит расхождение итогов в проверках 3b |
|
||||||
|
| OQ-150 | Состав редакции `fsbu4` (ФСБУ 4/2023, отчётность с 2025 года) в `api/rules/statement_lines.yaml` scan-gateway: коды и наименования строк ББ и ОФР; источник — Приложения 3–4 к ФСБУ 4/2023 (черновой перечень кодов — шаблоны `fixtures/statements-xlsx/templates/`, OQ-142). Пока `lines: {}` | change `slice-3a-ocr-normalizer`, D-7 | scan-gateway `api/rules/`, эталоны `fixtures/ocr/expected_normalize/` для документов 2025 года, статус `done` | открыт: **данные даёт владелец**, код нормализатора не меняется; до заполнения документы 2025 года идут с именами из OCR, предупреждением «редакция fsbu4 не заполнена» и `needs_check`. Порядок заполнения — `scan-gateway/README.md` |
|
||||||
|
| OQ-151 | Эмулятор ИС Сканирование: файл, совпавший по sha256 с PDF из `SCAN_FIXTURES_DIR`, у которого нет JSON-набора (`<имя>.json`), отвечает «нет пары» (`INVALID_DOCUMENT`) и **не** уходит на `SCAN_EMULATOR_DEFAULT`. Пробел спеки эмулятора: считать ли такой файл промахом и отдавать набор по умолчанию | change `slice-3a-ocr-normalizer`, спека `scan-emulator` | scan-gateway `scan_emulator`, dev-стенд (`dev/qa-guide.md` §6) | открыт: допущение — поведение как есть; PDF без JSON-пары в `fixtures/ocr` не класть |
|
||||||
|
|
||||||
Служебные идентификаторы в DBML/аналитике: `ANL-nnn` — замечания аналитика к макету; `ARCH-nnn` — архитектурные решения;
|
Служебные идентификаторы в DBML/аналитике: `ANL-nnn` — замечания аналитика к макету; `ARCH-nnn` — архитектурные решения;
|
||||||
`DECISION-n` — решения по модели; `FIX-nn` — исправления дефектов v4.
|
`DECISION-n` — решения по модели; `FIX-nn` — исправления дефектов v4.
|
||||||
|
|||||||
@ -169,16 +169,31 @@ Change'и: `arm-core/openspec/changes/slice-2c-osv-ingest` (issue arm-core#14,
|
|||||||
|
|
||||||
## Срез 3 — ББ/ОФР: scan-gateway, маппинг форм, логические проверки, подтверждение документа
|
## Срез 3 — ББ/ОФР: scan-gateway, маппинг форм, логические проверки, подтверждение документа
|
||||||
|
|
||||||
- `scan-gateway`: горячие папки (по FNR02) с заглушкой-эмулятором ИС Сканирование для dev (возвращает `aep_*.json`
|
Срез 3 делится на два подсреза с отдельными change'ами.
|
||||||
по имени файла); сырой ответ → S3 `raw/`; нормализация в контракт результата по §11 и **§11.1** (очистка OCR-текста:
|
|
||||||
перенос проверенного на стенде модуля `ofr_result_conversion` старого ядра — единственное разрешённое исключение для
|
### Срез 3a — scan-gateway: горячие папки и нормализатор OCR (репозиторий scan-gateway)
|
||||||
`reference-old-arm`, решение владельца 06.10.2026); таймаут 5 мин.
|
|
||||||
- Маппинг: `financial_statement_meta/column/line/value` из JSON; числа, скобки, confidence; ИНН/имя сверка.
|
Change `slice-3a-ocr-normalizer` (arm-scan-gateway#6; docs#46, fixtures#12). Реализация — по `05-domain-rules.md` §11 и §11.1.
|
||||||
|
|
||||||
|
- Горячие папки в раскладке FileWorker (OQ-137): `Input/<job_id>.<ext>`, `.part` в `Tmp` и запись без замены, `Output/<job_id>/*.json`
|
||||||
|
набором (по стабилизации), очистка — под флагом `SCAN_CLEANUP` (по умолчанию выключена, OQ-144); таймаут 5 мин.
|
||||||
|
- Эмулятор ИС Сканирование для dev: пара PDF + JSON по sha256 содержимого (`fixtures/ocr`), набор по умолчанию
|
||||||
|
`SCAN_EMULATOR_DEFAULT`; сырой ответ → S3 `raw/`.
|
||||||
|
- Нормализатор `OcrNormalizer`: перенос проверенного на стенде модуля `ofr_result_conversion` старого ядра (единственное
|
||||||
|
разрешённое исключение для `reference-old-arm`, решение владельца 06.10.2026) в `statementDocument` v1.2 — по документу на
|
||||||
|
таблицу; справочники `api/rules/` с редакциями форм `66n` / `fsbu4` (`fsbu4` заполняет владелец, OQ-150); `checks` не передаются.
|
||||||
|
- Эталоны нормализатора — `fixtures/ocr/expected_normalize/` (отдельный PR fixtures), `make golden-update`.
|
||||||
|
- Новые вопросы: OQ-144…OQ-151.
|
||||||
|
|
||||||
|
### Срез 3b — маппинг форм, проверки, подтверждение (arm-core)
|
||||||
|
|
||||||
|
- Маппинг: `financial_statement_meta/column/line/value` из `statementDocument` (общий для Excel и OCR); числа, скобки, confidence; ИНН/имя сверка.
|
||||||
- Логические проверки §7 (чистые функции `domain/checks.py`), акцепт КА, «Подтвердить данные» → `CONFIRMED`,
|
- Логические проверки §7 (чистые функции `domain/checks.py`), акцепт КА, «Подтвердить данные» → `CONFIRMED`,
|
||||||
`RECOGNIZED_DATA_EDIT` с before/after.
|
`RECOGNIZED_DATA_EDIT` с before/after.
|
||||||
- Чек-лист `required_document_rule × периметр − document CONFIRMED` (API + модалка + CSV).
|
- Чек-лист `required_document_rule × периметр − document CONFIRMED` (API + модалка + CSV).
|
||||||
- UI: экран «Проверка распознавания» (исходник слева — PDF.js, результат справа, блок «Логические ошибки»).
|
- UI: экран «Проверка распознавания» (исходник слева — PDF.js, результат справа, блок «Логические ошибки»).
|
||||||
- Тесты: пары `aep_N.pdf/json`; машиночитаемые PDF АО РБ 2024 как dev-путь без OCR (опционально pdfplumber).
|
- Тесты: пары `aep_N.pdf/json`; машиночитаемые PDF АО РБ 2024 как dev-путь без OCR (опционально pdfplumber).
|
||||||
|
- Открытые вопросы для 3b: редакция формы в контракте (OQ-145), словари §7/§12 по редакциям (OQ-146), строки с нечисловым кодом (OQ-149).
|
||||||
|
|
||||||
## Срез 4 — Консолидация: карточка, версии, периметр, периоды, сводная ОСВ
|
## Срез 4 — Консолидация: карточка, версии, периметр, периоды, сводная ОСВ
|
||||||
|
|
||||||
|
|||||||
@ -181,7 +181,10 @@ services:
|
|||||||
|
|
||||||
# Эмулятор ИС Сканирование: тот же образ, что у шлюза (собирает сервис scan-gateway, см. arm-core-migrate); общий
|
# Эмулятор ИС Сканирование: тот же образ, что у шлюза (собирает сервис scan-gateway, см. arm-core-migrate); общий
|
||||||
# том hotfolder с шлюзом. Переменные MinIO/NATS не нужны.
|
# том hotfolder с шлюзом. Переменные MinIO/NATS не нужны.
|
||||||
# Фикстуры ответов ИС — ../../fixtures/ocr (только чтение): набор <имя>.json + <имя>_*.json для входного файла UUID_<имя>.ext.
|
# Фикстуры ответов ИС — ../../fixtures/ocr (только чтение). Входной файл шлюз называет <uuid>.<ext> (имя файла
|
||||||
|
# фикстуры не несёт); эмулятор считает sha256 содержимого и ищет пару PDF + набор <имя>.json / <имя>_*.json по хешу
|
||||||
|
# (aep_3.pdf, aep_6.pdf, ...). Любое другое содержимое — ответ-ошибка INVALID_DOCUMENT, пока SCAN_EMULATOR_DEFAULT пуст;
|
||||||
|
# чтобы любой файл получал набор по умолчанию, задайте SCAN_EMULATOR_DEFAULT=<имя набора>, например aep_6.
|
||||||
scan-emulator:
|
scan-emulator:
|
||||||
profiles: ["services"]
|
profiles: ["services"]
|
||||||
image: scan-gateway:dev
|
image: scan-gateway:dev
|
||||||
@ -192,6 +195,8 @@ services:
|
|||||||
SCAN_HOTFOLDER_PATH: /data/hotfolder
|
SCAN_HOTFOLDER_PATH: /data/hotfolder
|
||||||
SCAN_FIXTURES_DIR: /fixtures/ocr
|
SCAN_FIXTURES_DIR: /fixtures/ocr
|
||||||
SCAN_EMULATOR_DELAY: "2"
|
SCAN_EMULATOR_DELAY: "2"
|
||||||
|
# Набор по умолчанию для файлов без пары по содержимому. Пусто — такой файл получает INVALID_DOCUMENT.
|
||||||
|
SCAN_EMULATOR_DEFAULT: ""
|
||||||
volumes:
|
volumes:
|
||||||
- hotfolder:/data/hotfolder
|
- hotfolder:/data/hotfolder
|
||||||
- ../../fixtures/ocr:/fixtures/ocr:ro
|
- ../../fixtures/ocr:/fixtures/ocr:ro
|
||||||
@ -213,6 +218,9 @@ services:
|
|||||||
<<: *service-env
|
<<: *service-env
|
||||||
SCAN_HOTFOLDER_MODE: local
|
SCAN_HOTFOLDER_MODE: local
|
||||||
SCAN_HOTFOLDER_PATH: /data/hotfolder
|
SCAN_HOTFOLDER_PATH: /data/hotfolder
|
||||||
|
# Очистка Output/<job_id>/ и входящего файла после чтения ответа (dev-том принадлежит нам). На шаре контура по
|
||||||
|
# умолчанию выключена: права на удаление не подтверждены (OQ-144).
|
||||||
|
SCAN_CLEANUP: "true"
|
||||||
volumes:
|
volumes:
|
||||||
- hotfolder:/data/hotfolder
|
- hotfolder:/data/hotfolder
|
||||||
ports:
|
ports:
|
||||||
|
|||||||
@ -18,6 +18,8 @@ Node 24, uv 0.10): от пустого каталога до экрана адм
|
|||||||
- Загрузка файлов ОСВ через API (`POST /legal-entities/{id}/files`), распознавание реальным `tabular-parser`, разбор
|
- Загрузка файлов ОСВ через API (`POST /legal-entities/{id}/files`), распознавание реальным `tabular-parser`, разбор
|
||||||
результата в документы и строки ОСВ, статусы файла и документа, автозамена версий, подтверждение документа, удаление
|
результата в документы и строки ОСВ, статусы файла и документа, автозамена версий, подтверждение документа, удаление
|
||||||
файла (раздел 6.6); проверка парсера на произвольных форматах ОСВ и формах ББ/ОФР — раздел 6.7 (маппинг форм в ядре — Срез 3). Всё — через Swagger/curl, UI «Первичные документы» — подсрез 2d.
|
файла (раздел 6.6); проверка парсера на произвольных форматах ОСВ и формах ББ/ОФР — раздел 6.7 (маппинг форм в ядре — Срез 3). Всё — через Swagger/curl, UI «Первичные документы» — подсрез 2d.
|
||||||
|
- Распознавание PDF в `scan-gateway` с эмулятором ИС Сканирование (Срез 3a, раздел 6.8): отдельно от ядра, без UI; загружать
|
||||||
|
`aep_3.pdf` / `aep_6.pdf` из `fixtures/ocr`.
|
||||||
- Консолидация через API (Срез 4): версии за период, периметр, блокировка «взять в работу», статусы, привязка подтверждённых
|
- Консолидация через API (Срез 4): версии за период, периметр, блокировка «взять в работу», статусы, привязка подтверждённых
|
||||||
документов, чек-лист, сводная ОСВ, ручное сопоставление контрагента, экспорт xlsx (раздел 7; полный прогон на данных РБ и
|
документов, чек-лист, сводная ОСВ, ручное сопоставление контрагента, экспорт xlsx (раздел 7; полный прогон на данных РБ и
|
||||||
контрольные цифры — 7.1–7.3). UI консолидации — другой исполнитель.
|
контрольные цифры — 7.1–7.3). UI консолидации — другой исполнитель.
|
||||||
@ -429,6 +431,31 @@ ENV_FILE=.env.test .venv/bin/python -m src.parser.cli "/путь/к/файлу.x
|
|||||||
отступов, `done` при реально не сходящихся суммах, падение (`RECOGNITION_FAILED`) на любом файле. К багу прикладывайте
|
отступов, `done` при реально не сходящихся суммах, падение (`RECOGNITION_FAILED`) на любом файле. К багу прикладывайте
|
||||||
файл (или его обезличенную копию) и `result.json`.
|
файл (или его обезличенную копию) и `result.json`.
|
||||||
|
|
||||||
|
### 6.8 Распознавание PDF: scan-gateway и эмулятор ИС Сканирование (Срез 3a)
|
||||||
|
|
||||||
|
PDF и изображения обрабатывает `scan-gateway`; в ядре маппинг форм из PDF (`statementDocument`) — Срез 3b, поэтому из
|
||||||
|
UI/API ядра PDF-путь пока не проверяется. Сервис и нормализатор проверяются отдельно: поднимите профиль `services`
|
||||||
|
(`scan-gateway` и `scan-emulator` в `docs/dev/docker-compose.yml`) и пройдите сценарий из README `scan-gateway` («Полный круг против compose и эмулятора»:
|
||||||
|
файл в MinIO → `recognition.image.request` в NATS → ack и итоговый конверт).
|
||||||
|
|
||||||
|
Что загружать на стенде: **PDF из `fixtures/ocr` — `aep_3.pdf` и `aep_6.pdf`**. Эмулятор узнаёт их **по содержимому**
|
||||||
|
(sha256), а не по имени: шлюз кладёт файл в горячую папку как `<uuid>.<ext>`, и имя фикстуры в нём не сохраняется.
|
||||||
|
Любой другой PDF даёт ответ-ошибку `INVALID_DOCUMENT` («нет пары по содержимому»), если не задан `SCAN_EMULATOR_DEFAULT`
|
||||||
|
(в compose по умолчанию пуст).
|
||||||
|
|
||||||
|
| Файл | Ожидание |
|
||||||
|
|---|---|
|
||||||
|
| `aep_3.pdf` | `needs_check`, два документа (`BALANCE`, `INCOME`); в `metrics.warnings` — предупреждения нормализации и строка `<job_id>_ofr.json: INVALID_DOCUMENT: …` (второй файл набора — ответ-ошибка, штатно) |
|
||||||
|
| `aep_6.pdf` | `needs_check`, два документа; в предупреждениях «редакция fsbu4 не заполнена» (форма 2025 года, редакция `fsbu4` пока пуста — OQ-150) |
|
||||||
|
| `aep_5.PDF`, `aep_8.pdf` | `error`, `INVALID_DOCUMENT` с сообщением ИС (в фикстурах лежат ответы-ошибки) |
|
||||||
|
| любой другой PDF | `error`, `INVALID_DOCUMENT` эмулятора |
|
||||||
|
|
||||||
|
Что смотреть: `raw/<job_id>.json` (склейка всех файлов набора) и `results/<file_id>/<job_id>/result.json` в MinIO; подписи
|
||||||
|
колонок разобраны в даты, коды строк, суммы со скобками (минус), `is_recognized`, `string_value` только у неразобранных сумм.
|
||||||
|
`done` на реальных сканах почти не бывает — это ожидаемо. В dev-compose у шлюза `SCAN_CLEANUP=true`: после ответа
|
||||||
|
`Output/<job_id>/` и входящий файл удаляются. Что не считать багом: потеря строки с мусорным кодом (OQ-149), минус не поставлен
|
||||||
|
при `}` вместо `)` (OQ-147), `is_recognized = false` у прочерка «—» (OQ-148).
|
||||||
|
|
||||||
## 7. Консолидация через API (Срез 4)
|
## 7. Консолидация через API (Срез 4)
|
||||||
|
|
||||||
Предусловия: ядро запущено (раздел 4.2); для сценариев с файлами — ещё `NATS_ENABLED=true` и tabular-parser на `:8001`
|
Предусловия: ядро запущено (раздел 4.2); для сценариев с файлами — ещё `NATS_ENABLED=true` и tabular-parser на `:8001`
|
||||||
@ -599,6 +626,8 @@ Q3-only — `missing_quarters = ["Q1","Q2"]`.
|
|||||||
- Ручное сопоставление контрагента меняет строку ОСВ для всех версий, куда входит документ (OQ-123); рекомендация кандидата — только
|
- Ручное сопоставление контрагента меняет строку ОСВ для всех версий, куда входит документ (OQ-123); рекомендация кандидата — только
|
||||||
при точном совпадении нормализованного имени (OQ-127).
|
при точном совпадении нормализованного имени (OQ-127).
|
||||||
|
|
||||||
|
- PDF-путь (Срез 3a): ядро PDF в формы не маппит (Срез 3b); эмулятор отвечает только на PDF из `fixtures/ocr`, остальные —
|
||||||
|
`INVALID_DOCUMENT` (раздел 6.8); редакция `fsbu4` не заполнена (OQ-150).
|
||||||
- Парсер ОСВ: границы и «не баги» — в конце раздела 6.7 (счёт 91 — OQ-132; скользящий период — OQ-131).
|
- Парсер ОСВ: границы и «не баги» — в конце раздела 6.7 (счёт 91 — OQ-132; скользящий период — OQ-131).
|
||||||
- Удаление алиаса одновременно с его переносом ручным сопоставлением даёт 404 вместо 204 (arm-core #26) — так задумано.
|
- Удаление алиаса одновременно с его переносом ручным сопоставлением даёт 404 вместо 204 (arm-core #26) — так задумано.
|
||||||
- Код `60.АВ` (синтетика s09) и буквенные забалансовые вида `РВ.1` в план счетов не резолвятся (arm-core #28, C23-1/C23-2).
|
- Код `60.АВ` (синтетика s09) и буквенные забалансовые вида `РВ.1` в план счетов не резолвятся (arm-core #28, C23-1/C23-2).
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user