# Архитектура Streamlit-приложение поверх async SQLAlchemy. Слои: UI/джоба → сервисы → репозитории → БД; сервисы ходят во внешние API через интеграции и в разбор файлов через imports. Потоки данных по шагам — `README.md`, раздел «Потоки»; решения D… и вопросы Q… — `docs/project.md`. ## Модули | Модуль | Ответственность | Зависит от | |---|---|---| | `main.py` | `load_dotenv` → `set_page_config` → `AuthService().run()` → `st.navigation` по страницам | `auth`, `core`, `ui` | | `run_local.py` | локальный запуск: `LOCATION=local`, `alembic upgrade head`, `streamlit run` | — | | `app/core` | `Settings` (pydantic-settings, префикс `OPENBAO__SETTINGS__` приоритетнее `.env`), логирование | — | | `app/auth` | Keycloak через `raisa-streamlit-oauth`, mock-форма при `LOCATION=local` | — | | `app/db` | async engine (`NullPool`), `SessionLocal`, `Base`, `run_db()`; типы колонок (BigInteger PK, JSON/JSONB, Numeric, аудит); ORM-модели | `core` | | `app/integrations` | `gateway.py` — requests-клиент к Gravitee (ключ, таймауты, повторы, классификация ошибок, `RequestRecord`); `moex.py`, `cbr.py` — клиенты и разбор ответов в dataclass'ы | `core` | | `app/imports` | `quik.py` — разбор выгрузки сделок QUIK (.xlsx), ошибки «строка N, колонка» | — | | `app/repositories` | async-доступ к таблицам, upsert `ON CONFLICT` (`_upsert.py`) | `db`, `integrations` (только dataclass'ы результатов) | | `app/services` | сценарии: `securities`, `market_data`, `fx_rates`, `quik_import`, `manual_operations`, `request_log`, `dates` | `db`, `repositories`, `integrations`, `imports` | | `app/ui` | страницы «Бумаги», «Рыночные данные», «Загрузка сделок», «Ручные операции»; `common.py` | `services`, `imports`, `db` (`run_db`, перечисления моделей) | | `app/jobs` | `update_market` — котировки + курс CNY за дату от пользователя `system`; коды выхода 0/1/2 | `core`, `db`, `services` | | `alembic/` | async `env.py` со схемой `DATABASE_SCHEMA`, ревизии, сиды справочников | `db` | Таблицы: `currency`, `portfolio`, `operation_type` (справочники с сидом), `security`, `security_nominal_history`, `market_data`, `market_data_manual`, `exchange_rate`, `operation`, `import_batch`, `external_request_log`. Уникальные ключи и смысл колонок — `README.md`, «БД и event loop». ## Границы - `core`, `auth`, `imports` не импортируют другие пакеты `app`. - `integrations` и `imports` не знают о БД; `integrations` — единственное место, где есть HTTP. - `repositories` не вызывают внешние API: из `integrations` берут только dataclass'ы результатов разбора. - `ui` не импортирует `repositories` и `integrations` напрямую — только через `services`; в БД ходит только через `run_db()`. - Внешние API вызываются только из сервисов по действию пользователя или из джобы, не при рендере страницы. - Каждый запрос к шлюзу пишется в `external_request_log` без тела ответа и заголовков (кроме `X-Gravitee-Transaction-Id`). - Загрузка сделок QUIK пишет `import_batch` и операции одной транзакцией; план перед записью пересобирается по БД. ## Открытые вопросы - Бизнес-вопросы Q2–Q28 — в `docs/project.md`; ключевые для следующей итерации — партии внутри выпуска (Q24), фондирование (Q8, Q19), хранение результатов расчёта (Q20), роль NOM и ADJ в результате (Q6, Q28). - Где будут жить расчёты финрезультата и фондирования (новый пакет `app/calc/` с чистой логикой или сервисы) — решить на brainstorming первой расчётной итерации. - Как запускать джобу по расписанию в Раисе — не решено.