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

73 lines
6.4 KiB
Markdown
Raw Permalink 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.

# 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):
```bash
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/`):
```bash
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.