Andrey Epifancev 6f2e227978 docs: change slice-0-skeleton под факты платформы — NATS без JetStream, Python 3.11, без CI и Nexus
Обмен с ядром на core NATS: подписка queue group, ack status=processing сразу по получении, process-local
LRU job_id против повторных запросов, публикация результата с повторами; удалены durable consumer / max_deliver /
ack_wait. Dockerfile python:3.11-slim; make check / make check-full вместо CI; raisa_* фильтруются при
dev-установке. Обновлены proposal, design (решения без обсуждения), specs, tasks, CLAUDE.md, openspec/config.yaml.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-03 16:21:47 +04:00

7.6 KiB
Raw Blame History

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(uuid) -> bytes | None) с двумя реализациями — LocalHotFolder (каталог; dev и тесты) и SmbHotFolder (smbclient шаблона; прод). Имена подкаталогов настраиваются (SCAN_INPUT_DIR = "Input", SCAN_OUTPUT_DIR = "Output"); файл кладётся как <job_id>_<original_name>, ответ ожидается в <output>/<job_id>/<job_id>.json.
  • Воркер src/core/worker.py (NATS без JetStream): подписка на recognition.image.request в queue group scan-gateway; по получении — сразу ack processing в recognition.result; до SCAN_MAX_INFLIGHT (4) заданий параллельно: S3 uploads/… → горячая папка → опрос каждые SCAN_POLL_SECONDS (5) до min(reply_deadline, now + SCAN_TIMEOUT_SECONDS=300) → сырой ответ в S3 raw/{job_id}.json → нормализатор → results/{file_id}/{job_id}/result.json → конверт результата (raw_s3_key заполнен). Таймаут → error.code = TIMEOUT. Process-local множество job_id в работе: повторный запрос получает ack и не создаёт второй файл в горячей папке.
  • Нормализатор как интерфейс src/normalizer/: протокол Normalizer(raw_json) -> documents | error; в Срезе 0 — NullNormalizer: ошибки Content AI (error.code из ответа: FILE_READ_ERROR, INVALID_FORMAT, INVALID_DOCUMENT, RECOGNITION_FAILED, UNKNOWN_ERROR) пробрасываются как есть; успешный ответ → NOT_IMPLEMENTED (Срез 3 реализует §11).
  • Эмулятор ИС Сканирование src/scan_emulator/ (python -m src.scan_emulator): следит за Input/, через SCAN_EMULATOR_DELAY (2 с) кладёт в Output/<uuid>/<uuid>.json содержимое SCAN_FIXTURES_DIR/<имя без uuid и расширения>*.json (aep_5.pdf → aep_5.json, aep_8.pdf → aep_8_form4.json), для неизвестных имён — ответ Content AI с error.code = INVALID_DOCUMENT. Запускается тем же образом в compose (scan-emulator).
  • Ошибки транспорта → результат: запрос не по схеме → SCHEMA_MISMATCH; S3 не читается → FILE_READ_ERROR; горячая папка недоступна → RECOGNITION_FAILED; ответ ИС не JSON → 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), периодический heartbeat processing.
  • Реальная SMB-шара в тестах — SmbHotFolder покрыт только unit-тестами с подменой smbclient.
  • Конфигурации CI.