Andrey Epifancev 561eaa5c71 docs: соглашения шаблона в CLAUDE.md и OpenSpec change slice-0-skeleton
Срез 0 (issue #1): раздел «Соглашения шаблона» (что оставлено от шаблона команды, что
вырезается, что добавляется); change openspec/changes/slice-0-skeleton — proposal, design
с таблицей «Decisions made without discussion», спеки и tasks; в openspec/config.yaml
строки с двоеточием взяты в кавычки (YAML разбирал их как словари).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-03 02:43:41 +04:00

70 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: Срез 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, CI-шаблон).
**Соседние:** `../arm-core/openspec/changes/slice-0-skeleton`, `../tabular-parser/openspec/changes/slice-0-skeleton`.
## Why
Репозиторий — копия каркаса `arm-core` с фронтом и БД, тогда как по DECISION-4 это stateless-адаптер внешней ИС
Сканирование. Самая рискованная часть адаптера — не разбор JSON (Срез 3), а обмен через горячие папки с таймаутом и
без доступа к БД: положить файл, дождаться ответа, не потерять задание. Срез 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` (строка убрана из `.gitignore`), `Makefile`, `.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`: durable pull-consumer `scan-gateway` на `recognition.image.request` (`max_deliver` 1 —
OQ-15), до `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` → конверт `recognition.result` (`raw_s3_key` заполнен) → ack.
Таймаут → `error.code = TIMEOUT`.
- **Нормализатор как интерфейс** `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**: `.gitea/workflows/ci.yml` из шаблона docs.
## Capabilities
### New Capabilities
- `image-recognition-gateway`: адаптер ИС Сканирование — приём задания из NATS, обмен файлами через горячие папки с
таймаутом, сохранение сырого ответа, результат по контракту v1.0.
- `scan-emulator`: эмулятор ИС Сканирование для разработки и тестов, воспроизводящий протокол горячих папок на фикстурах `../fixtures/ocr`.
### Modified Capabilities
<!-- openspec/specs пуст -->
## Impact
- **Таблицы DBML:** сервис не пишет в БД. Косвенно: `recognition_job` ядра получает `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`.