DKB_ARMKA/CLAUDE.md
Andrey Epifancev 64c516395b docs: add CLAUDE.md with commands and architecture overview
Refs #4

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 18:19:54 +04:00

6.4 KiB
Raw Blame History

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Overview

dkb_armka — internal RSHB (aurora) app built on the "FastAPI template": a FastAPI backend (api/) plus a React SPA (web/) that the backend serves as static files. Domain: company groups, legal entities, regional offices (RF offices), uploaded financial documents (ОСВ / trial balances from XLS(X), бухотчётность / financial statements from PDF/images) and their recognition results. UI text, log messages and comments are in Russian.

Commands

Backend (run from api/, the working directory matters — .env, web/ static dir and pytest.ini are resolved relative to it):

python -m venv .venv && cp ../setup/pip.conf .venv/pip.conf && source .venv/bin/activate
pip install -r requirements.txt            # prod deps (pinned); requirements-dev.txt holds pytest-asyncio / pytest-env
python run_server.py                       # uvicorn on SERVER__HOST/SERVER__PORT (default 0.0.0.0:8000)
alembic upgrade head                       # migrations; alembic revision --autogenerate -m "..."
pytest                                     # all tests (asyncio_mode=auto, APP_ENV=test, ENV_FILE=.env.test)
pytest tests/unit/test_schemas_unit.py::test_name

setup/setup.sh does the venv + npm i bootstrap for both halves (uses internal pip/npm mirrors from setup/pip.conf, setup/.npmrc).

Frontend (run from web/):

npm install
npm run start         # rsbuild dev server
npm run build         # rsbuild build -> dist/
npx biome check src   # lint/format (biome 1.9, lineWidth 140)

Known state of tests

The existing tests in api/tests/ are left over from another project: they import src.main, src.domain.schemas, src.services.membership_service, src.services.org_type_service — none of which exist here. Expect them to fail on import; they do not describe this codebase.

Configuration

api/src/core/config.py — pydantic-settings, .env file, nested delimiter __. All infra secrets live under OPENBAO (populated from OpenBao in deployment), e.g. OPENBAO__DATABASE__DATABASE_URL, OPENBAO__DATABASE__DATABASE_SCHEMA, OPENBAO__MINIO__*, OPENBAO__NATS__* (URL, NKEY seed, JWT), OPENBAO__SMB__*. Other keys: DEBUG, ROOT_PATH, SERVER__HOST, SERVER__PORT.

  • settings is instantiated at import time (src/core/config.py, src/db/db.py, src/app.py), so importing almost any module requires the full env to be present.
  • DEBUG=true skips NATS connection/subscriptions and the raisa_fastapi_protected_api auth middleware import.
  • The DB engine sets Postgres search_path to DATABASE_SCHEMA; Alembic (api/alembic/env.py) takes the URL from settings (not alembic.ini), creates the schema if missing, filters autogenerate to that schema, and prompts for confirmation when -x env=prod / APP_ENV=prod.

Backend architecture (api/src)

Layered: api/endpoints/* (routers, exported via api/router.py and included in app.py) → services/* → repositories/* (subclasses of BaseRepository with model = ..., taking an AsyncSession) → models/* (SQLAlchemy 2.0 declarative, all re-exported from models/__init__.py, which Alembic imports). Wiring is manual FastAPI Depends factories in api/dependencies/services.py; FileService additionally pulls storage and nats from request.app.state. schemas/ holds Pydantic DTOs and enums (RecognitionStatus, FileStatus, DocumentType). Domain errors from common/exceptions.py are mapped to HTTP in core/exception_handlers.py (IntegrityError→409, SQLAlchemyError→500).

Document recognition pipeline (spans several files)

  1. Upload (endpoints/files.py → FileService) stores the file in MinIO (core/clients.py:S3Storage) and creates StoredFile + RecognitionJob (status QUEUED).
  2. PollWorker (core/clients.py, started in the app.py lifespan, every ~7s) claims queued jobs with SELECT … FOR UPDATE SKIP LOCKED and marks them PROCESSING. For image/PDF jobs it copies the file from S3 to the SMB share {SMB_PATH}/Input/ and registers the job in AsyncInMemoryCache (in-process, 10-min expiry). On failure the job is re-queued. NATS publishing (publish_to_nats) is currently commented out.
  3. PDFPollWorker (core/PDFWorker/PDFTempPoller.py, every 10s) scans {SMB_PATH}/Output/<file_id>/ for JSON results of the external recognizer, saves the combined JSON to S3, converts it (PDFWorker/utils.py:json_to_db_rows) into FinancialStatementMeta/Line/Value rows, and sets job DONE / file CHECK + DocumentType.BUH_OT. Expired cache entries are marked timeout, failures marked error.
  4. core/nats_callback.py:build_file_handler is the older NATS path (subjects files.tabular_parsed / files.img_parsed): loads result JSON from S3 and, for XLS(X), inserts OsvEntry rows mapped to ChartOfAccount by normalized account code. Its subscriptions in app.py are currently commented out.

Because the job cache is in-memory, the pipeline assumes a single app instance.

App wiring quirks (app.py)

The lifespan registers the SMB session, checks the MinIO bucket (returns early — without yielding — if S3 is unreachable), connects NATS with NKEY/JWT callbacks, and starts both pollers. The SPA is served from api/web/ via SPAStaticFiles (falls back to index.html); routers must be included before the / mount.

Frontend architecture (web/src)

React 18 + TypeScript, rsbuild, Ant Design (primary UI kit; tailwind/daisyUI/raisa-ui also installed), Redux Toolkit (store/slices), react-router with routes defined in App.tsx (layouts/* are pages, widgets/* and components/* are building blocks; widgets/PDFResultViewer renders pdfjs + recognized data).

  • Env vars must be prefixed REACT_APP_ (loaded in rsbuild.config.ts, exposed via process.env) and read through conf/config.ts.
  • Always call the backend as `${Config.API_URL}/...` — in the built bundle API_URL resolves relative to the app's root path, not http://localhost:8000.
  • REACT_APP_ROOT_PATH drives rsbuild base/assetPrefix and the router basename (default /aurora/apps/fastapi-dkb-armka); it must match the backend ROOT_PATH.

Deployment

raisa_config.yml sets the platform resource profile (CPU/RAM). Dockerfile / docker-compose are gitignored (local only). The platform expects run_server.py to start the server on the host/port passed via env.