# 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 ` (или `/opsx:propose `), а содержимое proposal.md, specs/ и design.md написать по итогам brainstorming. tasks.md из OpenSpec не заполнять — задачи живут в плане superpowers. - `/opsx:apply` не использовать: исполнение идёт через writing-plans и SDD/executing-plans. - `openspec archive ` — после merge PR, как часть чеклиста закрытия. - Хук require-plan защищает каталоги из `.claude/code-paths` (`app/`, `alembic/`, `tests/`). Файлы в корне (`main.py`, `run_local.py`) хук не защищает — правки в них всё равно только по плану или с `.bounded`.