Andrey Epifancev a15fe65d52 openspec: спека Среза 3a — горячие папки как у armka_file_worker, нормализатор OCR → statementDocument v1.2 (#6)
proposal, design (D-1…D-9, решения без обсуждения №1–12), дельты спек image-recognition-gateway
(папки, очистка под флагом, нормализация, справочники с редакциями, статусы) и scan-emulator (пара по sha256,
набор по умолчанию). tasks.md заполняется планом.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-06 18:39:52 +04:00

63 lines
7.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Proposal: Срез 3a — горячие папки как у armka_file_worker и нормализатор OCR → `statementDocument` v1.2
**Срез:** 3a, первая половина Среза 3 «ББ/ОФР: scan-gateway, маппинг форм, логические проверки» (`../docs/context/09-delivery-plan.md`).
**Репозиторий:** scan-gateway. **Issue:** RSHB/arm-scan-gateway#6.
**Соседние:** fixtures — пары PDF+JSON и `ocr/expected_normalize/` (issue заводится при плане); docs — bounded-PR без change
(`03-architecture.md`, OQ-137, §11.1 п. 6 и п. 9, новые OQ). Срез 3b (маппинг `statementDocument` в ядре, проверки §7,
подтверждение, чек-лист) — отдельный change в arm-core.
## Why
Срез 0 оставил scan-gateway с `NullNormalizer`: любой успешный ответ ИС Сканирование превращается в `NOT_IMPLEMENTED`.
Срез 2b даёт ядру `statementDocument` v1.2 из Excel; чтобы ядро в 3b маппило формы одним кодом для обоих источников,
PDF-путь должен отдавать тот же документ. Второй повод — 06.10.2026 в контуре проверен PDFWorker старого АРМ
(`RSHB/armka_file_worker`): его раскладка горячих папок ИС принята владельцем как эталон (OQ-137), а модуль очистки OCR
(`ofr_result_conversion`) разрешено переносить (§11.1 `05-domain-rules.md`, единственное исключение для `reference-old-arm`).
## What Changes
- **Горячие папки** (`src/gateway/`): входящий файл — `<Input>/<job_id>.<ext>` (расширение исходного имени в нижнем
регистре) вместо `<job_id>_<original_name>`; временный `.part` по-прежнему в `<SCAN_TMP_DIR>` (решение владельца: в `Input`
временных файлов не класть, ИС может их подхватить), rename **без замены** — существующее назначение считается
доставленным. Новый метод `cleanup(job_id, ext)` под флагом `SCAN_CLEANUP` (по умолчанию `false`): удаляет
`Output/<job_id>/*.json`, каталог, входящий файл (и его `.done`-вариант эмулятора) и осиротевший `.part`; любое
удаление только при включённом флаге (на SMB может не быть прав). Ошибка удаления не меняет исход задания.
- **Эмулятор** (`src/scan_emulator/`): входящий `<uuid>.<ext>`; пара фикстур ищется по **sha256 содержимого** среди
не-JSON файлов `SCAN_FIXTURES_DIR` (`aep_6.pdf` → `aep_6.json` + `aep_6_*.json`); промах → набор
`SCAN_EMULATOR_DEFAULT`, если задан, иначе ответ-ошибка `INVALID_DOCUMENT`. `.part`/`.tmp`/`.done` игнорируются.
- **Нормализатор** (`src/normalizer/ocr/`): `OcrNormalizer` реализует протокол `Normalizer` Среза 0 — конвейер чистых
функций `text` → `dates` → `amounts` → `layout` → `rows` → `header` → `document`; проверенные на стенде функции
старого ядра (двойники, `|`, разбор дат, суммы, раскладка с шапкой и без) переносятся дословно по §11.1; выход —
pydantic-модели контракта v1.2 (`StatementDocument`), по одному документу на таблицу `table_structure[i]`.
`checks` не считаются (их считает ядро в 3b для обоих источников).
- **Статусы**: ошибочные файлы — как в Срезе 0; из успешных собираются документы; ни одной таблицы →
`error RECOGNITION_FAILED`; всё распознано → `done`, иначе `needs_check`; причины — в `metrics.warnings`.
- **Правила как данные** (`api/rules/`): `statement_lines.yaml` с **редакциями** форм (`66n` до отчётности за 2024
включительно — из `codes_mapping` старого ядра; `fsbu4` с 2025 — пустая, данные положит владелец), `ocr_lookalikes.yaml`,
`statement_labels.yaml`. Загружаются и валидируются при старте.
- **Настройки**: `SCAN_CLEANUP`, `SCAN_RULES_DIR`, `SCAN_EMULATOR_DEFAULT`; `SCAN_TMP_DIR` остаётся. Dev-compose:
`SCAN_CLEANUP=true`.
- **Тесты**: юнит по функциям, golden на `../fixtures/ocr/aep_3.json`, `aep_6.json` (эталоны `../fixtures/ocr/expected_normalize/`),
обновлённые тесты горячих папок, эмулятора, воркера; roundtrip с эмулятором.
## Non-goals
- Маппинг `statementDocument` в `financial_statement_*`, проверки §7, подтверждение документа, чек-лист, UI — Срез 3b.
- Изменение контракта (v1.2 заморожен): редакция формы в конверт не добавляется (OQ, кандидат в MINOR 1.3).
- Расшифровки без кода (OQ-36), склейка одной формы из нескольких таблиц (OQ-77), `DISCLOSURE` из OCR.
- Заполнение словаря редакции `fsbu4` и подбор пар PDF+JSON — владелец, отдельный PR в fixtures.
- Перенос обвязки старого ядра (`json_to_db_rows`, запись в БД, `pdfworker_results.py`).
## Параграфы `05-domain-rules.md`
§11 (структура ответа ИС, маппинг таблица → документ), §11.1 п. 1–9 (очистка OCR-текста), §12 п. 4 (разбор дат —
общий смысл с Excel-путём). Правила, которых в `05` нет и которые вводятся этим change'ом: редакции форм в справочнике
кодов (ASSUMPTION(OQ-12): состав 2025 года не задан), `is_recognized` колонок шапки. Таблицы DBML не затрагиваются
(сервис без БД).
## Открытые вопросы, затрагиваемые change'ом
OQ-137 (уточнить: `Tmp` остаётся, очистка под флагом), OQ-36 (строки без кода не отдаются), OQ-77 (одна форма двумя
таблицами — два документа с предупреждением), OQ-12 (редакции форм и §7), новые: права на удаление на SMB-шаре;
редакция формы в контракте; §7/§12 по редакциям для 2b и 3b.