2026-10-03 23:01:23 +04:00

arm-core — ядро АРМ КА

Ядро автоматизированного рабочего места кредитного аналитика Департамента крупного бизнеса (АРМ КА, АО «Россельхозбанк»): справочники, файлы и документы, приём результатов распознавания, вычисления консолидации (КФО ГСК), маршрут согласования, Единое хранилище (ЕХ), аудит и права. Единственный писатель в PostgreSQL: сервисы распознавания (tabular-parser, scan-gateway) видят только NATS и S3.

Монорепозиторий по шаблону команды «Шаблон FastAPI»: api/ — бэкенд (FastAPI, SQLAlchemy 2 async, Alembic, PostgreSQL 16, core NATS без JetStream), web/ — фронтенд (React + TypeScript, rsbuild).

Этот репозиторий — часть workspace АРМ КА. Общий контекст, модель данных, правила предметной области и контракты — в соседних репозиториях:

  • ../CLAUDE.md — инварианты и процесс работы всего workspace;
  • ../docs/ — контекст (context/), модель данных (model/), контракты (contracts/), окружение (dev/);
  • CLAUDE.md — соглашения этого репозитория.

Срез 0 («скелет ядра»): схема БД по DBML v5.13, справочники (seed), аутентификация и права, GET /api/v1/me, /healthcheck. Спецификация — openspec/changes/slice-0-skeleton/.

Предварительные требования

  • Python 3.11 (версия платформы) и uv — make venv создаёт окружение и ставит зависимости через uv; без него цель не работает. Системный python3 другой версии не подойдёт: uv сам подберёт интерпретатор 3.11.
  • Docker и compose v2 — для PostgreSQL, NATS и MinIO (compose лежит в ../docs/dev/).
  • Node.js и npm — только для web/. Для запуска бэкенда не нужны.
  • make, curl.

Репозитории workspace должны лежать рядом: arm-core/ и docs/ — соседние каталоги (так же устроено зеркало .wt/<срез>/). Пути ../docs/... ниже и в Makefile рассчитаны на это.

Быстрый старт

Путь «с нуля до зелёного /api/v1/me». Команды make выполняются из корня репозитория.

1. Виртуальное окружение

make venv

Создаёт api/.venv (Python 3.11) и ставит api/requirements.txt и api/requirements-dev.txt. Пакеты raisa_* из Nexus банка вне банка недоступны, поэтому цель пропускает строки raisa_* (подробнее — «Работа без Nexus»). Повторный запуск безопасен.

2. Инфраструктура (PostgreSQL, NATS, MinIO)

Файл .env compose — отдельный от api/.env; он создаётся один раз и в git не попадает:

cp ../docs/dev/.env.example ../docs/dev/.env
make up          # docker compose -f ../docs/dev/docker-compose.yml up -d

make up поднимает postgres (5432), nats (4222, мониторинг 8222), minio (9000, консоль 9001) и одноразовый minio-init, который создаёт бакет arm-ka и завершается с кодом 0. Команда возвращается сразу, а PostgreSQL становится готовым примерно через 6 секунд: дождитесь healthy у трёх долгоживущих сервисов, прежде чем выполнять make migrate:

docker compose -f ../docs/dev/docker-compose.yml up -d --wait postgres nats minio   # возвращается, когда все healthy
docker compose -f ../docs/dev/docker-compose.yml ps                                 # проверка статуса

(голый --wait без списка сервисов возвращает код 1 из-за завершающегося minio-init; поэтому make up для бакета и --wait со списком — вместе). Подробности, смена портов, сброс — ../docs/dev/README.md.

3. Настройки приложения

cp api/.env.example api/.env

Значения из api/.env.example уже совпадают с ../docs/dev/.env.example (стандартные порты и учётные данные compose), менять ничего не нужно. Менять api/.env придётся, только если вы изменили порты или пароли в ../docs/dev/.env (правьте обе части). api/.env в git не попадает.

4. Миграции и справочники

make migrate     # alembic upgrade head: схема dkb, 27 enum-типов, 43 таблицы
make seed-dev    # справочники продукта + dev-пользователи (по одному на каждую роль)

make seed загружает только справочники (роли, права, филиалы, план счетов, правила элиминации, чек-лист), без dev-пользователей. Seed идемпотентен: повторный запуск обновляет данные и ничего не дублирует.

5. Запуск

cd api && .venv/bin/python run_server.py

Сервер слушает 0.0.0.0:8000 (SERVER__HOST, SERVER__PORT). Дальнейшие команды — из другого терминала.

6. Проверка

curl -s localhost:8000/healthcheck
{"uptime":"2026-10-03T18:37:15.121394+00:00","version":"1.0.0","nats":"ok"}

nats: ok — подключён, down — включён (NATS_ENABLED=true), но связи нет, off — выключен.

Dev-токен по email любого dev-пользователя и профиль текущего пользователя:

curl -s -X POST localhost:8000/api/v1/dev/token \
  -H 'Content-Type: application/json' \
  -d '{"email":"ka_rf@armka.local"}'
# {"access_token":"<JWT>","token_type":"bearer"}

curl -s localhost:8000/api/v1/me -H "Authorization: Bearer <JWT>"
{
  "id": "b8d7595a-787f-4bcb-8e93-7612cc8afba3",
  "email": "ka_rf@armka.local",
  "full_name": "Dev: Аналитик РФ",
  "regional_office": {
    "id": "3b38e823-e09a-47ce-a296-061948a200b0",
    "name": "Ростовский региональный филиал",
    "is_head_office": false
  },
  "business_segment": "DKB",
  "roles": ["KA_RF"],
  "permissions": [
    "CONSOLIDATION_EDIT", "CONSOLIDATION_VIEW", "DIRECTORY_EDIT", "DIRECTORY_VIEW", "DOCUMENT_EDIT",
    "DOCUMENT_VIEW", "EXPORT", "EX_VIEW", "KFO_EXPORT", "KFO_VIEW"
  ]
}

(идентификаторы в вашей БД будут другими). Токен действует 8 часов. Интерактивная документация API — /docs (Swagger UI), схема — /openapi.json.

Dev-пользователи (api/seed/dev/users.yaml, создаются make seed-dev):

Email Роль Филиал
ka_rf@armka.local KA_RF Ростовский региональный филиал
rka@armka.local RKA Ростовский региональный филиал
client_manager@armka.local CLIENT_MANAGER Краснодарский региональный филиал
ka_go@armka.local KA_GO Головной офис
curator_go@armka.local CURATOR_GO Головной офис
expert@armka.local EXPERT Головной офис
admin@armka.local ADMIN Головной офис

Права не зашиты в код роли: effective_permissions(user) — объединение прав ролей пользователя (таблицы permission, role_permission).

Что вы увидите в логе при запуске

Старт занимает около секунды; в логе два трейсбэка — это ожидаемо в dev, приложение при этом работает:

  • Трейсбэк SMB: Ошибка при регистрации сессии SMB ... Failed to connect to 'localhost:445': Connection refused. Настройки OPENBAO__SMB__* — заглушки, SMB в Срезе 0 не используется; игнорируйте. Пока на localhost:445 ничего не слушает, отказ приходит сразу (старт не задерживается); если порт отвечает, но молчит, ожидание ограничено тайм-аутом smbclient (60 с).
  • Трейсбэк S3: ClientConnectorSSLError ... localhost:9000. Клиент S3 из шаблона ходит по https, а MinIO из compose слушает http. S3 в Срезе 0 не используется (app.state.storage остаётся пустым). Это до исправления в задаче 22 (отдельная настройка схемы подключения к MinIO).

Недоступность SMB, S3 или NATS при старте приложение не роняет: ошибка логируется, состояние NATS видно в /healthcheck.

Предупреждение «Включён dev-эмитент токенов … только для разработки» появляется не при старте, а при первом запросе к защищённому эндпоинту (/api/v1/me): проверяющий токены создаётся лениво (штатно, см. «Безопасность dev-режима»).

Остановка и сброс

docker compose -f ../docs/dev/docker-compose.yml down        # остановить, данные (тома) сохраняются
docker compose -f ../docs/dev/docker-compose.yml down -v     # полный сброс: БД и бакет с нуля (затем снова migrate и seed-dev)

Имя проекта compose фиксировано (arm-ka), тома общие для всех worktree.

Конфигурация

Настройки — pydantic-settings; читаются из переменных окружения и файла api/.env. Вложенные настройки разделяются __. Файл окружения выбирается переменной ENV_FILE (по умолчанию .env, путь относительно текущего каталога — api/; значение читается при импорте). Тесты используют api/.env.test (pytest.ini). Полный пример — api/.env.example.

Переменная По умолчанию Назначение
ENV_FILE .env файл окружения (относительно api/)
DEBUG false режим разработки. true необходим dev-эмитенту токенов; при false пакеты РАИСА подгружаются условно
SERVER__HOST, SERVER__PORT 0.0.0.0, 8000 адрес и порт сервера (run_server.py)
ROOT_PATH / корневой путь за прокси (должен совпадать с REACT_APP_ROOT_PATH фронта)
VERSION 1.0.0 версия в /healthcheck и OpenAPI
OPENBAO__DATABASE__DATABASE_URL — (обязательно) PostgreSQL, драйвер asyncpg: postgresql+asyncpg://armka:armka@localhost:5432/armka
OPENBAO__MINIO__MINIO_URL — (обязательно) адрес MinIO без схемы (host:port, так ждёт miniopy-async)
OPENBAO__MINIO__MINIO_ADMIN_LOGIN — (обязательно) логин MinIO
OPENBAO__MINIO__MINIO_ADMIN_PASSWORD — (обязательно) пароль MinIO
OPENBAO__MINIO__MINIO_BUCKET — (обязательно) бакет MinIO (arm-ka)
OPENBAO__NATS__NATS_URL — (обязательно) адрес NATS (nats://localhost:4222)
OPENBAO__NATS__NATS_SEED пусто nkey seed; пусто — подключение без учётных данных (compose, тесты). Задаётся вместе с NATS_JWT или не задаётся вовсе
OPENBAO__NATS__NATS_JWT пусто JWT NATS; см. NATS_SEED
OPENBAO__SMB__SMB_HOST — (обязательно) заглушка SMB (в Срезе 0 не используется, блок обязателен)
OPENBAO__SMB__SMB_PATH — (обязательно) заглушка SMB
OPENBAO__SMB__SMB_LOGIN — (обязательно) заглушка SMB
OPENBAO__SMB__SMB_PASSWORD — (обязательно) заглушка SMB
NATS_ENABLED false подключение к NATS и обмен через recognition_job; без него /healthcheck показывает nats=off
OUTBOX_POLL_SECONDS 5 период опроса outbox
ACK_TIMEOUT_SECONDS 30 ожидание ack сервиса распознавания до повторной публикации
OUTBOX_MAX_REPUBLISH 3 число повторных публикаций до перевода в dlq
AUTH_DEV_ISSUER false dev-эмитент токенов: работает только при AUTH_DEV_ISSUER=true и DEBUG=true
AUTH_DEV_SECRET пусто секрет HS256 dev-эмитента (не короче 32 байт); только для разработки
AUTH_JWKS_URL или AUTH_PUBLIC_KEY пусто проверка токенов РАИСА: JWKS по URL либо публичный ключ (PEM)
AUTH_ISSUER, AUTH_AUDIENCE пусто проверка iss и aud токена РАИСА
AUTH_EMAIL_CLAIM email claim токена с email пользователя (сопоставляется с app_user.email)

Параметры JWT РАИСА (алгоритм, claim, эмитент) пока неизвестны — это открытый вопрос (../docs/context/07-open-questions.md); AUTH_JWKS_URL/AUTH_PUBLIC_KEY и связанные поля — каркас под него. Если не настроено ни dev-эмитента, ни ключей РАИСА, токены не принимаются вовсе (/me отвечает 401).

Порты по умолчанию: приложение 8000; compose — PostgreSQL 5432, NATS 4222 (мониторинг 8222), MinIO 9000 (консоль 9001). Порты compose меняются в ../docs/dev/.env, тогда же — соответствующие OPENBAO__* в api/.env.

Команды make

Все цели выполняются из корня репозитория (сами переходят в api/).

Цель Что делает
make venv api/.venv на Python 3.11 через uv, зависимости без raisa_*
make up поднимает compose из ../docs/dev (PostgreSQL, NATS, MinIO, бакет)
make migrate alembic upgrade head на БД из api/.env
make seed загрузка справочников продукта (идемпотентна)
make seed-dev то же и dev-пользователи/филиалы (--dev) — только для разработки
make lint ruff check, ruff format --check, mypy src scripts
make test юнит-тесты: pytest -m "not integration", Docker не нужен
make test-integration интеграционные тесты: pytest -m integration, нужны PostgreSQL (и NATS)
make dbml-check сверка ORM с ../docs/model/data-model-v5.13.dbml
make migration-cycle миграции на временной БД: upgrade head → downgrade base → upgrade head, пустой autogenerate
make check быстрая проверка без Docker: lint + test
make check-full check + dbml-check + test-integration; выполняется перед PR (нужна поднятая инфраструктура)

make check-full пока не включает проверки фронта, OpenAPI и откат миграций отдельным шагом (задача 25 среза).

Тесты

  • Юнит-тесты (make test) не обращаются ни к БД, ни к NATS, Docker не нужен.
  • Интеграционные (make test-integration) требуют PostgreSQL, тесты обмена через NATS — ещё и NATS. Рабочую БД armka они не трогают: на каждую сессию pytest создаётся свежая БД armcore_pytest_<hex>, мигрируется alembic и удаляется в конце. Параметры подключения берутся из api/.env.test (pytest.ini задаёт ENV_FILE=.env.test). Сейчас этот файл указывает на временную тестовую инфраструктуру (PostgreSQL 55432, NATS 54222, MinIO 59000), а не на стандартные порты compose: чтобы гонять интеграционные тесты против make up, поправьте порты в api/.env.test на 5432, 4222, 9000 (не коммитьте это изменение без согласования). То же относится к make migration-cycle и make check-full: они тоже читают .env.test через pytest.ini.
  • Реальные данные — репозиторий ../fixtures/ (путь можно задать переменной ARM_KA_FIXTURES). Тесты с маркером fixtures без каталога пропускаются с понятной причиной.

Проверка ORM и DBML

Модель данных — ../docs/model/data-model-v5.13.dbml. ORM обязана совпадать с ней один в один:

make dbml-check     # пустой вывод и код 0 — совпадение; расхождения печатаются построчно

Другой путь к DBML: make dbml-check DBML=<путь относительно api/>.

Миграции

make migrate                                  # применить: alembic upgrade head
cd api && .venv/bin/alembic downgrade base    # откатить до пустой БД
make migration-cycle                          # проверить цикл upgrade -> downgrade -> upgrade на временной БД

Ревизии лежат в api/alembic/versions/; автогенерация — только черновик, финал сверяется с DBML (make dbml-check).

Раскладка репозитория

api/
  run_server.py        точка входа (uvicorn; хост и порт — из настроек)
  alembic/             миграции (versions/0001_dbml_v5_13.py — вся модель v5.13)
  seed/core/           справочники продукта (роли, права, филиалы, план счетов, правила элиминации, чек-лист)
  seed/dev/            dev-данные: филиалы и пользователи по ролям
  scripts/             check_dbml_vs_orm.py (ORM <-> DBML), seed.py и seed_lib/ (загрузчик справочников)
  src/
    app.py             FastAPI-приложение: lifespan (S3, NATS, SMB), /healthcheck, раздача SPA
    api/endpoints/     эндпоинты (me.py, dev_auth.py); router.py собирает /api/v1
    api/dependencies/  зависимости FastAPI (current_user, проверка прав)
    core/              config, auth (JWT), clients (S3), nats_callback, logger, exception_handlers
    db/                движок и сессия (схема dkb)
    models/            ORM-модели, 1:1 с DBML
    repositories/      доступ к данным
    schemas/           pydantic-схемы запросов и ответов
    services/          бизнес-логика
    common/            исключения
    domain/            (появится) чистые функции по §§ правил предметной области, без ORM
  tests/               pytest (юнит и integration/fixtures по маркерам)
web/                   фронтенд (React 18 + TypeScript, rsbuild, antd); сборка web/dist -> api/web/
setup/                 бутстрап шаблона через Nexus банка (pip.conf, setup.sh)
openspec/              спецификации и изменения (OpenSpec)
docs/superpowers/      планы реализации
raisa_config.yml       ресурсы для платформы
Dockerfile             только для локального compose

Слои: src/api/endpoints → services → repositories → models.

Работа без Nexus

Пакеты raisa_* (Python) и raisa-* (npm) лежат в Nexus банка и вне банка недоступны. Что с этим сделано:

  • make venv ставит requirements.txt без строк raisa_* (фильтр grep -v '^raisa_') и поэтому требует uv, а не setup/setup.sh (тот рассчитан на pip.conf с Nexus).
  • Код импортирует raisa_* условно: пакет middleware РАИСА импортируется только при DEBUG=false, и ImportError допустим — приложение работает без него. Аутентификация Среза 0 — собственная проверка JWT (api/src/core/auth.py).
  • В web/package.json пакеты raisa-* вынесены в optionalDependencies: npm install не падает, если Nexus недоступен. Файл .npmrc с адресом реестра (см. setup/setup.sh) вне банка не нужен.

Безопасность dev-режима

  • POST /api/v1/dev/token выдаёт токен любому, кто знает email существующего активного пользователя: пароля нет. Только для локальной разработки и тестовых стендов.
  • Маршрут существует лишь при AUTH_DEV_ISSUER=true и DEBUG=true; при любом другом сочетании его нет вовсе (404), а dev-проверяющий токенов не создаётся даже при заданном AUTH_DEV_SECRET. В проде DEBUG=false, поэтому dev-эмитент там невозможен.
  • AUTH_DEV_SECRET из api/.env.example — публичный пример, в проде не использовать. make seed-dev в прод не запускать.
  • Порты compose по умолчанию публикуются только на 127.0.0.1 (BIND_ADDR); пароли armka/minioadmin — только dev.

Соседние документы

Description
No description provided
Readme 14 MiB
Languages
Python 83.9%
TypeScript 15.7%
CSS 0.2%