Склейка всех json каталога ответа (settle), SCAN_TMP_DIR вне Input, успех = error.code == "", per-file ошибки в metrics.warnings, набор файлов в эмуляторе; решения №54-62 в design.md; OQ-76/77/78/79 отмечены решёнными; группа 7 в tasks.md. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
9.6 KiB
Proposal: Срез 0 — скелет scan-gateway
Срез: 0 «Скелет» (../docs/context/09-delivery-plan.md). Репозиторий: scan-gateway. Issue: RSHB/arm-scan-gateway#1.
Родительский change: ../docs/openspec/changes/slice-0-skeleton (контракт v1.0, compose, проверки качества).
Соседние: ../arm-core/openspec/changes/slice-0-skeleton, ../tabular-parser/openspec/changes/slice-0-skeleton.
Why
Репозиторий — копия каркаса arm-core с фронтом и БД, тогда как по DECISION-4 это stateless-адаптер внешней ИС
Сканирование. Самая рискованная часть адаптера — не разбор JSON (Срез 3), а обмен через горячие папки с таймаутом и
без доступа к БД: положить файл, дождаться ответа, не потерять задание — причём на NATS без JetStream. Срез 0 строит
этот конвейер и эмулятор ИС Сканирование, чтобы он работал в compose без доступа к банковской системе.
What Changes
- Вырезать из шаблона (список —
CLAUDE.md, «Соглашения шаблона»):web/,setup/.npmrcи npm-шагиsetup/setup.sh; изapp.py— mount SPA, CORS, импортraisa_fastapi_protected_api;db/,models/,repositories/,alembic/,alembic.ini; настройкаOPENBAO.DATABASE; зависимостиasyncpg,SQLAlchemy,alembic,argon2-cffi,raisa_fastapi_protected_api; шаблонныйREADME.md. SMB остаётся (smbprotocol,OPENBAO.SMB, сессия в lifespan). - Инструменты:
api/pyproject.toml(ruff, mypy),requirements-dev.txt=-r requirements.txt+ инструменты,Dockerfile(python:3.11-slim; строка убрана из.gitignore),Makefileс общими целями +venv,emulator,.env.example,.env.test;SettingsчитаетENV_FILE;SERVICE_NAME = "scan-gateway". - Контракт: вендоренные схемы
api/contracts/+ pydantic-моделиsrc/contract/; тест идентичности с../docs/contracts. - Горячие папки
src/gateway/: протоколHotFolder(put(name, data),poll(job_id) -> list[AnswerFile] | None,healthy()) с двумя реализациями —LocalHotFolder(каталог; dev и тесты) иSmbHotFolder(smbclientшаблона; прод). Имена подкаталогов настраиваются (SCAN_INPUT_DIR = "Input",SCAN_OUTPUT_DIR = "Output",SCAN_TMP_DIR = "Tmp"); файл кладётся как<job_id>_<original_name>; временный файл атомарной записи (.part) лежит в служебном каталоге<корень>/<SCAN_TMP_DIR>/рядом сInput/Output(на той же шаре/ФС) и переименовывается вInput, так что вInputнедописанных файлов не бывает (OQ-79). ИС кладёт все распознанные документы задания одновременно в<output>/<job_id>/— по одному*.jsonна документ (OQ-78);pollвозвращает все*.jsonкаталога по возрастанию имени, когда набор готов: есть хотя бы один*.json, а набор имён и размеров не менялсяSCAN_SETTLE_SECONDS(2). - Воркер
src/core/worker.py(NATS без JetStream): подписка наrecognition.image.requestв queue groupscan-gateway; по получении — сразу ackprocessingвrecognition.result; доSCAN_MAX_INFLIGHT(4) заданий параллельно: S3uploads/…→ горячая папка → опрос каждыеSCAN_POLL_SECONDS(5) доmin(reply_deadline, now + SCAN_TIMEOUT_SECONDS=300)→ склейка всех*.jsonответа в один сырой ответ{"job_id", "files": [{"name", "response", "parse_error"}]}в S3raw/{job_id}.json→ нормализатор →results/{file_id}/{job_id}/result.json→ конверт результата (raw_s3_keyзаполнен). Таймаут →error.code = TIMEOUT. Process-local множествоjob_idв работе: повторный запрос получает ack и не создаёт второй файл в горячей папке. - Нормализатор как интерфейс
src/normalizer/: протоколNormalizer(merged) -> documents | error, вход — склеенный ответ; в Срезе 0 —NullNormalizer. Успех файла ИС —error.code == ""(полеerrorнеnull: у реального успешного ответаerror = {"code": "", "message": ""}); ошибка — непустойerror.code(FILE_READ_ERROR,INVALID_FORMAT,INVALID_DOCUMENT,RECOGNITION_FAILED,UNKNOWN_ERROR) либоparse_error(файл не разобран как JSON). Если ошибочны все файлы — результатerrorс кодом первого ошибочного файла по имени (дляparse_error—INVALID_FORMAT), каждый ошибочный файл — строка<имя>: <код>: <сообщение>вmetrics.warnings. Есть хотя бы один успешный файл →NOT_IMPLEMENTED(Срез 3 реализует §11), ошибочные файлы — вmetrics.warningsтак же. - Эмулятор ИС Сканирование
src/scan_emulator/(python -m src.scan_emulator): следит заInput/, черезSCAN_EMULATOR_DELAY(2 с) кладёт вOutput/<uuid>/набор ответов: всеSCAN_FIXTURES_DIR/<name>.jsonи<name>_*.json(<name>— имя без uuid и расширения; не префикс<name>*) под именами<uuid>.jsonи<uuid>_<suffix>.json, сfile_uid = <uuid>(aep_3.pdf→<uuid>.json+<uuid>_ofr.json,aep_6.pdf→ один файл,aep_8.pdf→<uuid>_form4.json); набор появляется вOutput/<uuid>/атомарно целиком (временный каталог + переименование каталога). Для неизвестных имён — один ответ Content AI сerror.code = INVALID_DOCUMENT. Запускается тем же образом в compose (scan-emulator). - Ошибки транспорта → результат: запрос не по схеме →
SCHEMA_MISMATCH; S3 не читается →FILE_READ_ERROR; горячая папка недоступна →RECOGNITION_FAILED; ответ ИС не JSON (после готовности набора) →parse_errorфайла, при ошибочности всех файлов —INVALID_FORMAT; исключение →UNKNOWN_ERROR. - Healthcheck дополняется полями
natsиhotfolder(ok/down). - Проверки качества вместо CI:
make checkиmake check-fullпо../docs/dev/checks.md.
Capabilities
New Capabilities
image-recognition-gateway: адаптер ИС Сканирование — приём и подтверждение задания из NATS без JetStream, обмен файлами через горячие папки с таймаутом, сохранение сырого ответа, результат по контракту v1.0.scan-emulator: эмулятор ИС Сканирование для разработки и тестов, воспроизводящий протокол горячих папок на фикстурах../fixtures/ocr.
Modified Capabilities
Impact
- Таблицы DBML: сервис не пишет в БД. Косвенно:
recognition_jobядра получаетstatus = processing(started_at), затемstatus,error_code(TIMEOUT,NOT_IMPLEMENTED, коды Content AI),service_name = scan-gateway;external_task_idне передаётся (у ИС Сканирование идентификатор = нашjob_idв имени файла). Сырой ответraw/{job_id}.jsonв БД не регистрируется (v5.13 п. 5). - Правила: §11 — только как контракт нормализатора; §9.4 — статусы задания; OQ-13/14/15 — допущения в коде нормализатора (Срез 3).
- Зависимости: удаляются БД/фронт; остаётся
smbprotocol; добавляютсяruff,mypy,pytest*,jsonschema. - Внешние системы: NATS без JetStream, MinIO/S3, SMB-шара ИС Сканирование (прод) / локальный каталог (dev).
Non-goals
- Нормализация ответа Content AI в
statementDocument(§11, OQ-13, OQ-14) — Срез 3. - PNG/JPG — транспорт не различает типы, но проверяется только PDF (пары в
fixtures/ocr). - Повторы OCR (
max_attempts= 1 по OQ-15), периодический heartbeatprocessing. - Реальная SMB-шара в тестах —
SmbHotFolderпокрыт только unit-тестами с подменойsmbclient. - Конфигурации CI.