DRRK/CLAUDE.md
Andrey Epifancev b4d49bdab8 docs: описать проект в CLAUDE.md и ARCHITECTURE.md
- CLAUDE.md: что это, стек и ограничения, архитектура, команды, тесты
- ARCHITECTURE.md: карта модулей, границы импортов, открытые вопросы
- .claude/code-paths: app/, alembic/, tests/ для хука require-plan
- .gitignore: .claude/worktrees/

Refs #1

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 13:51:22 +04:00

54 lines
7.8 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.

# DRRK
## Что это
Streamlit-сервис «Портфели ДРРК» (BIQ-38115) для ДРРК банка: заменяет два Excel-файла с VBA-макросами, в которых ведутся портфели облигаций **UW** (андеррайтинг) и **SALES** (продажи), плюс сводный отчёт по обоим. Главный сценарий: пользователь ведёт справочник бумаг, подтягивает котировки MOEX и курс CNY ЦБ, загружает выгрузку сделок QUIK (.xlsx) в портфель и вводит вручную то, чего нет в QUIK (купоны, амортизация, комиссия UW, корректировки). Расчёт финрезультата, фондирование и отчёты — следующие итерации (`TODO(BIQ-38115)` в коде).
Подробности: `README.md` (потоки, таблицы, переменные окружения), `docs/project.md` (решения D…, открытые вопросы Q…), `docs/legacy_excel.md` и `docs/vba/` (как устроены текущие Excel-файлы).
## Стек и ограничения
- Python 3 (CI-образ `streamlit-app-base:2.1.0`), Streamlit 1.41, async SQLAlchemy 2.0 + asyncpg (прод, PostgreSQL) / aiosqlite (локально и в тестах), Alembic, pydantic-settings, requests, openpyxl. Тесты — pytest + pytest-asyncio.
- Платформа — Раиса (k8s), авторизация Keycloak через `raisa-streamlit-oauth`. Этот пакет есть только во внутреннем зеркале: вне контура ставить `requirements-dev.txt` (без него), при `LOCATION=local` он не нужен.
- Внешние данные (MOEX ISS, ЦБ) — только через шлюз Gravitee, у MOEX и ЦБ **разные** ключи. Внешние API вызываются только по кнопке или джобой; экраны читают БД.
- Streamlit синхронный: любой доступ к БД из UI и джобы — только через `run_db(coro)` (`app/db/base.py`), движок с `NullPool`. Свой event loop/пул не заводить.
- Деньги, цены, курсы — `Decimal` / `Numeric`, никогда `float`.
- Ключи шлюза и заголовки запросов не логируются никогда (кроме `X-Gravitee-Transaction-Id`, D18).
- Файлы заказчика (оригинал выгрузки QUIK, `docs/excel/`) — банковская тайна и ПДн: только локально, в git не добавлять. Тесты работают на обезличенной копии `tests/fixtures/quik_sample_300326_anon.xlsx`.
- Где бизнес-решения нет — `TODO(BIQ-38115)` с номером вопроса Q… из `docs/project.md`; решения из спеки/обсуждения записывать туда как D….
- Соглашения (конфиг, БД, Alembic, auth, линтеры, стиль тестов) повторяют проект `SFM-parser` (`../sfm`).
## Архитектура
- `app/integrations/` — HTTP-клиенты к шлюзу (`gateway.py`: ключ, таймауты, повторы, журнал запросов) и разбор ответов MOEX/ЦБ в dataclass'ы. Без БД.
- `app/imports/quik.py` — разбор выгрузки сделок QUIK (.xlsx). Чистая логика, без БД и сети.
- `app/db/` — движок, `run_db()`, типы колонок, ORM-модели (`models/`, реэкспорт в `__init__.py`).
- `app/repositories/` — async-доступ к таблицам, upsert через `ON CONFLICT`.
- `app/services/` — сценарии: бумаги, рыночные данные, курсы, загрузка QUIK, ручные операции, даты.
- `app/ui/pages/` — страницы Streamlit; `main.py` — auth + `st.navigation`.
- `app/jobs/update_market.py` — джоба котировок и курса для расписания.
- `app/auth/`, `app/core/` — Keycloak/mock-авторизация, настройки, логирование.
- Карта модулей и границы импортов — `ARCHITECTURE.md`.
## Команды
- `pytest` — весь набор (unit + smoke UI через `AppTest`), без сети и Postgres. Зелёным считается только весь набор.
- `ruff check . && black --check . && isort --check-only --profile black .` — линтеры (они же в pre-commit).
- `python run_local.py` — локальный запуск: `LOCATION=local`, миграции SQLite, `streamlit run main.py`.
- `alembic upgrade head` / `alembic revision -m "..."` — миграции (стиль — `alembic/versions/`).
- `python -m app.jobs.update_market [--date YYYY-MM-DD]` — джоба котировок и курса CNY.
- `DRRK_SMOKE_GATEWAY=1 pytest tests/smoke/test_gateway_smoke.py -v` — smoke против реального шлюза (нужны ключи и доступ к тестовому контуру).
- Окружение: `python -m venv .venv && .venv/bin/pip install -r requirements-dev.txt`. На macOS arm64 SQLAlchemy не тянет `greenlet` — доставить `pip install greenlet`, иначе тесты с БД падают с «the greenlet library is required».
## Тесты
- `tests/unit/` — без сети и без Postgres; БД — временный SQLite со схемой из миграций (фикстура `session_local` в `tests/conftest.py`), внешние клиенты — фейки из `tests/unit/fakes.py`.
- `tests/smoke/` — UI через `streamlit.testing.v1.AppTest`; реальный шлюз — только по флагу `DRRK_SMOKE_GATEWAY=1`.
- `tests/fixtures/` — реальные ответы MOEX ISS / ЦБ байт в байт (см. `.gitattributes`) и обезличенная выгрузка QUIK. Новые ответы внешних API сохранять туда же, а не собирать руками.
- `test_anonymized_fixture_matches_original` пропускается без локального оригинала выгрузки — это нормально.
## Дальше
Следующие итерации — расчёт финрезультата, фондирование, сводный отчёт (см. `TODO(BIQ-38115)` и открытые вопросы Q… в `docs/project.md`). Многие из них упираются в неотвеченные Q — перед brainstorming сверяться со списком.
## OpenSpec и superpowers
- Вход в любую задачу — superpowers:brainstorming. Skills openspec-propose и openspec-explore сами по себе не запускать.
- Когда brainstorming классифицировал задачу как architectural, структуру change создать через `openspec new change <id>` (или `/opsx:propose <id>`), а содержимое proposal.md, specs/ и design.md написать по итогам brainstorming. tasks.md из OpenSpec не заполнять — задачи живут в плане superpowers.
- `/opsx:apply` не использовать: исполнение идёт через writing-plans и SDD/executing-plans.
- `openspec archive <id>` — после merge PR, как часть чеклиста закрытия.
- Хук require-plan защищает каталоги из `.claude/code-paths` (`app/`, `alembic/`, `tests/`). Файлы в корне (`main.py`, `run_local.py`) хук не защищает — правки в них всё равно только по плану или с `.bounded`.