Срез 0 [1.3]: README.md и актуализированный CLAUDE.md, отметка задачи
README.md описывает сервис как он есть: границы (без БД и фронта), поток обработки, протокол горячих папок, переменные окружения шлюза и эмулятора, коды ошибок, контракт NATS, запуск (dev, полный круг против compose и эмулятора, проверки), /healthcheck, Docker-образ и ограничения Среза 0. CLAUDE.md: «Соглашения шаблона» переписаны по фактическому дереву api/, устаревший SCAN_EMULATOR=1 заменён на `make emulator` / `python -m src.scan_emulator`. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
700aba4dc3
commit
22f62a4eda
49
CLAUDE.md
49
CLAUDE.md
@ -12,29 +12,38 @@
|
||||
- Нормализация по фактическим ответам `../fixtures/ocr/aep_*.json`, а не по ТЗ (OQ-13, OQ-14): confidence → 0–100,
|
||||
поля организации/даты — по тому, что реально приходит.
|
||||
- Один PDF может содержать ББ и ОФР → два документа в результате.
|
||||
- Dev-режим: эмулятор ИС Сканирование, возвращающий `aep_N.json` по имени файла (`SCAN_EMULATOR=1`).
|
||||
- Dev-режим: эмулятор ИС Сканирование, возвращающий `aep_N.json` по имени файла: `make emulator` (то же, что
|
||||
`python -m src.scan_emulator` из `api/`); в контейнере — тот же образ с `command: python -m src.scan_emulator`.
|
||||
- Описание сервиса, протокол горячих папок, env, запуск и коды ошибок — `README.md`; требования и решения Среза 0 —
|
||||
`openspec/changes/slice-0-skeleton/` (`design.md`).
|
||||
|
||||
## Соглашения шаблона
|
||||
Тот же шаблон команды, что у `arm-core` («Шаблон FastAPI», `RSHB/DKB_ARMKA@ba172f8`); общие соглашения —
|
||||
`../docs/context/03-architecture.md`, «Соглашения шаблона команды». Сейчас репозиторий — полная копия каркаса `arm-core`;
|
||||
change `slice-0-skeleton` приводит его к сервису **без фронта и без БД**, но с SMB. Все команды — из `api/`.
|
||||
`../docs/context/03-architecture.md`, «Соглашения шаблона команды». Репозиторий приведён к сервису **без фронта и без
|
||||
БД**, но с SMB. Команды `make` — из корня репозитория (рецепты сами заходят в `api/`), `pytest` — из `api/`.
|
||||
|
||||
**Оставлено от шаблона:** `api/run_server.py` (точка входа платформы, `SERVER__HOST/PORT`), `api/src/app.py` (lifespan с
|
||||
регистрацией SMB-сессии, `GET /healthcheck`, gzip/proxy middleware, обработчики ошибок), `core/config.py` (pydantic-settings,
|
||||
`OPENBAO__MINIO__*`, `OPENBAO__NATS__*`, `OPENBAO__SMB__*`, `DEBUG`), `core/clients.py` (S3), `core/nats_callback.py`,
|
||||
`core/logger.py`, `core/exception_handlers.py`, `common/exceptions.py`, `pytest.ini`, `requirements*.txt` (включая
|
||||
`smbprotocol`), `raisa_config.yml`, `setup/setup.sh` + `pip.conf` (без npm-шага).
|
||||
**Оставлено от шаблона:** `api/run_server.py` (точка входа платформы, `SERVER__HOST/PORT`), `api/src/app.py` (lifespan:
|
||||
SMB-сессия, S3, NATS, воркер; `GET /healthcheck`; gzip/proxy middleware), `core/config.py` (pydantic-settings,
|
||||
`OPENBAO__MINIO__*`, `OPENBAO__NATS__*`, `OPENBAO__SMB__*`, `DEBUG`), `core/logger.py`, `core/exception_handlers.py`,
|
||||
`common/exceptions.py`, `api/pytest.ini`, `api/requirements*.txt` (включая `smbprotocol`), `raisa_config.yml`,
|
||||
`setup/setup.sh` + `setup/pip.conf` (без npm-шага).
|
||||
|
||||
**Вырезано (Срез 0):** `web/` целиком и `setup/.npmrc`; из `app.py` — mount SPA (`SPAStaticFiles`, `os.makedirs("web")`),
|
||||
CORS, импорт `raisa_fastapi_protected_api`; `db/`, `models/`, `repositories/`, `alembic/`, `alembic.ini`; из настроек —
|
||||
`OPENBAO.DATABASE`; из `requirements.txt` — `asyncpg`, `SQLAlchemy`, `alembic`, `argon2-cffi`, `raisa_fastapi_protected_api`;
|
||||
шаблонный `README.md` заменён описанием сервиса.
|
||||
**Вырезано (Срез 0):** `web/`, `setup/.npmrc`; из `app.py` — mount SPA, CORS, `raisa_fastapi_protected_api`; `db/`,
|
||||
`models/`, `repositories/`, `alembic/`; `OPENBAO.DATABASE`; из `requirements.txt` — `asyncpg`, `SQLAlchemy`, `alembic`,
|
||||
`raisa_fastapi_protected_api`; шаблонные `core/nats_callback.py` и `README.md` заменены своими.
|
||||
|
||||
**Добавлено:** `api/src/gateway/` (горячие папки: положить `UUID_имя.pdf` во «Входящие», опрашивать «Исходящие/UUID/UUID.json»;
|
||||
режим `local` — обычный каталог для dev, `smb` — как в шаблоне через `smbclient`; референс поллинга —
|
||||
`../reference-old-arm/api/src/core/PDFWorker`), `api/src/normalizer/` (ответ Content AI → `statementDocument`; Срез 3),
|
||||
`api/src/contract/` (pydantic-модели `recognition-result` v1.0; идентичность с `../docs/contracts` проверяется в `make check`),
|
||||
`api/src/core/worker.py` (подписка queue group `recognition.image.request` → S3 → горячая папка → ожидание ≤ `reply_deadline`
|
||||
→ S3 `raw/` и `results/` → `recognition.result`), `api/src/scan_emulator/` (эмулятор ИС Сканирование для compose: следит за
|
||||
«Входящими», кладёт `../fixtures/ocr/aep_N.json` по имени файла в «Исходящие»; запускается тем же образом, `SCAN_EMULATOR=1`),
|
||||
`Dockerfile`, `api/pyproject.toml` (ruff, mypy). Проверки: `pytest`, `ruff check . && ruff format --check .`, `mypy src`.
|
||||
**Добавлено:**
|
||||
- `api/src/gateway/` — горячие папки: протокол `HotFolder` (`hotfolder.py`: `LocalHotFolder`, `inbox_name`), `smb.py`
|
||||
(`SmbHotFolder`, импортируется лениво), фабрика `create_hotfolder`; референс поллинга — `../reference-old-arm/api/src/core/PDFWorker`.
|
||||
- `api/src/core/` — `worker.py` (задание: проверка → ack → S3 → горячая папка → опрос → `raw/` → нормализатор →
|
||||
результат; ошибки по таблице D-7), `nats.py` (core NATS без JetStream, queue group `scan-gateway`, публикация с
|
||||
повторами), `clients.py` (`S3Storage`), `health.py` (`nats`/`hotfolder` в `/healthcheck`).
|
||||
- `api/src/normalizer/` — `Normalizer`, `NullNormalizer` (Срез 0: ошибка ИС → её код, успех → `NOT_IMPLEMENTED`);
|
||||
настоящая нормализация — Срез 3.
|
||||
- `api/src/contract/` + `api/contracts/` — pydantic-модели и байт-в-байт копии схем `../docs/contracts`; идентичность
|
||||
проверяет `api/tests/unit/test_contract.py` (входит в `make check`).
|
||||
- `api/src/scan_emulator/` — эмулятор ИС Сканирование (`SCAN_FIXTURES_DIR`, `SCAN_EMULATOR_DELAY`,
|
||||
`SCAN_EMULATOR_POLL_SECONDS`); в compose контейнера пока нет — `make emulator`.
|
||||
- `Makefile` (`venv`, `lint`, `test`, `test-integration`, `check`, `check-full`, `up`, `emulator`), `api/pyproject.toml`
|
||||
(ruff, mypy), `Dockerfile` + `.dockerignore` (один образ: шлюз и эмулятор), `api/.env.example`, `api/.env.test`.
|
||||
- Тесты: `api/tests/unit/`, `api/tests/integration/` (маркеры `integration`, `fixtures`); перед PR — `make check-full`.
|
||||
|
||||
347
README.md
347
README.md
@ -1,163 +1,224 @@
|
||||
[](https://github.com/psf/black) [](https://pycqa.github.io/isort/) [](https://github.com/pylint-dev/pylint)
|
||||
__________________________________________________________________________________________________________
|
||||
# dkb_armka, v 0.0.1
|
||||
____
|
||||
# scan-gateway
|
||||
|
||||
| | Исполнитель | Заказчик |
|
||||
|-----------|:----------------------------------------:|:----------------------------------------------:|
|
||||
| Сотрудник | raykov-mse | raykov-mse |
|
||||
| Отдел | aurora | aurora |
|
||||
| Контакты | raykovmse@rshb.ru | raykovmse@rshb.ru |
|
||||
-----
|
||||
# Шаблон FastAPI
|
||||
Адаптер внешней ИС Сканирование (Content AI) для АРМ КА. Принимает из NATS запросы на распознавание изображений
|
||||
(PDF/PNG/JPG), передаёт файл в ИС через «горячие папки», забирает ответ, сохраняет сырой ответ в S3, нормализует его
|
||||
и публикует результат в `recognition.result`. Контракт обмена — `../docs/contracts/` (v1.0), архитектура —
|
||||
`../docs/context/03-architecture.md`.
|
||||
|
||||
## Описание
|
||||
Границы сервиса (DECISION-4):
|
||||
|
||||
Данное шаблон предоставляет проект, в котором приложение состоит из API и UI. При использовании данного типа проекта, Вы можете разрабатывать, как отдельные API сервисы, так и WEB приложения с интерфейсом. Для построения WEB интерфейса используется React. Далее описаны основные иснтрументы предустановленные данным шаблоном:
|
||||
- нет базы данных: единственный писатель в БД — `arm-core`; шлюз видит только NATS, S3 и горячие папки;
|
||||
- нет фронта; HTTP — только `GET /healthcheck`;
|
||||
- сырой ответ ИС лежит в S3 (`raw/{job_id}.json`) и в БД не регистрируется;
|
||||
- саму ИС Сканирование мы не пишем: для разработки есть эмулятор (`api/src/scan_emulator/`).
|
||||
|
||||
### Подготовка к разработке
|
||||
Состояние: Срез 0 (скелет). Сквозной путь работает, нормализатор — заглушка (см. «Ограничения Среза 0»).
|
||||
Правила для агента и соглашения репозитория — `CLAUDE.md`; требования и решения — `openspec/changes/slice-0-skeleton/`.
|
||||
|
||||
Для удобной настройки проекта под разработку. Перейдите в каталог setup и выполните слудеющие команды:
|
||||
## Поток обработки
|
||||
|
||||
```
|
||||
recognition.image.request (NATS, queue group scan-gateway)
|
||||
1. разбор и проверка запроса ──нет/не по схеме──► error SCHEMA_MISMATCH (без ack)
|
||||
2. ack: recognition.result, status=processing (до чтения S3 и до ожидания слота)
|
||||
3. слот (SCAN_MAX_INFLIGHT одновременных заданий)
|
||||
4. файл из S3 (s3.bucket/s3.key)
|
||||
5. горячая папка: <Input>/<job_id>_<original_name>
|
||||
6. опрос <Output>/<job_id>/<job_id>.json каждые SCAN_POLL_SECONDS, до дедлайна
|
||||
7. сырой ответ ──► S3 raw/{job_id}.json (как пришёл, до нормализации)
|
||||
8. нормализатор ──► (done/needs_check) S3 results/{file_id}/{job_id}/result.json
|
||||
9. recognition.result: итоговый конверт (done | needs_check | error), в нём raw_s3_key
|
||||
```
|
||||
|
||||
Каждое задание заканчивается опубликованным конвертом: сбой превращается в `error` с кодом (таблица ниже), а не в
|
||||
молчание. Если не удалось даже опубликовать (после повторов) — запись в лог уровня error, ядро закроет задание по
|
||||
своему `reply_deadline`. Падение шлюза после ack теряет задание до `reply_deadline` (без JetStream иначе нельзя).
|
||||
|
||||
Повторный запрос с `job_id`, который уже в работе или в LRU обработанных (`DONE_CACHE_SIZE`), получает повторный
|
||||
ack (с `attempt` повторного запроса); файл в ИС второй раз не отправляется.
|
||||
|
||||
## Протокол горячих папок
|
||||
|
||||
Реализации интерфейса `HotFolder` (`api/src/gateway/hotfolder.py`: `put`, `poll`, `healthy`):
|
||||
|
||||
| Режим | Реализация | Для чего |
|
||||
|---|---|---|
|
||||
| `local` (по умолчанию) | `LocalHotFolder`, обычный каталог `SCAN_HOTFOLDER_PATH` | разработка, тесты, эмулятор |
|
||||
| `smb` | `SmbHotFolder` (`api/src/gateway/smb.py`, `smbprotocol`), шара из `OPENBAO__SMB__*` | платформа |
|
||||
|
||||
- Входящий файл: `<SCAN_INPUT_DIR>/<job_id>_<original_name>`. `original_name` приходит из запроса и не доверяется: берётся
|
||||
последний компонент пути, символы, недопустимые в Windows/SMB (`: < > " | ? *` и управляющие), заменяются на `_`,
|
||||
завершающие точки и пробелы отбрасываются; кириллица и пробелы сохраняются. Если имени не осталось —
|
||||
`RECOGNITION_FAILED`.
|
||||
- Запись атомарна: сначала `.<имя>.part` в том же каталоге, затем переименование. Каталог `Input` создаётся при
|
||||
необходимости (без родителей), корень горячей папки сам не создаётся: нет корня — `RECOGNITION_FAILED`.
|
||||
- Ответ ИС: `<SCAN_OUTPUT_DIR>/<job_id>/<job_id>.json` (читается только этот файл). `job_id` проверяется на
|
||||
безопасность пути. Ответ, который ещё пишется (не JSON), перечитывается на следующем опросе.
|
||||
- Таймаут ожидания: `min(reply_deadline, момент записи файла + SCAN_TIMEOUT_SECONDS)`; опрос каждые `SCAN_POLL_SECONDS`.
|
||||
Ошибка чтения «Исходящих» не прерывает ожидание. Если срок вышел, пока задание ждало слот, файл в ИС не отправляется.
|
||||
- Повторной отправки в ИС нет (OQ-15): после `TIMEOUT` входящий файл остаётся в каталоге, поздний ответ ИС теряется
|
||||
(сырой файл остаётся на шаре).
|
||||
- Эмулятор ИС в `local`-режиме: для файла `<uuid>_<имя>` ждёт `SCAN_EMULATOR_DELAY`, пишет ответ из фикстуры
|
||||
(`<имя без расширения>.json`, затем первый по алфавиту `<имя без расширения>*.json`; `file_uid` подставляется), потом
|
||||
переименовывает входящий файл в `….done`. Нет фикстуры — ответ-ошибка `INVALID_DOCUMENT`. Dot-файлы и `.done`
|
||||
игнорируются.
|
||||
|
||||
## Переменные окружения
|
||||
|
||||
Файл окружения — `ENV_FILE` (по умолчанию `.env`, путь от текущего каталога, то есть от `api/`); переменные окружения
|
||||
приоритетнее файла. Шаблон — `api/.env.example`. Настройки читаются при старте, не при импорте.
|
||||
|
||||
| Переменная | По умолчанию | Назначение |
|
||||
|---|---|---|
|
||||
| `OPENBAO__MINIO__MINIO_URL` | — (обязательна) | адрес MinIO `host:port`, без схемы |
|
||||
| `OPENBAO__MINIO__MINIO_ADMIN_LOGIN` | — (обязательна) | логин MinIO |
|
||||
| `OPENBAO__MINIO__MINIO_ADMIN_PASSWORD` | — (обязательна) | пароль MinIO |
|
||||
| `OPENBAO__MINIO__MINIO_BUCKET` | — (обязательна) | единственный бакет сервиса; запрос с другим `s3.bucket` -> `FILE_READ_ERROR` |
|
||||
| `OPENBAO__MINIO__MINIO_SECURE` | `true` | https; для MinIO из compose — `false` |
|
||||
| `OPENBAO__NATS__NATS_URL` | — (обязательна) | адрес NATS |
|
||||
| `OPENBAO__NATS__NATS_SEED`, `OPENBAO__NATS__NATS_JWT` | пусто | nkeys/JWT платформы; оба пусты — без учётных данных; задан один — ошибка конфигурации |
|
||||
| `OPENBAO__SMB__SMB_HOST`, `OPENBAO__SMB__SMB_PATH`, `OPENBAO__SMB__SMB_LOGIN`, `OPENBAO__SMB__SMB_PASSWORD` | пусто | шара горячих папок; обязательны только при `SCAN_HOTFOLDER_MODE=smb`; `SMB_PATH` — `share/dir` или полный UNC |
|
||||
| `NATS_ENABLED` | `true` | `false` — воркер не запускается, подключения к NATS нет (`/healthcheck.nats = disabled`) |
|
||||
| `PUBLISH_RETRIES` | `3` | повторы публикации ack/результата после первой попытки (всего до 4) |
|
||||
| `DONE_CACHE_SIZE` | `1000` | размер LRU обработанных `job_id` (дедупликация) |
|
||||
| `SCAN_HOTFOLDER_MODE` | `local` | `local` или `smb` |
|
||||
| `SCAN_HOTFOLDER_PATH` | пусто | корень горячей папки в режиме `local` (обязателен, каталог должен существовать) |
|
||||
| `SCAN_INPUT_DIR` | `Input` | «Входящие» относительно корня |
|
||||
| `SCAN_OUTPUT_DIR` | `Output` | «Исходящие» относительно корня |
|
||||
| `SCAN_POLL_SECONDS` | `5` | период опроса «Исходящих», секунды |
|
||||
| `SCAN_TIMEOUT_SECONDS` | `300` | потолок ожидания ответа ИС, секунды (дополнительно к `reply_deadline`) |
|
||||
| `SCAN_MAX_INFLIGHT` | `4` | одновременно обрабатываемых заданий |
|
||||
| `SERVICE_NAME` | `scan-gateway` | `service.name` в конверте |
|
||||
| `VERSION` | `1.0.0` | версия сервиса (`/healthcheck`, `service.version`) |
|
||||
| `ROOT_PATH` | `/` | префикс FastAPI за прокси |
|
||||
| `SERVER__HOST`, `SERVER__PORT` | `0.0.0.0`, `8000` | адрес HTTP-сервера `run_server.py` |
|
||||
| `DEBUG` | `false` | флаг шаблона платформы; поведение шлюза не меняет |
|
||||
| `ENV_FILE` | `.env` | какой файл окружения читать |
|
||||
|
||||
Эмулятор (`python -m src.scan_emulator`) читает свои переменные и не использует настройки шлюза (в его контейнере нет
|
||||
MinIO и NATS); те же значения подхватываются из `ENV_FILE`:
|
||||
|
||||
| Переменная | По умолчанию | Назначение |
|
||||
|---|---|---|
|
||||
| `SCAN_HOTFOLDER_PATH` | — (обязательна) | корень горячей папки; должен существовать, иначе код выхода 2 |
|
||||
| `SCAN_INPUT_DIR`, `SCAN_OUTPUT_DIR` | `Input`, `Output` | подкаталоги корня (как у шлюза) |
|
||||
| `SCAN_FIXTURES_DIR` | `../../fixtures/ocr` | каталог эталонных ответов ИС (`aep_N.json`); путь от `api/` |
|
||||
| `SCAN_EMULATOR_DELAY` | `2` | задержка перед выдачей ответа, секунды (>= 0) |
|
||||
| `SCAN_EMULATOR_POLL_SECONDS` | `1` | период просмотра «Входящих», секунды (> 0) |
|
||||
|
||||
## Коды ошибок
|
||||
|
||||
Итоговый конверт `error` содержит `error.code` и `error.message`. Нормализатор может вернуть любой код `ErrorCode`
|
||||
контракта, остальные коды ставит воркер (`api/src/core/worker.py`).
|
||||
|
||||
| Код | Когда | Ack |
|
||||
|---|---|---|
|
||||
| `SCHEMA_MISMATCH` | тело не JSON / не по схеме / MAJOR версии ≠ 1 / `pipeline` ≠ `image`. Конверт публикуется, только если в теле есть корректные `job_id` и `file_id`, иначе запрос пишется в лог и отбрасывается | нет |
|
||||
| `FILE_READ_ERROR` | объект S3 не прочитан; `s3.bucket` не совпадает с бакетом сервиса | да |
|
||||
| `RECOGNITION_FAILED` | не записан файл во «Входящие» (горячая папка недоступна); недопустимое `original_name`; «Исходящие» не читались ни разу до дедлайна | да |
|
||||
| `TIMEOUT` | ИС не ответила до `min(reply_deadline, старт + SCAN_TIMEOUT_SECONDS)`; `reply_deadline` истёк до отправки файла | да |
|
||||
| `INVALID_FORMAT` | ответ ИС не разбирается как JSON (сырой ответ сохранён, `raw_s3_key` указан); корень ответа не объект | да |
|
||||
| код ошибки ИС | в ответе ИС есть `error`: код пробрасывается, если он входит в `ErrorCode` (например, `INVALID_DOCUMENT`) | да |
|
||||
| `NOT_IMPLEMENTED` | ответ ИС без ошибки (нормализатор Среза 0 — заглушка) | да |
|
||||
| `UNKNOWN_ERROR` | сырой ответ или результат не записан в S3; нормализатор упал; неизвестный код ИС; непредвиденная ошибка | да |
|
||||
|
||||
## Контракт NATS (кратко)
|
||||
|
||||
Схемы и примеры — `../docs/contracts/`; их байт-в-байт копии лежат в `api/contracts/` (идентичность проверяет unit-тест
|
||||
`api/tests/unit/test_contract.py`, входит в `make check`), pydantic-модели — `api/src/contract/`.
|
||||
|
||||
- Запрос: subject `recognition.image.request`, подписка в queue group `scan-gateway`, `Nats-Msg-Id = <job_id>:<attempt>`.
|
||||
- Ответы — subject `recognition.result`: ack `status=processing`, `Nats-Msg-Id = ack:<job_id>:<attempt>`; итог
|
||||
`status = done | needs_check | error`, `Nats-Msg-Id = result:<job_id>:<attempt>`. `service.name = "scan-gateway"`.
|
||||
- Ключи S3: исходник — из запроса (`s3.key`), сырой ответ — `raw/{job_id}.json`, результат —
|
||||
`results/{file_id}/{job_id}/result.json`. Бакет один.
|
||||
- NATS без JetStream: переподключение и повторная подписка — на `nats-py`, ack и повторы публикации — на уровне
|
||||
приложения (`api/src/core/nats.py`).
|
||||
|
||||
## Запуск
|
||||
|
||||
Требуются Docker с compose v2, `uv`, Python 3.11. Все команды `make` — из корня репозитория; backend-команды
|
||||
рецепты выполняют в `api/`. Рядом в workspace должны лежать `../docs` (compose, контракт) и `../fixtures`.
|
||||
|
||||
### Разработка
|
||||
|
||||
```bash
|
||||
cd setup
|
||||
chmod +x setup.sh
|
||||
./setup.sh
|
||||
make up # NATS, MinIO, PostgreSQL: compose-проект arm-ka из ../docs/dev
|
||||
make venv # api/.venv (Python 3.11), зависимости без raisa_*
|
||||
cp api/.env.example api/.env # значения совпадают с ../docs/dev (NATS 4222, MinIO 9000, бакет arm-ka)
|
||||
mkdir -p api/.hotfolder # корень горячей папки: сам не создаётся
|
||||
cd api && .venv/bin/python run_server.py # сервис на :8000
|
||||
make emulator # в другом терминале: эмулятор ИС Сканирование
|
||||
curl -s localhost:8000/healthcheck
|
||||
```
|
||||
По итогу успешного выполнения Вы получите сообщение следующего
|
||||
|
||||
Целей `down`/`stop` в `Makefile` нет намеренно: compose-проект `arm-ka` общий для сервисов workspace.
|
||||
Контейнера `scan-emulator` в compose пока нет (его добавит репозиторий `docs` позже), поэтому эмулятор запускается
|
||||
локально командой `make emulator`.
|
||||
|
||||
### Полный круг против compose и эмулятора
|
||||
|
||||
Исполняемый пример — `api/tests/integration/test_roundtrip.py` (поднимает приложение и эмулятор в процессе теста,
|
||||
публикует запрос, проверяет ack, итоговый конверт, объекты S3). Вручную те же шаги:
|
||||
|
||||
1. Выполнить «Разработку» выше: сервис и эмулятор работают, `/healthcheck` отдаёт `nats = ok`, `hotfolder = ok`.
|
||||
2. Положить файл в MinIO (консоль http://localhost:9001 или `mc`) в бакет `arm-ka`, например
|
||||
`uploads/<file_id>/<job_id>/aep_5.pdf` (имя файла должно совпадать с фикстурой эмулятора: `aep_5.*` -> `aep_5.json`,
|
||||
`aep_8.*` -> `aep_8_form4.json`).
|
||||
3. Подписаться на `recognition.result` любым клиентом NATS.
|
||||
4. Опубликовать в `recognition.image.request` тело по образцу `api/contracts/examples/request-image.json`
|
||||
(свои `job_id` и `file_id` — UUID, `original_name`, `s3.key`, `reply_deadline` в будущем, `attempt` = 1) с заголовком
|
||||
`Nats-Msg-Id: <job_id>:1`.
|
||||
5. Ожидать: ack `processing`, через `SCAN_EMULATOR_DELAY` — конверт `error`. Для `aep_5`/`aep_8` это `INVALID_DOCUMENT`
|
||||
с сообщением ИС (обе реальные фикстуры — ответы-ошибки); для неизвестного имени — `INVALID_DOCUMENT` эмулятора.
|
||||
В бакете появится `raw/<job_id>.json`, `raw_s3_key` указан в конверте.
|
||||
6. Путь «успех -> `NOT_IMPLEMENTED`» требует успешной фикстуры: добавьте в `SCAN_FIXTURES_DIR` синтетический
|
||||
`<имя>.json` с `error: null` и непустым `data_ocr` (так делает `test_roundtrip.py`, фикстура `ok_doc.json`).
|
||||
|
||||
### Проверки
|
||||
|
||||
```bash
|
||||
complete setup web application
|
||||
To start api, cd to api directory, and use venv, start run_server.py
|
||||
To start api, cd to web directory, and use command npm run start!
|
||||
Happy Hacking! :)
|
||||
make check # lint (ruff check, ruff format --check, mypy src) + test (pytest -m "not integration")
|
||||
make test-integration # pytest -m integration: нужны NATS и MinIO (make up)
|
||||
make check-full # check + test-integration; перед PR
|
||||
```
|
||||
|
||||
Далее просто запускайте проекты и приступайте к разработке
|
||||
Тесты запускаются из `api/`: `.env.test` подключается через `ENV_FILE=.env.test` в `api/pytest.ini` и разрешается
|
||||
относительно текущего каталога (рецепты `make` это делают сами). Маркеры: `integration` — нужна инфраструктура,
|
||||
исключается из `make test`; `fixtures` — нужны реальные данные из `../fixtures` (нет репозитория — тесты пропускаются).
|
||||
Тесты: `api/tests/unit/`, `api/tests/integration/`. Интеграционные тесты делят NATS с другими сервисами workspace:
|
||||
если в нём работает другой экземпляр `scan-gateway`, он может забрать запрос теста из queue group.
|
||||
Сборка Docker-образа в `make check-full` пока не входит.
|
||||
|
||||
### Backend
|
||||
## `/healthcheck`
|
||||
|
||||
Исходный код API находится в каталоге api. После клонирования репозитория требуется создать виртуальное окружение и установить зависимости, это достигается следующими командами:
|
||||
`GET /healthcheck` всегда отвечает 200; состояние — в полях:
|
||||
|
||||
```bash
|
||||
cd api
|
||||
python -m venv ./.venv
|
||||
cp ./../setup/pip.conf ./.venv/pip.conf
|
||||
source ./.venv/bin/python
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
| Поле | Значения |
|
||||
|---|---|
|
||||
| `uptime` | время старта процесса |
|
||||
| `version` | `VERSION` |
|
||||
| `nats` | `ok` — соединение установлено; `down` — подключение идёт, переподключение, сбой; `disabled` — `NATS_ENABLED=false` |
|
||||
| `hotfolder` | `ok` — `HotFolder.healthy()` (корень существует и доступен); `down` — нет, проверка не уложилась в 2 с или упала |
|
||||
|
||||
Запуск приложения осуществляется через файл: run_server.py
|
||||
Подключение к NATS идёт в фоне: HTTP отвечает сразу (`nats = down`), подписка появляется после подключения.
|
||||
Результат проверки горячей папки кэшируется на 5 с.
|
||||
|
||||
В качестве фреймворка ля api используется FastAPI. (Как понятно из названия шаблона) Подробнее ознакомится с возможностями Fast API вы можете в официальной документации: [Fast API документация](https://fastapi.tiangolo.com/)
|
||||
## Docker-образ
|
||||
|
||||
Вы можете дополнительно устанавливать библиотеки необходимые для разработки. Настоятельно не рекомендуется изменять базовые предустановки из файла requirements.txt
|
||||
Один образ (`Dockerfile`, `python:3.11-slim`, контекст сборки — корень репозитория, `docker build -t scan-gateway .`)
|
||||
для шлюза и для эмулятора. По умолчанию запускается шлюз (`python run_server.py`, порт 8000); эмулятор — тем же образом
|
||||
с `command: python -m src.scan_emulator`. Пакеты `raisa_*` в образ не ставятся. `api/.env` в образ не попадает (см.
|
||||
`.dockerignore`): окружение приходит из compose; шлюзу и эмулятору нужен общий том с горячей папкой по пути
|
||||
`SCAN_HOTFOLDER_PATH`, эмулятору — каталог фикстур по пути `SCAN_FIXTURES_DIR`.
|
||||
|
||||
Базовый шаблон предоставляет минимальный функционал по работе с данными. Далее описаны основные моменты влияющие на работу приложения.
|
||||
|
||||
#### Основная структура проекта
|
||||
|
||||
```bash
|
||||
api
|
||||
├── requirements.txt
|
||||
├── run_server.py
|
||||
├── src
|
||||
│ ├── app.py
|
||||
│ ├── __init__.py
|
||||
│ ├── settings
|
||||
│ │ ├── __init__.py
|
||||
│ │ ├── models.py
|
||||
│ │ └── settings.py
|
||||
└── web
|
||||
```
|
||||
|
||||
Вы можете придерживатся любой структуры проекта, главное, чтобы у Вас присутствовал файл run_server.py. Который запускает web сервер uvicorn или gunicorn. Также обязательно Ваш сервер должен подниматся на определенном хосте и порте. Требуемый хост и порт передаются внутрь через переменные окружения SERVER_HOST, SERVER_PORT
|
||||
|
||||
```python
|
||||
uvicorn.run(
|
||||
app,
|
||||
host=settings.server.HOST,
|
||||
port=settings.server.PORT,
|
||||
lifespan="on"
|
||||
)
|
||||
```
|
||||
|
||||
#### Отказ от ui (react)
|
||||
|
||||
Если внутри вашего приложения не требуется добавлять визуальный интерфейс, а Вам требуется исключительно разработать API, то в таком случае не стоит удалять директорию web находящуюся на уровне с директорией api. ТРебуется внести небольшие правки в конфигурацию приложения, в частности в файл: /api/src/app.py, удалив следующее содержимое. В таком случае Fast API сервис не будет обрабатывать статические файлы, от web приложения.
|
||||
|
||||
```python
|
||||
#Подключение собранного web приложения
|
||||
app.mount("/", StaticFiles(directory="web", html=True), name="web")
|
||||
```
|
||||
|
||||
### Frontend
|
||||
|
||||
Исходный код UI находится в каталоге web. После клонирования репозитория требуется установить зависимости, это достигается следующими командами:
|
||||
|
||||
```bash
|
||||
cd web
|
||||
npm install
|
||||
```
|
||||
|
||||
Запуск приложения осуществляется через файл команду:
|
||||
|
||||
```bash
|
||||
npm run start
|
||||
```
|
||||
|
||||
В качестве фреймворка для ui используется React. Подробнее ознакомится с возможностями React вы можете в официальной документации: [React документация](https://react.dev/reference/react)
|
||||
|
||||
Вы можете дополнительно устанавливать библиотеки необходимые для разработки. Настоятельно не рекомендуется изменять базовые предустановки из файла package.json
|
||||
|
||||
Базовый шаблон предоставляет минимальный интерфейс по работе с данными. Далее описаны основные моменты влияющие на работу web приложения.
|
||||
|
||||
#### Основная структура проекта
|
||||
|
||||
```bash
|
||||
web
|
||||
├── biome.json
|
||||
├── .npmrc
|
||||
├── dist
|
||||
├── package.json
|
||||
├── postcss.config.cjs
|
||||
├── public
|
||||
│ └── index.html
|
||||
├── README.md
|
||||
├── rsbuild.config.ts
|
||||
├── src
|
||||
│ ├── App.tsx
|
||||
│ ├── config.ts
|
||||
│ ├── env.d.ts
|
||||
│ ├── index.css
|
||||
│ └── index.tsx
|
||||
├── tailwind.config.js
|
||||
└── tsconfig.json
|
||||
```
|
||||
|
||||
Вы можете придерживатся любой структуры проекта, внутри src. Но на верхнем уровне находятся файлы конфигурации и сборки их требуется изменять только в крайнем случае. Для сборки приложения используется [RSBuild](https://rsbuild.dev/)
|
||||
|
||||
Для создания UI настоятельно рекомендуется использовать предустановленную библиотеку компонентови стилей:
|
||||
|
||||
- tailwindcss
|
||||
- daisy-ui
|
||||
- react-daisy-ui
|
||||
- raisa-ui
|
||||
|
||||
!!! Важно, если внутри web приложения Вы хотите обращатся к API данного проекта, используйте следующую конструкцию:
|
||||
|
||||
```javascript
|
||||
|
||||
fetch(`${Config.API_URL}/api`, {
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
method: 'GET'
|
||||
})
|
||||
|
||||
```
|
||||
|
||||
Речь идет об вызываемом эндпоинте ${Config.API_URL}/api, Обязательно используйте конструкцию данного вида т.к. при сборке приложения статический сайт обращается относительно api и вместо
|
||||
запросов вида http://localhost:8000/api должно быть так: /api
|
||||
## Ограничения Среза 0
|
||||
|
||||
- `NullNormalizer` (`api/src/normalizer/`): ошибка ИС пробрасывается с её кодом, любой успешный ответ даёт
|
||||
`NOT_IMPLEMENTED`. Настоящая нормализация `data_ocr` в `statementDocument` — Срез 3.
|
||||
- Реальные фикстуры `aep_5.json` и `aep_8_form4.json` — ответы-ошибки ИС (`INVALID_DOCUMENT`, пустой `data_ocr`):
|
||||
успешных образцов нет, это открытый вопрос Среза 3.
|
||||
- Режим `smb` покрыт unit-тестами с подменённым `smbclient`; на реальной шаре ИС Сканирование не проверялся. Фактические
|
||||
имена каталогов и путь шары на стенде неизвестны — они настраиваются, а не зашиты в код.
|
||||
- Повторной отправки в ИС нет (OQ-15); падение шлюза после ack теряет задание до `reply_deadline` ядра.
|
||||
- Контейнер `scan-emulator` в compose отсутствует, сборка образа в `make check-full` не входит.
|
||||
|
||||
@ -28,7 +28,7 @@
|
||||
|
||||
### Task 3: [1.3] `README.md` — описание сервиса (назначение, протокол горячих папок, env, запуск
|
||||
|
||||
- [ ] `README.md` — описание сервиса (назначение, протокол горячих папок, env, запуск против compose и эмулятора), `CLAUDE.md` — актуализировать «Соглашения шаблона» по факту; проверка: упомянутые файлы существуют
|
||||
- [x] `README.md` — описание сервиса (назначение, протокол горячих папок, env, запуск против compose и эмулятора), `CLAUDE.md` — актуализировать «Соглашения шаблона» по факту; проверка: упомянутые файлы существуют
|
||||
|
||||
### Task 4: [2.1] `api/pyproject.toml` (ruff, mypy), `requirements-dev.txt` = `-r requirements.txt
|
||||
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
`SCAN_POLL_SECONDS=5`, `SCAN_TIMEOUT_SECONDS=300`, `SCAN_MAX_INFLIGHT=4`; `requirements.txt` без `asyncpg`,
|
||||
`SQLAlchemy`, `alembic`, `argon2-cffi`, `raisa_fastapi_protected_api`; проверка: чистый venv (python3.11),
|
||||
`python run_server.py` с `.env.example`, `/healthcheck` 200
|
||||
- [ ] 1.3 `README.md` — описание сервиса (назначение, протокол горячих папок, env, запуск против compose и эмулятора),
|
||||
- [x] 1.3 `README.md` — описание сервиса (назначение, протокол горячих папок, env, запуск против compose и эмулятора),
|
||||
`CLAUDE.md` — актуализировать «Соглашения шаблона» по факту; проверка: упомянутые файлы существуют
|
||||
|
||||
## 2. Инструменты
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user