diff --git a/apps/docs/docs/adr/ADR-001-backend-single-point-of-access.md b/apps/docs/docs/adr/ADR-001-backend-single-point-of-access.md
index 6170454..0321030 100644
--- a/apps/docs/docs/adr/ADR-001-backend-single-point-of-access.md
+++ b/apps/docs/docs/adr/ADR-001-backend-single-point-of-access.md
@@ -1,13 +1,13 @@
-# ADR-001: Backend — Single Point of Access to MOEX
+# ADR-001: Backend — единственная точка доступа к MOEX
-**Status:** Accepted
-**Date:** 2026-06-13
-**Deciders:** Architect, Tech Lead
+**Статус:** Accepted
+**Дата:** 2026-06-13
+**Участники решения:** Architect, Tech Lead
-## Context
+## Контекст
Frontend должен отображать данные Московской биржи. MOEX ISS API отдаёт сырые данные со сложной структурой (вложенные таблицы, различные форматы). Прямые запросы с фронта приведут к дублированию логики нормализации, усложнят обработку ошибок и сделают систему зависимой от внешнего API.
-## Decision
+## Решение
Backend (NestJS) является единственной точкой доступа к MOEX. Frontend никогда не обращается к MOEX напрямую.
Backend:
@@ -17,7 +17,7 @@ Backend:
- Обрабатывает ошибки MOEX (пустые данные, rate limit, таймауты)
- Предоставляет собственный OpenAPI-контракт для фронта
-## Consequences
+## Последствия
- Единый источник правды для трансформации данных
- Изоляция изменений MOEX API — меняется только MoexClient
- Централизованное кеширование сокращает количество запросов к MOEX
diff --git a/apps/docs/docs/adr/ADR-002-in-memory-cache.md b/apps/docs/docs/adr/ADR-002-in-memory-cache.md
index 62175ff..216f1fe 100644
--- a/apps/docs/docs/adr/ADR-002-in-memory-cache.md
+++ b/apps/docs/docs/adr/ADR-002-in-memory-cache.md
@@ -1,13 +1,13 @@
-# ADR-002: In-Memory Cache with Migration Path to Redis
+# ADR-002: In-memory cache с путём миграции на Redis
-**Status:** Accepted
-**Date:** 2026-06-13
-**Deciders:** Architect, Tech Lead
+**Статус:** Accepted
+**Дата:** 2026-06-13
+**Участники решения:** Architect, Tech Lead
-## Context
+## Контекст
Для MVP требуется кеширование MOEX-данных, чтобы снизить нагрузку на внешнее API и обеспечить приемлемое время ответа. На начальном этапе нет требований к горизонтальному масштабированию, и хочется избежать внешних зависимостей.
-## Decision
+## Решение
Использовать `@nestjs/cache-manager` с MemoryStore. TTL настраивается per-endpoint через конфигурацию.
Архитектура позволяет переключиться на Redis заменой импорта провайдера:
@@ -26,7 +26,7 @@ CacheModule.registerAsync({
})
```
-## Consequences
+## Последствия
- Нет внешних зависимостей для MVP
- Кеш сбрасывается при рестарте сервера (приемлемо для read-only приложения)
- Чистый путь миграции на Redis
diff --git a/apps/docs/docs/adr/ADR-003-rate-limiting-strategy.md b/apps/docs/docs/adr/ADR-003-rate-limiting-strategy.md
index 483a88f..f007be9 100644
--- a/apps/docs/docs/adr/ADR-003-rate-limiting-strategy.md
+++ b/apps/docs/docs/adr/ADR-003-rate-limiting-strategy.md
@@ -1,20 +1,20 @@
-# ADR-003: Rate Limiting Strategy for MOEX Client
+# ADR-003: Стратегия rate limiting для MOEX client
-**Status:** Accepted
-**Date:** 2026-06-13
-**Deciders:** Architect, Tech Lead
+**Статус:** Accepted
+**Дата:** 2026-06-13
+**Участники решения:** Architect, Tech Lead
-## Context
+## Контекст
MOEX ISS не документирует жёсткие лимиты на количество запросов, но массовые запросы могут привести к блокировке или ухудшению качества обслуживания. Backend является единственным клиентом MOEX и должен контролировать исходящий трафик.
-## Decision
+## Решение
Внедрить два механизма в MoexClient:
-1. **Request Queue (p-queue)**: конфигурируемый лимит запросов в секунду (default: 10 req/s). Запросы сверх лимита ставятся в очередь и выполняются по расписанию.
+1. **Request queue (p-queue)**: конфигурируемый лимит запросов в секунду (по умолчанию: 10 req/s). Запросы сверх лимита ставятся в очередь и выполняются по расписанию.
2. **Circuit Breaker (`@nestjs/axios` + interceptor)**: при 5+ последовательных ошибках (5xx, timeout, network error) клиент перестаёт отправлять запросы к MOEX на 30 секунд. После таймаута — пробный запрос для восстановления.
-## Consequences
+## Последствия
- Плавная нагрузка на MOEX, без пиков
- Автоматическое восстановление после сбоев MOEX
- Graceful degradation: при отключённом circuit breaker возвращаются кешированные данные
diff --git a/apps/docs/docs/adr/ADR-004-feature-modules.md b/apps/docs/docs/adr/ADR-004-feature-modules.md
index ab31eec..804d65a 100644
--- a/apps/docs/docs/adr/ADR-004-feature-modules.md
+++ b/apps/docs/docs/adr/ADR-004-feature-modules.md
@@ -1,16 +1,16 @@
-# ADR-004: Feature Modules by Domain
+# ADR-004: Feature-модули по доменам
-**Status:** Accepted
-**Date:** 2026-06-13
-**Deciders:** Architect, Tech Lead
+**Статус:** Accepted
+**Дата:** 2026-06-13
+**Участники решения:** Architect, Tech Lead
-## Context
+## Контекст
NestJS рекомендует модульную архитектуру. Требования указывают на архитектуру по feature modules. Модули должны иметь чёткие границы и быть тестируемыми изолированно.
-## Decision
+## Решение
Каждый бизнес-домен — отдельный NestJS feature module:
-| Module | Responsibility |
+| Модуль | Ответственность |
|--------|---------------|
| `MoexClientModule` | HTTP-клиент к MOEX ISS, rate limiting, circuit breaker |
| `CacheModule` | Абстракция кеширования |
@@ -22,7 +22,7 @@ NestJS рекомендует модульную архитектуру. Тре
Каждый module exports свой сервис, control imports через `@Module({ imports: [...] })`.
-## Consequences
+## Последствия
- Чёткие границы, изолированное тестирование
- Возможность вынести модуль в отдельный микросервис
- Понятная навигация по коду
diff --git a/apps/docs/docs/adr/ADR-005-openapi-codegen-frontend.md b/apps/docs/docs/adr/ADR-005-openapi-codegen-frontend.md
index 244b0ad..7175666 100644
--- a/apps/docs/docs/adr/ADR-005-openapi-codegen-frontend.md
+++ b/apps/docs/docs/adr/ADR-005-openapi-codegen-frontend.md
@@ -1,13 +1,13 @@
-# ADR-005: OpenAPI Codegen with openapi-typescript
+# ADR-005: OpenAPI codegen через openapi-typescript
-**Status:** Accepted
-**Date:** 2026-06-13
-**Deciders:** Architect, Tech Lead
+**Статус:** Accepted
+**Дата:** 2026-06-13
+**Участники решения:** Architect, Tech Lead
-## Context
+## Контекст
Frontend должен потреблять API бэкенда. Ручное написание клиентов и DTO приводит к рассинхронизации с бэкендом и ошибкам типизации.
-## Decision
+## Решение
Использовать `openapi-typescript` + `openapi-fetch` для генерации:
- TypeScript типов (DTO, request/response schemas)
@@ -33,7 +33,7 @@ export function useStock(secid: string) {
}
```
-## Consequences
+## Последствия
- Полная типобезопасность на стыке frontend/backend
- Автоматическая синхронизация с API-контрактом
- TanStack Query hooks пишутся вручную — полный контроль staleTime/caching
diff --git a/apps/docs/docs/adr/ADR-006-no-cci.md b/apps/docs/docs/adr/ADR-006-no-cci.md
index ab2f9f5..56a7b62 100644
--- a/apps/docs/docs/adr/ADR-006-no-cci.md
+++ b/apps/docs/docs/adr/ADR-006-no-cci.md
@@ -1,20 +1,20 @@
-# ADR-006: CCI (Financial Reporting) Moved Out of MVP
+# ADR-006: CCI (финансовая отчётность) вынесен за пределы MVP
-**Status:** Accepted
-**Date:** 2026-06-13
-**Deciders:** Architect, Product
+**Статус:** Accepted
+**Дата:** 2026-06-13
+**Участники решения:** Architect, Product
-## Context
+## Контекст
MOEX предоставляет корпоративную информацию (CCI) — финансовую отчётность по МСФО/РСБУ. Данные включают отчёты о прибылях/убытках, балансовые отчёты, мультипликаторы. Однако:
- CCI API имеет собственную сложную структуру (виды отчётности, периоды, индикаторы)
- Данные требуют дополнительной нормализации и расчёта метрик
- Для MVP пользователи хотят базовую информацию (цена, купон, график)
-## Decision
+## Решение
Не включать CCI в MVP. Roadmap на post-MVP.
-## Consequences
+## Последствия
- Меньший объём работы в MVP
- API не привязывается к CCI-схемам (будет отдельный модуль)
- Пользователи не увидят мультипликаторы (P/E, EV/EBITDA) в первой версии
diff --git a/apps/docs/docs/adr/ADR-007-two-level-caching.md b/apps/docs/docs/adr/ADR-007-two-level-caching.md
index 6826570..50ad4c0 100644
--- a/apps/docs/docs/adr/ADR-007-two-level-caching.md
+++ b/apps/docs/docs/adr/ADR-007-two-level-caching.md
@@ -1,15 +1,15 @@
-# ADR-007: Two-Level Caching (Backend + Frontend)
+# ADR-007: Двухуровневое кеширование (backend + frontend)
-**Status:** Accepted
-**Date:** 2026-06-13
-**Deciders:** Architect
+**Статус:** Accepted
+**Дата:** 2026-06-13
+**Участники решения:** Architect
-## Context
+## Контекст
Данные MOEX имеют задержку 15 минут. Кеширование на одном уровне (только бэкенд или только фронтенд) неоптимально:
- Только бэкенд: каждый пользователь создаёт запрос к серверу
- Только фронтенд: нет централизованного кеша, не защищает MOEX от повторных запросов
-## Decision
+## Решение
Внедрить два уровня кеширования:
1. **Backend (in-memory cache-manager)**: централизованное кеширование ответов от MOEX. Предотвращает повторные запросы к MOEX от разных пользователей.
@@ -20,7 +20,7 @@ TTL согласованы (см. Caching Strategy).
Cache-Control заголовки в HTTP-ответах для промежуточных proxy/CDN (опционально).
-## Consequences
+## Последствия
- Избыточность intentional: resilience при отказе одного уровня
- TanStack Query staleTime = backend TTL (нет лишних запросов)
- При рестарте бэкенда фронт всё ещё имеет данные в memory cache
diff --git a/apps/docs/docs/adr/ADR-008-auth-system.md b/apps/docs/docs/adr/ADR-008-auth-system.md
index d94e80b..f6e45ab 100644
--- a/apps/docs/docs/adr/ADR-008-auth-system.md
+++ b/apps/docs/docs/adr/ADR-008-auth-system.md
@@ -3,13 +3,13 @@ sidebar_position: 14
title: ADR-008
---
-# ADR-008: Authentication & Authorization System
+# ADR-008: Система Authentication и Authorization
-## Status
+## Статус
Accepted
-## Context
+## Контекст
Приложение MoexVibe публичное, но для персонализации и будущих функций (избранное, уведомления, подписки) требуется система аутентификации. Необходимо решение, которое:
@@ -18,26 +18,26 @@ Accepted
- Позволяет расширять ролевую модель
- Интегрируется в существующий стек (NestJS + React)
-## Decision
+## Решение
### Backend
-- **Database:** SQLite через Prisma ORM (без необходимости внешнего сервиса)
-- **Auth strategy:** JWT access tokens (15m) + refresh tokens (7d) in httpOnly cookies
-- **Password storage:** bcrypt with 12 salt rounds
-- **Guard model:** Global `JwtAuthGuard` (все маршруты защищены по умолчанию, `@Public()` для открытых)
+- **База данных:** SQLite через Prisma ORM без внешнего сервиса
+- **Auth strategy:** JWT access tokens (15m) + refresh tokens (7d) в httpOnly cookies
+- **Хранение паролей:** bcrypt with 12 salt rounds
+- **Guard model:** глобальный `JwtAuthGuard` (все маршруты защищены по умолчанию, `@Public()` для открытых)
- **RBAC:** Роли `user` и `admin`, проверка через `RolesGuard`
### Frontend
-- **State management:** React Context (`AuthContext`) для хранения пользователя и access token
-- **Token storage:** Access token только в памяти (не localStorage), refresh token в httpOnly cookie
+- **Управление состоянием:** React Context (`AuthContext`) для хранения пользователя и access token
+- **Token storage:** access token только в памяти (не localStorage), refresh token в httpOnly cookie
- **Auto-refresh:** При 401 → автоматический вызов `/auth/refresh` → повтор оригинального запроса
-- **Route protection:** `ProtectedRoute` компонент, редирект на `/login` с return URL
+- **Route protection:** компонент `ProtectedRoute`, редирект на `/login` с return URL
-## Consequences
+## Последствия
-### Positive
+### Плюсы
- httpOnly cookie защищает refresh token от XSS
- Access token в памяти защищён от кражи через localStorage
@@ -45,17 +45,17 @@ Accepted
- Refresh token rotation повышает безопасность
- Глобальный guard — безопасность по умолчанию
-### Negative
+### Минусы
- Access token теряется при полной перезагрузке страницы (восстанавливается через refresh cookie)
- Для production требуется генерация надёжных JWT_SECRET
-- Нет rate limiting на login endpoint (TODO)
+- Rate limiting на login endpoint не входит в текущее решение
- Нет верификации email (принятое упрощение)
-## Migration Path
+## Путь миграции
Для перехода на PostgreSQL потребуется:
1. Изменить `provider` в schema.prisma на `postgresql`
2. Обновить `DATABASE_URL`
3. Выполнить `prisma migrate dev`
-4. Никаких изменений кода не требуется (Prisma abstracts database)
+4. Никаких изменений кода не требуется: Prisma абстрагирует database
diff --git a/apps/docs/docs/adr/ADR-009-portfolio-domain.md b/apps/docs/docs/adr/ADR-009-portfolio-domain.md
index 010a8bc..78058ef 100644
--- a/apps/docs/docs/adr/ADR-009-portfolio-domain.md
+++ b/apps/docs/docs/adr/ADR-009-portfolio-domain.md
@@ -1,27 +1,29 @@
-# ADR-009: Portfolio Domain Model
+# ADR-009: Доменная модель портфеля
-**Status:** Accepted
+**Статус:** Accepted
-**Date:** 2026-06-14
+**Дата:** 2026-06-14
-## Context
+## Контекст
-Adding portfolio tracking feature to MoexVibe. Need to decide on data model — relational tables vs document-oriented storage for portfolio and position data.
+В MoexVibe добавляется отслеживание портфелей. Нужно выбрать модель данных: реляционные таблицы
+или document-oriented storage для портфелей и позиций.
-## Decision
+## Решение
-Use SQL via Prisma (existing database) with `Portfolio` and `Position` as separate tables. JSON fields for flexible data (`targets`, `tags`).
+Использовать SQL через Prisma в существующей базе: `Portfolio` и `Position` как отдельные таблицы,
+JSON-поля для гибких данных (`targets`, `tags`).
-## Rationale
+## Обоснование
-- SQLite already in use for User model
-- Portfolio and Position have clear relational structure (1:N)
-- JSON fields cover semi-structured requirements (targets, tags) without adding another database
-- No need for complex queries across tags at current scale (50 positions max per portfolio)
-- Straightforward migration to PostgreSQL if needed
+- SQLite уже используется для модели User
+- Portfolio и Position имеют понятную реляционную структуру 1:N
+- JSON-поля закрывают полуструктурированные требования (`targets`, `tags`) без новой базы
+- На текущем масштабе не нужны сложные запросы по tags (до 50 позиций на портфель)
+- При необходимости есть прямой путь миграции на PostgreSQL
-## Consequences
+## Последствия
-- JSON fields not individually queryable in SQLite (acceptable for Phase 1)
-- Targets edited as full JSON replacement (not atomic per-item update)
-- Cascade delete handles portfolio → position cleanup
+- JSON-поля не индексируются и не запрашиваются по отдельности в SQLite, что приемлемо для Phase 1
+- Targets редактируются полной заменой JSON, без атомарного обновления отдельных элементов
+- Cascade delete очищает позиции при удалении портфеля
diff --git a/apps/docs/docs/adr/ADR-010-backend-price-computation.md b/apps/docs/docs/adr/ADR-010-backend-price-computation.md
index 6898ee2..90efa6e 100644
--- a/apps/docs/docs/adr/ADR-010-backend-price-computation.md
+++ b/apps/docs/docs/adr/ADR-010-backend-price-computation.md
@@ -1,26 +1,29 @@
-# ADR-010: Backend Price Computation
+# ADR-010: Расчёт цен на backend
-**Status:** Accepted
+**Статус:** Accepted
-**Date:** 2026-06-14
+**Дата:** 2026-06-14
-## Context
+## Контекст
-Portfolio positions need current market prices for value calculation. Where should price computation happen — on backend or frontend?
+Позициям портфеля нужны текущие рыночные цены для расчёта стоимости. Нужно решить, где выполнять
+расчёт: на backend или frontend.
-## Decision
+## Решение
-Compute prices on backend. `GET /api/v1/portfolios/:id` returns fully computed `PortfolioDetailResponseDto` with `currentPrice`, `currentValue`, `weightPercent`, and `deviation` for each position.
+Рассчитывать цены на backend. `GET /api/v1/portfolios/:id` возвращает полностью рассчитанный
+`PortfolioDetailResponseDto` с `currentPrice`, `currentValue`, `weightPercent` и `deviation` для
+каждой позиции.
-## Rationale
+## Обоснование
-- Single source of truth for financial calculations
-- Frontend receives ready-to-display data
-- Backend caching reduces MOEX API calls
-- Avoids N individual price requests from frontend
+- Единый источник правды для финансовых расчётов
+- Frontend получает готовые к отображению данные
+- Backend cache сокращает количество запросов к MOEX API
+- Frontend не делает N отдельных запросов цен
-## Consequences
+## Последствия
-- Backend makes N MOEX requests per portfolio read (cached by TTL 900s)
-- Portfolio endpoint cannot use naive response caching (prices per-user)
-- Extra load on backend when many users view portfolios simultaneously
+- Backend делает N запросов к MOEX при чтении портфеля; они кешируются с TTL 900s
+- Portfolio endpoint не может использовать наивное response caching, потому что цены считаются для пользователя
+- При одновременном просмотре портфелей многими пользователями растёт нагрузка на backend
diff --git a/apps/docs/docs/adr/index.md b/apps/docs/docs/adr/index.md
index 1e994b0..7fea488 100644
--- a/apps/docs/docs/adr/index.md
+++ b/apps/docs/docs/adr/index.md
@@ -1,16 +1,16 @@
-# Architecture Decision Records
+# Архитектурные решения (ADR)
-| ADR | Status | Description |
+| ADR | Статус | Описание |
|---|---|---|
-| [ADR-001](ADR-001-backend-single-point-of-access) | Accepted | Backend — Single Point of Access to MOEX |
-| [ADR-002](ADR-002-in-memory-cache) | Accepted | In-Memory Cache Strategy |
-| [ADR-003](ADR-003-rate-limiting-strategy) | Accepted | Rate Limiting Strategy |
-| [ADR-004](ADR-004-feature-modules) | Accepted | Feature Modules Architecture |
-| [ADR-005](ADR-005-openapi-codegen-frontend) | Accepted | OpenAPI Codegen for Frontend |
-| [ADR-006](ADR-006-no-cci) | Deprecated | No Custom Components Infrastructure |
-| [ADR-007](ADR-007-two-level-caching) | Draft | Two-Level Caching (In-Memory + Redis) |
-| [ADR-008](ADR-008-auth-system) | Accepted | Authentication & Authorization |
-| [ADR-009](ADR-009-portfolio-domain) | Accepted | Portfolio Domain Model |
-| [ADR-010](ADR-010-backend-price-computation) | Accepted | Backend Price Computation |
+| [ADR-001](ADR-001-backend-single-point-of-access) | Accepted | Backend — единственная точка доступа к MOEX |
+| [ADR-002](ADR-002-in-memory-cache) | Accepted | Стратегия in-memory cache |
+| [ADR-003](ADR-003-rate-limiting-strategy) | Accepted | Стратегия rate limiting |
+| [ADR-004](ADR-004-feature-modules) | Accepted | Архитектура feature-модулей |
+| [ADR-005](ADR-005-openapi-codegen-frontend) | Accepted | OpenAPI codegen для frontend |
+| [ADR-006](ADR-006-no-cci) | Deprecated | CCI вынесен за пределы MVP |
+| [ADR-007](ADR-007-two-level-caching) | Draft | Двухуровневый cache: backend + frontend |
+| [ADR-008](ADR-008-auth-system) | Accepted | Authentication и Authorization |
+| [ADR-009](ADR-009-portfolio-domain) | Accepted | Доменная модель портфеля |
+| [ADR-010](ADR-010-backend-price-computation) | Accepted | Расчёт цен на backend |
Все опубликованные ADR находятся в `apps/docs/docs/adr/` и отображаются в этом Docusaurus-разделе.
diff --git a/apps/docs/docs/architecture.md b/apps/docs/docs/architecture.md
index 6e9fc06..66359e7 100644
--- a/apps/docs/docs/architecture.md
+++ b/apps/docs/docs/architecture.md
@@ -1,94 +1,101 @@
-# Architecture
+# Архитектура
-## System Architecture
+## Архитектура системы
```mermaid
-graph TD
+flowchart LR
Browser["Browser
(React SPA)"]
- Backend["NestJS API
:3000"]
- Cache["In-Memory Cache
(cache-manager)"]
+ API["NestJS API
:3000"]
+ Cache["In-memory cache
(cache-manager)"]
MOEX["MOEX ISS API
iss.moex.com"]
- DB[("SQLite Database
(Prisma)")]
+ DB[("SQLite
(Prisma)")]
- Browser -->|"/api/v1/*"| Backend
- Browser -->|"Cookie: refreshToken"| Backend
- Browser -->|"Authorization: Bearer"| Backend
- Backend -->|"getOrFetch()"| Cache
- Backend -->|"GET /iss/*.json"| MOEX
- Backend -->|"Prisma ORM"| DB
- Cache -->|"data"| Backend
- DB -->|"users"| Backend
- MOEX -->|"raw data"| Backend
-
- subgraph Backend_Internal["Backend (NestJS)"]
- MoexClient["MoexClientModule
p-queue + circuit breaker"]
- Auth["AuthModule
JWT + bcrypt + Guards"]
- Shares["SharesModule"]
- Bonds["BondsModule"]
- Candles["CandlesModule"]
- Securities["SecuritiesModule"]
- Health["HealthModule"]
- CacheService["CacheService
(global)"]
- Prisma["PrismaService
(global)"]
-
- Auth -->|"user CRUD"| Prisma
- MoexClient -->|"fetches"| Shares
- MoexClient -->|"fetches"| Bonds
- MoexClient -->|"fetches"| Candles
- MoexClient -->|"fetches"| Securities
- Shares -->|"uses"| CacheService
- Bonds -->|"uses"| CacheService
- Candles -->|"uses"| CacheService
- Securities -->|"uses"| CacheService
- end
+ Browser -->|"/api/v1/*"| API
+ Browser -->|"Cookie: refreshToken"| API
+ Browser -->|"Authorization: Bearer"| API
+ API -->|"getOrFetch()"| Cache
+ API -->|"GET /iss/*.json"| MOEX
+ API -->|"Prisma ORM"| DB
+ Cache -->|"данные"| API
+ DB -->|"пользователи и портфели"| API
+ MOEX -->|"сырые данные"| API
```
-## Request Flow (Authenticated)
+## Внутренние модули backend
+
+```mermaid
+flowchart LR
+ Auth["AuthModule
JWT + bcrypt + guards"]
+ Shares["SharesModule"]
+ Bonds["BondsModule"]
+ Candles["CandlesModule"]
+ Securities["SecuritiesModule"]
+ Portfolio["PortfolioModule"]
+ Health["HealthModule"]
+ MoexClient["MoexClientModule
p-queue + circuit breaker"]
+ CacheService["CacheService
(global)"]
+ Prisma["PrismaService
(global)"]
+
+ Auth -->|"CRUD пользователей"| Prisma
+ Portfolio -->|"портфели и позиции"| Prisma
+ MoexClient -->|"запрашивает"| Shares
+ MoexClient -->|"запрашивает"| Bonds
+ MoexClient -->|"запрашивает"| Candles
+ MoexClient -->|"запрашивает"| Securities
+ MoexClient -->|"обогащает"| Portfolio
+ Shares -->|"использует"| CacheService
+ Bonds -->|"использует"| CacheService
+ Candles -->|"использует"| CacheService
+ Securities -->|"использует"| CacheService
+ Portfolio -->|"использует"| CacheService
+```
+
+## Поток авторизованного запроса
```mermaid
sequenceDiagram
- participant User
+ participant User as Пользователь
participant Frontend as React SPA
participant Backend as NestJS API
- participant Cache as In-Memory Cache
+ participant Cache as In-memory cache
participant MOEX as MOEX ISS
participant AuthDB as SQLite
- User->>Frontend: View stock SBER
- Note over Frontend: Access token in memory
+ User->>Frontend: Открывает акцию SBER
+ Note over Frontend: access token хранится в памяти
Frontend->>Backend: GET /securities/shares/SBER (Authorization: Bearer)
- Backend->>Backend: JwtAuthGuard validates token
+ Backend->>Backend: JwtAuthGuard проверяет token
Backend->>Cache: getOrFetch('share:SBER')
- alt Cache miss
+ alt cache miss
Cache->>Backend: null
Backend->>MOEX: GET /iss/.../SBER.json
- MOEX-->>Backend: raw data
+ MOEX-->>Backend: сырые данные
Backend->>Cache: set('share:SBER', ..., TTL=900)
- else Cache hit
+ else cache hit
Cache-->>Backend: cached data
end
Backend-->>Frontend: { data, meta }
- Frontend-->>User: rendered UI
+ Frontend-->>User: отрисованный UI
```
-## Architecture Decisions
+## Архитектурные решения
-All architectural decisions are documented as ADR pages in this documentation app:
+Все архитектурные решения описаны в ADR-страницах этой документации:
-| ADR | Summary |
+| ADR | Кратко |
|---|---|
-| [ADR-001](adr/ADR-001-backend-single-point-of-access) | Backend — single point of access to MOEX |
-| [ADR-002](adr/ADR-002-in-memory-cache) | In-memory cache (cache-manager) |
-| [ADR-003](adr/ADR-003-rate-limiting-strategy) | Rate limiting with p-queue |
-| [ADR-004](adr/ADR-004-feature-modules) | Feature modules architecture |
-| [ADR-005](adr/ADR-005-openapi-codegen-frontend) | OpenAPI codegen for frontend |
-| [ADR-006](adr/ADR-006-no-cci) | No CCI (Custom Components Infrastructure) |
-| [ADR-007](adr/ADR-007-two-level-caching) | Two-level caching strategy |
-| [ADR-008](adr/ADR-008-auth-system) | Authentication & Authorization |
-| [ADR-009](adr/ADR-009-portfolio-domain) | Portfolio domain model |
-| [ADR-010](adr/ADR-010-backend-price-computation) | Backend price computation |
+| [ADR-001](adr/ADR-001-backend-single-point-of-access) | Backend — единственная точка доступа к MOEX |
+| [ADR-002](adr/ADR-002-in-memory-cache) | In-memory cache через cache-manager |
+| [ADR-003](adr/ADR-003-rate-limiting-strategy) | Rate limiting через p-queue |
+| [ADR-004](adr/ADR-004-feature-modules) | Архитектура feature-модулей |
+| [ADR-005](adr/ADR-005-openapi-codegen-frontend) | OpenAPI codegen для frontend |
+| [ADR-006](adr/ADR-006-no-cci) | Отказ от CCI (Custom Components Infrastructure) |
+| [ADR-007](adr/ADR-007-two-level-caching) | Двухуровневая стратегия cache |
+| [ADR-008](adr/ADR-008-auth-system) | Authentication и Authorization |
+| [ADR-009](adr/ADR-009-portfolio-domain) | Доменная модель портфеля |
+| [ADR-010](adr/ADR-010-backend-price-computation) | Расчёт цен на backend |
-## Response Format
+## Формат ответа
Все ответы API обёрнуты в единый формат:
@@ -114,9 +121,9 @@ All architectural decisions are documented as ADR pages in this documentation ap
}
```
-## Global Configuration
+## Глобальная конфигурация
-- Global prefix: `/api/v1`
+- Глобальный префикс: `/api/v1`
- ValidationPipe: `transform: true, whitelist: true`
- HttpExceptionFilter (catch-all)
- TransformInterceptor (авто-обёртка в `ApiResponse`)
diff --git a/apps/docs/docs/backend/api.md b/apps/docs/docs/backend/api.md
index 11af40d..6945f10 100644
--- a/apps/docs/docs/backend/api.md
+++ b/apps/docs/docs/backend/api.md
@@ -1,4 +1,4 @@
-# API Reference
+# API reference
Все эндпоинты находятся под префиксом `/api/v1`. Swagger UI: `/api/docs`.
@@ -104,13 +104,13 @@
Поиск по инструментам.
-**Parameters:**
+**Параметры:**
-| Param | Type | Required | Default | Description |
+| Параметр | Тип | Обязателен | По умолчанию | Описание |
|---|---|---|---|---|
-| `q` | string | yes | — | Поисковый запрос (1-100 символов) |
-| `type` | enum | no | `all` | Фильтр: `all`, `share`, `bond` |
-| `limit` | integer | no | `20` | Лимит результатов |
+| `q` | string | да | — | Поисковый запрос (1-100 символов) |
+| `type` | enum | нет | `all` | Фильтр: `all`, `share`, `bond` |
+| `limit` | integer | нет | `20` | Лимит результатов |
**Response:**
```json
@@ -134,18 +134,18 @@
Скринер ценных бумаг по параметрам цены, объёма, доходности, дюрации, купона и срока погашения.
-**Parameters:**
+**Параметры:**
-| Param | Type | Required | Description |
+| Параметр | Тип | Обязателен | Описание |
|---|---|---|---|
-| `type` | enum | yes | `share` или `bond` |
-| `priceMin`, `priceMax` | number | no | Диапазон цены |
-| `volumeMin` | number | no | Минимальный объём |
-| `yieldMin`, `yieldMax` | number | no | Диапазон доходности облигаций |
-| `durationMin`, `durationMax` | number | no | Диапазон дюрации |
-| `sortBy` | string | no | Поле сортировки |
-| `sortOrder` | enum | no | `asc` или `desc` |
-| `page`, `pageSize` | integer | no | Пагинация |
+| `type` | enum | да | `share` или `bond` |
+| `priceMin`, `priceMax` | number | нет | Диапазон цены |
+| `volumeMin` | number | нет | Минимальный объём |
+| `yieldMin`, `yieldMax` | number | нет | Диапазон доходности облигаций |
+| `durationMin`, `durationMax` | number | нет | Диапазон дюрации |
+| `sortBy` | string | нет | Поле сортировки |
+| `sortOrder` | enum | нет | `asc` или `desc` |
+| `page`, `pageSize` | integer | нет | Пагинация |
**Response:** `{ data: { items, total, page, pageSize, totalPages }, meta }`.
@@ -155,7 +155,7 @@
Спецификация акции.
-**Parameters:** `secid` — тикер (например, `SBER`)
+**Параметры:** `secid` — тикер (например, `SBER`)
**Response:** `ShareResponse` — спецификация + текущие рыночные данные.
@@ -216,7 +216,7 @@
Дивиденды акции.
-**Parameters:** `secid` — тикер
+**Параметры:** `secid` — тикер
**Response:**
```json
@@ -236,12 +236,12 @@
Дневная история торгов акции.
-**Parameters:**
+**Параметры:**
-| Param | Type | Required | Description |
+| Параметр | Тип | Обязателен | Описание |
|---|---|---|---|
-| `from` | string (date) | yes | Начальная дата (`YYYY-MM-DD`) |
-| `till` | string (date) | yes | Конечная дата (`YYYY-MM-DD`) |
+| `from` | string (date) | да | Начальная дата (`YYYY-MM-DD`) |
+| `till` | string (date) | да | Конечная дата (`YYYY-MM-DD`) |
**Response:**
```json
@@ -267,7 +267,7 @@
Спецификация облигации.
-**Parameters:** `secid` — тикер
+**Параметры:** `secid` — тикер
**Response:** `BondResponse` — спецификация + рыночные данные.
@@ -320,7 +320,7 @@
Дневная история торгов облигации.
-**Parameters:** `from`, `till` (date)
+**Параметры:** `from`, `till` (date)
**Response:**
```json
@@ -347,13 +347,13 @@
Свечи облигации.
-**Parameters:**
+**Параметры:**
-| Param | Type | Required | Description |
+| Параметр | Тип | Обязателен | Описание |
|---|---|---|---|
-| `interval` | enum | yes | `1h` или `24h` |
-| `from` | string (date) | yes | Начальная дата |
-| `till` | string (date) | yes | Конечная дата |
+| `interval` | enum | да | `1h` или `24h` |
+| `from` | string (date) | да | Начальная дата |
+| `till` | string (date) | да | Конечная дата |
**Response:**
```json
@@ -374,11 +374,11 @@
}
```
-## Portfolios
+## Портфели
Все portfolio endpoints защищены JWT и возвращают envelope `{ data, meta }`.
-| Endpoint | Method | Description |
+| Endpoint | Method | Описание |
|---|---|---|
| `/api/v1/portfolios` | GET | Список портфелей пользователя |
| `/api/v1/portfolios` | POST | Создать портфель |
diff --git a/apps/docs/docs/backend/auth.md b/apps/docs/docs/backend/auth.md
index 22f653e..7c86c14 100644
--- a/apps/docs/docs/backend/auth.md
+++ b/apps/docs/docs/backend/auth.md
@@ -1,71 +1,71 @@
-# Authentication & Authorization
+# Authentication и Authorization
-## Architecture
+## Архитектура
-### Token-Based Authentication
+### Token-based authentication
Система использует два типа JWT-токенов:
-| Token | Format | TTL | Storage | Purpose |
+| Token | Формат | TTL | Хранение | Назначение |
|-------|--------|-----|---------|---------|
-| Access Token | JWT `{ sub, email, role }` | 15 min | Memory (React) + `Authorization: Bearer` | Authenticate API requests |
-| Refresh Token | JWT `{ sub, jti }` | 7 days | httpOnly cookie + SHA-256 hash in DB | Issue new access tokens |
+| Access token | JWT `{ sub, email, role }` | 15 мин | Память React + `Authorization: Bearer` | Аутентифицирует API requests |
+| Refresh token | JWT `{ sub, jti }` | 7 дней | httpOnly cookie + SHA-256 hash в БД | Выпускает новые access tokens |
-### Security
+### Безопасность
-- Passwords hashed with **bcrypt** (12 salt rounds)
-- Refresh tokens stored as **bcrypt hash** in database
-- httpOnly, SameSite=Lax, Secure (production) cookies
-- Access token never persisted to localStorage (XSS protection)
-- Auto-refresh on 401 with automatic retry of failed request
-- Refresh token rotation: each refresh invalidates the old token
+- Пароли хешируются через **bcrypt** (12 salt rounds)
+- Refresh tokens хранятся в базе как **bcrypt hash**
+- Cookie: httpOnly, SameSite=Lax, Secure в production
+- Access token не сохраняется в localStorage, чтобы снизить XSS-риск
+- При 401 frontend автоматически обновляет token и повторяет исходный request
+- Refresh token rotation: каждый refresh инвалидирует старый token
-### Flow
+### Поток
```mermaid
sequenceDiagram
- participant User
+ participant User as Пользователь
participant Frontend as React SPA
participant Backend as NestJS API
participant DB as SQLite (Prisma)
- User->>Frontend: Enter email & password
+ User->>Frontend: Вводит email и password
Frontend->>Backend: POST /auth/login { email, password }
- Backend->>DB: Find user by email
+ Backend->>DB: Ищет пользователя по email
Backend->>Backend: bcrypt.compare(password, hash)
- Backend->>DB: Save refresh token hash
+ Backend->>DB: Сохраняет refresh token hash
Backend-->>Frontend: { user, accessToken } + Set-Cookie (refreshToken)
- Frontend->>Frontend: Store accessToken in memory
- Frontend-->>User: Redirect to app
+ Frontend->>Frontend: Хранит accessToken в памяти
+ Frontend-->>User: Перенаправляет в приложение
- Note over Frontend,Backend: Later API request
+ Note over Frontend,Backend: Следующий API request
Frontend->>Backend: GET /auth/me (Authorization: Bearer )
- Backend->>Backend: Verify JWT signature & expiry
+ Backend->>Backend: Проверяет JWT signature и expiry
Backend-->>Frontend: { user }
- Note over Frontend,Backend: Token refresh (auto on 401)
+ Note over Frontend,Backend: Token refresh автоматически при 401
Frontend->>Backend: POST /auth/refresh (Cookie: refreshToken)
- Backend->>Backend: Verify refresh JWT
- Backend->>DB: Compare refresh token hash
- Backend->>DB: Rotate: save new refresh token hash
+ Backend->>Backend: Проверяет refresh JWT
+ Backend->>DB: Сравнивает refresh token hash
+ Backend->>DB: Ротация: сохраняет новый refresh token hash
Backend-->>Frontend: { user, newAccessToken } + Set-Cookie (newRefreshToken)
- Frontend->>Frontend: Update accessToken in memory
- Frontend->>Backend: Retry original request
+ Frontend->>Frontend: Обновляет accessToken в памяти
+ Frontend->>Backend: Повторяет исходный request
Note over Frontend,Backend: Logout
Frontend->>Backend: POST /auth/logout
- Backend->>DB: Clear refresh token hash
- Backend-->>Frontend: Clear cookie
- Frontend->>Frontend: Clear accessToken
+ Backend->>DB: Очищает refresh token hash
+ Backend-->>Frontend: Очищает cookie
+ Frontend->>Frontend: Очищает accessToken
```
-## API Endpoints
+## API endpoints
-All endpoints are under `/api/v1/auth`.
+Все endpoints находятся под `/api/v1/auth`.
### `POST /auth/register`
-Register a new user.
+Регистрирует нового пользователя.
**Request:**
```json
@@ -76,11 +76,11 @@ Register a new user.
}
```
-**Response:** `{ data: { user, accessToken }, meta }` + `Set-Cookie` with refresh token.
+**Response:** `{ data: { user, accessToken }, meta }` + `Set-Cookie` с refresh token.
### `POST /auth/login`
-Authenticate existing user.
+Аутентифицирует существующего пользователя.
**Request:**
```json
@@ -94,19 +94,19 @@ Authenticate existing user.
### `POST /auth/refresh`
-Refresh access token. Reads refresh token from cookie.
+Обновляет access token. Refresh token читается из cookie.
**Response:** `{ data: { user, accessToken }, meta }` + new `Set-Cookie`.
### `POST /auth/logout`
-Invalidates refresh token. Requires `Authorization: Bearer`.
+Инвалидирует refresh token. Требует `Authorization: Bearer`.
-**Response:** `{ data: { message }, meta }` + cookie cleared.
+**Response:** `{ data: { message }, meta }` + очищенная cookie.
### `GET /auth/me`
-Returns current user profile. Requires `Authorization: Bearer`.
+Возвращает профиль текущего пользователя. Требует `Authorization: Bearer`.
**Response:**
```json
@@ -123,7 +123,7 @@ Returns current user profile. Requires `Authorization: Bearer`.
### `PATCH /auth/me`
-Update current user profile. Requires `Authorization: Bearer`.
+Обновляет профиль текущего пользователя. Требует `Authorization: Bearer`.
**Request:**
```json
@@ -134,19 +134,20 @@ Update current user profile. Requires `Authorization: Bearer`.
## Authorization (RBAC)
-| Role | Permissions |
+| Роль | Права |
|------|-------------|
-| `user` | View/edit own profile |
-| `admin` | All user permissions |
+| `user` | Просмотр и редактирование своего профиля |
+| `admin` | Все права пользователя |
-**Application level:** Global `JwtAuthGuard` protects all routes by default. Use `@Public()` decorator to bypass. `RolesGuard` checks required roles from `@Roles()` decorator.
+**Уровень приложения:** глобальный `JwtAuthGuard` по умолчанию защищает все routes.
+Декоратор `@Public()` открывает публичные endpoints. `RolesGuard` проверяет роли из `@Roles()`.
-## Environment Variables
+## Переменные окружения
-| Variable | Default | Description |
+| Переменная | По умолчанию | Описание |
|----------|---------|-------------|
-| `JWT_SECRET` | `dev-jwt-secret-...` | Secret for access token signing |
-| `JWT_REFRESH_SECRET` | `dev-refresh-secret-...` | Secret for refresh token signing |
+| `JWT_SECRET` | `dev-jwt-secret-...` | Secret для подписи access token |
+| `JWT_REFRESH_SECRET` | `dev-refresh-secret-...` | Secret для подписи refresh token |
| `JWT_ACCESS_EXPIRES` | `15m` | Access token TTL |
| `JWT_REFRESH_EXPIRES` | `7d` | Refresh token TTL |
-| `DATABASE_URL` | `file:./dev.db` | SQLite database URL (Prisma format) |
+| `DATABASE_URL` | `file:./dev.db` | URL SQLite в формате Prisma |
diff --git a/apps/docs/docs/backend/caching.md b/apps/docs/docs/backend/caching.md
index c5dd918..db9337b 100644
--- a/apps/docs/docs/backend/caching.md
+++ b/apps/docs/docs/backend/caching.md
@@ -1,22 +1,22 @@
-# Caching
+# Кеширование
-## Cache Strategy
+## Стратегия cache
In-memory кеш через `@nestjs/cache-manager` (cache-manager v5). Поддерживается миграция на Redis (см. ADR-002).
-## Cache Flow
+## Поток cache
```mermaid
flowchart LR
- Request["Request Data"]
+ Request["Запрос данных"]
CacheService["CacheService.getOrFetch()"]
BuildKey["buildKey(prefix, keyParts)"]
CacheGet["cacheManager.get(key)"]
- Hit["Cache HIT → return data"]
+ Hit["Cache HIT → вернуть данные"]
Miss["Cache MISS"]
- FetchFn["FetchFn() → get from MOEX"]
+ FetchFn["fetchFn() → получить из MOEX"]
CacheSet["cacheManager.set(key, data, ttl)"]
- Return["Return { data, fromCache, cachedAt }"]
+ Return["Вернуть { data, fromCache, cachedAt }"]
Request --> CacheService
CacheService --> BuildKey
@@ -48,7 +48,7 @@ getOrFetch(
- При cache hit возвращает `{ data, fromCache: true, cachedAt: null }`
- При cache miss вызывает `fetchFn`, сохраняет результат с TTL и возвращает `{ data, fromCache: false, cachedAt: '...' }`
-## Cache Module Configuration
+## Конфигурация CacheModule
`apps/backend/src/modules/cache/cache.module.ts`:
@@ -68,13 +68,13 @@ getOrFetch(
export class CacheModule {}
```
-## Per-Data TTL
+## TTL по типам данных
-| Data Type | Config Key | Default TTL | Config Variable |
+| Тип данных | Config key | TTL по умолчанию | Переменная |
|---|---|---|---|
-| Market Data | `marketDataTtl` | 900s (15 min) | `CACHE_MARKET_DATA_TTL` |
-| History | `historyTtl` | 3600s (1h) | `CACHE_HISTORY_TTL` |
-| Candles | `candlesTtl` | 3600s (1h) | `CACHE_CANDLES_TTL` |
-| Security Spec | `securityTtl` | 86400s (24h) | `CACHE_SECURITY_TTL` |
-| Search | `searchTtl` | 3600s (1h) | `CACHE_SEARCH_TTL` |
-| Dividends | `dividendsTtl` | 86400s (24h) | `CACHE_DIVIDENDS_TTL` |
+| Рыночные данные | `marketDataTtl` | 900s (15 мин) | `CACHE_MARKET_DATA_TTL` |
+| История | `historyTtl` | 3600s (1 ч) | `CACHE_HISTORY_TTL` |
+| Свечи | `candlesTtl` | 3600s (1 ч) | `CACHE_CANDLES_TTL` |
+| Спецификация инструмента | `securityTtl` | 86400s (24 ч) | `CACHE_SECURITY_TTL` |
+| Поиск | `searchTtl` | 3600s (1 ч) | `CACHE_SEARCH_TTL` |
+| Дивиденды | `dividendsTtl` | 86400s (24 ч) | `CACHE_DIVIDENDS_TTL` |
diff --git a/apps/docs/docs/backend/configuration.md b/apps/docs/docs/backend/configuration.md
index a90c3bf..d5a517c 100644
--- a/apps/docs/docs/backend/configuration.md
+++ b/apps/docs/docs/backend/configuration.md
@@ -1,10 +1,10 @@
-# Configuration
+# Конфигурация
Конфигурация загружается через `@nestjs/config` из `config/configuration.ts` и env-переменных.
-## Environment Variables
+## Переменные окружения
-| Variable | Default | Description |
+| Переменная | По умолчанию | Описание |
|---|---|---|
| `PORT` | `3000` | Порт HTTP-сервера |
| `MOEX_BASE_URL` | `https://iss.moex.com/iss` | Базовый URL MOEX ISS API |
@@ -18,7 +18,7 @@
| `CACHE_SEARCH_TTL` | `3600` | TTL результатов поиска (секунды) |
| `CACHE_DIVIDENDS_TTL` | `86400` | TTL дивидендов (секунды) |
-## Configuration File
+## Файл конфигурации
`apps/backend/src/config/configuration.ts`:
diff --git a/apps/docs/docs/backend/database.md b/apps/docs/docs/backend/database.md
index 2168651..e60b9c6 100644
--- a/apps/docs/docs/backend/database.md
+++ b/apps/docs/docs/backend/database.md
@@ -1,63 +1,128 @@
-# Database Schema
+# Схема базы данных
-## Overview
+## Обзор
Используется **SQLite** через **Prisma ORM 7**. База данных находится в `apps/backend/dev.db`.
-## Schema
+## Схема
```prisma
generator client {
- provider = "prisma-client"
- output = "../src/generated/prisma"
+ provider = "prisma-client-js"
}
datasource db {
provider = "sqlite"
}
+model Portfolio {
+ id Int @id @default(autoincrement())
+ userId Int
+ name String
+ description String?
+ currency String @default("RUB")
+ targets String?
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
+
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
+ positions Position[]
+
+ @@unique([userId, name])
+}
+
+model Position {
+ id Int @id @default(autoincrement())
+ portfolioId Int
+ secid String
+ type String @default("share")
+ quantity Int
+ buyPrice Float?
+ buyDate DateTime?
+ notes String?
+ tags String?
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
+
+ portfolio Portfolio @relation(fields: [portfolioId], references: [id], onDelete: Cascade)
+
+ @@unique([portfolioId, secid])
+}
+
model User {
- id Int @id @default(autoincrement())
- email String @unique
+ id Int @id @default(autoincrement())
+ email String @unique
password String
name String?
- role String @default("user")
+ role String @default("user")
refreshToken String?
- createdAt DateTime @default(now())
- updatedAt DateTime @updatedAt
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
+ portfolios Portfolio[]
}
```
-## Tables
+## Таблицы
-### Users
+### User
-| Column | Type | Constraints | Description |
+| Колонка | Тип | Ограничения | Описание |
|--------|------|-------------|-------------|
-| id | INTEGER | PK, AUTOINCREMENT | User ID |
-| email | TEXT | UNIQUE, NOT NULL | Email address |
-| password | TEXT | NOT NULL | bcrypt hash of password |
-| name | TEXT | NULLABLE | Display name |
-| role | TEXT | NOT NULL, DEFAULT 'user' | Role for RBAC |
-| refreshToken | TEXT | NULLABLE | bcrypt hash of current refresh token |
-| createdAt | DATETIME | NOT NULL | Timestamp |
-| updatedAt | DATETIME | NOT NULL | Auto-updated timestamp |
+| id | INTEGER | PK, AUTOINCREMENT | ID пользователя |
+| email | TEXT | UNIQUE, NOT NULL | Email |
+| password | TEXT | NOT NULL | bcrypt hash пароля |
+| name | TEXT | NULLABLE | Отображаемое имя |
+| role | TEXT | NOT NULL, DEFAULT 'user' | Роль для RBAC |
+| refreshToken | TEXT | NULLABLE | bcrypt hash текущего refresh token |
+| createdAt | DATETIME | NOT NULL | Дата создания |
+| updatedAt | DATETIME | NOT NULL | Дата последнего обновления |
+
+### Portfolio
+
+| Колонка | Тип | Ограничения | Описание |
+|--------|------|-------------|-------------|
+| id | INTEGER | PK, AUTOINCREMENT | ID портфеля |
+| userId | INTEGER | FK -> User, NOT NULL | Владелец портфеля |
+| name | TEXT | NOT NULL, UNIQUE per user | Название портфеля |
+| description | TEXT | NULLABLE | Описание |
+| currency | TEXT | DEFAULT 'RUB' | Валюта портфеля |
+| targets | TEXT | NULLABLE | Целевые доли или настройки в JSON-строке |
+| createdAt | DATETIME | NOT NULL | Дата создания |
+| updatedAt | DATETIME | NOT NULL | Дата последнего обновления |
+
+### Position
+
+| Колонка | Тип | Ограничения | Описание |
+|--------|------|-------------|-------------|
+| id | INTEGER | PK, AUTOINCREMENT | ID позиции |
+| portfolioId | INTEGER | FK -> Portfolio, NOT NULL | Портфель |
+| secid | TEXT | NOT NULL | MOEX security ID |
+| type | TEXT | DEFAULT 'share' | `share` или `bond` |
+| quantity | INTEGER | NOT NULL | Количество |
+| buyPrice | REAL | NULLABLE | Цена покупки |
+| buyDate | DATETIME | NULLABLE | Дата покупки |
+| notes | TEXT | NULLABLE | Заметки |
+| tags | TEXT | NULLABLE | Tags в JSON-строке |
+| createdAt | DATETIME | NOT NULL | Дата создания |
+| updatedAt | DATETIME | NOT NULL | Дата последнего обновления |
## Prisma Client
-The Prisma client is generated to `apps/backend/src/generated/prisma/` and imported via `@/generated/prisma/client`.
+Prisma client импортируется из `@prisma/client`.
-Key service: `PrismaService` (`src/modules/prisma/prisma.service.ts`) — extends `PrismaClient`, handles connection lifecycle (`onModuleInit`/`onModuleDestroy`). Registered as a global module.
+Ключевой сервис: `PrismaService` (`src/modules/prisma/prisma.service.ts`) расширяет
+`PrismaClient`, управляет жизненным циклом подключения (`onModuleInit`/`onModuleDestroy`) и
+регистрируется через глобальный модуль.
-## Migrations
+## Миграции
-Migrations are stored in `apps/backend/prisma/migrations/`. To create a new migration:
+Миграции лежат в `apps/backend/prisma/migrations/`. Создать новую миграцию:
```bash
npx prisma migrate dev --name
```
-To apply migrations in production:
+Применить миграции в production:
```bash
npx prisma migrate deploy
diff --git a/apps/docs/docs/backend/modules.md b/apps/docs/docs/backend/modules.md
index 0b65711..4a76b31 100644
--- a/apps/docs/docs/backend/modules.md
+++ b/apps/docs/docs/backend/modules.md
@@ -1,55 +1,58 @@
-# Backend Modules
+# Модули backend
-## Module Dependency Graph
+## Граф зависимостей модулей
+
+### Импорты `AppModule`
```mermaid
-graph TD
- AppModule --> ConfigModule
- AppModule --> CacheModule
- AppModule --> MoexClientModule
- AppModule --> PrismaModule
- AppModule --> HealthModule
- AppModule --> AuthModule
- AppModule --> SecuritiesModule
- AppModule --> SharesModule
- AppModule --> BondsModule
- AppModule --> CandlesModule
- AppModule --> PortfolioModule
+flowchart TB
+ AppModule["AppModule"]
+ GlobalModules["Глобальные модули
ConfigModule
PrismaModule
CacheModule
MoexClientModule"]
+ FeatureModules["Feature-модули
HealthModule
AuthModule
PortfolioModule
SecuritiesModule
SharesModule
BondsModule
CandlesModule"]
- subgraph Global_Modules["Global Modules"]
- PrismaModule
- CacheModule
- MoexClientModule
- end
-
- AuthModule --> PrismaService["PrismaService"]
- PortfolioModule --> PrismaService
- PortfolioModule --> MoexClientService
- SharesModule --> CacheService["CacheService"]
- BondsModule --> CacheService
- SecuritiesModule --> CacheService
- CandlesModule --> CacheService
-
- SharesModule --> MoexClientService["MoexClientService"]
- BondsModule --> MoexClientService
- SecuritiesModule --> MoexClientService
- CandlesModule --> MoexClientService
+ AppModule --> GlobalModules
+ AppModule --> FeatureModules
```
-## Module List
+### Зависимости от сервисов
-| Module | Global | Path | Description |
+```mermaid
+flowchart TB
+ AuthModule["AuthModule"]
+ PortfolioModule["PortfolioModule"]
+ MarketModules["SecuritiesModule
SharesModule
BondsModule
CandlesModule"]
+
+ PrismaService["PrismaService"]
+ CacheService["CacheService"]
+ MoexClientService["MoexClientService"]
+
+ PrismaModule --> PrismaService
+ CacheModule --> CacheService
+ MoexClientModule --> MoexClientService
+
+ AuthModule --> PrismaService
+ PortfolioModule --> PrismaService
+ PortfolioModule --> CacheService
+ PortfolioModule --> MoexClientService
+
+ MarketModules --> CacheService
+ MarketModules --> MoexClientService
+```
+
+## Список модулей
+
+| Модуль | Глобальный | Путь | Описание |
|---|---|---|---|
-| `PrismaModule` | Yes | `modules/prisma/` | Prisma client for SQLite |
-| `CacheModule` | Yes | `modules/cache/` | In-memory cache (cache-manager) |
-| `MoexClientModule` | Yes | `modules/moex-client/` | HTTP-клиент MOEX ISS |
-| `HealthModule` | No | `modules/health/` | Health check endpoint |
-| `AuthModule` | No | `modules/auth/` | JWT auth, refresh cookie, guards |
-| `SecuritiesModule` | No | `modules/securities/` | Поиск инструментов |
-| `SharesModule` | No | `modules/shares/` | Акции |
-| `BondsModule` | No | `modules/bonds/` | Облигации |
-| `CandlesModule` | No | `modules/candles/` | Свечи OHLCV |
-| `PortfolioModule` | No | `modules/portfolio/` | Пользовательские портфели и аналитика |
+| `PrismaModule` | Да | `modules/prisma/` | Prisma client для SQLite |
+| `CacheModule` | Да | `modules/cache/` | In-memory cache через cache-manager |
+| `MoexClientModule` | Да | `modules/moex-client/` | HTTP-клиент MOEX ISS |
+| `HealthModule` | Нет | `modules/health/` | Health check endpoint |
+| `AuthModule` | Нет | `modules/auth/` | JWT auth, refresh cookie, guards |
+| `SecuritiesModule` | Нет | `modules/securities/` | Поиск инструментов |
+| `SharesModule` | Нет | `modules/shares/` | Акции |
+| `BondsModule` | Нет | `modules/bonds/` | Облигации |
+| `CandlesModule` | Нет | `modules/candles/` | Свечи OHLCV |
+| `PortfolioModule` | Нет | `modules/portfolio/` | Пользовательские портфели и аналитика |
### PrismaModule
diff --git a/apps/docs/docs/backend/moex-client.md b/apps/docs/docs/backend/moex-client.md
index b2dadf6..b55e8b9 100644
--- a/apps/docs/docs/backend/moex-client.md
+++ b/apps/docs/docs/backend/moex-client.md
@@ -1,10 +1,10 @@
# MOEX Client
-## Overview
+## Обзор
`MoexClientService` (`apps/backend/src/modules/moex-client/moex-client.service.ts`) — HTTP-клиент для MOEX ISS API.
-## Rate Limiting
+## Rate limiting
Использует `p-queue`:
@@ -17,7 +17,7 @@ this.queue = new PQueue({
Все запросы к MOEX проходят через очередь — не более `MOEX_RATE_LIMIT` запросов в секунду.
-## Circuit Breaker
+## Circuit breaker
Состояние: закрыт → открыт → полуоткрыт (через таймаут).
@@ -30,7 +30,7 @@ private circuitErrorCount = 0;
- В открытом состоянии все запросы мгновенно падают с ошибкой `"Circuit breaker is open"`
- Через `MOEX_CIRCUIT_BREAKER_RESET_SECONDS` (30) автоматически сбрасывается
-## Request Method
+## Метод request
```typescript
private async request(path: string, params?: Record): Promise
@@ -40,7 +40,7 @@ private async request(path: string, params?: Record): Promise
- Устанавливает `iss.meta=off` (отключает метаданные)
- Таймаут: 10s
-## Response Parsing
+## Разбор response
MOEX возвращает данные в табличном формате:
@@ -55,9 +55,9 @@ MOEX возвращает данные в табличном формате:
Метод `extractTable` преобразует это в массив объектов по колонкам.
-## Available MOEX Methods
+## Доступные методы MOEX
-| Method | MOEX Path | Description |
+| Метод | MOEX path | Описание |
|---|---|---|
| `searchSecurities` | `/securities?q=` | Поиск инструментов |
| `getSecurityDescription` | `/securities/{secid}` | Спецификация |
@@ -69,7 +69,7 @@ MOEX возвращает данные в табличном формате:
| `getHistory` | `/engines/stock/markets/shares/securities/{secid}` | История акций |
| `getBondHistory` | `/engines/stock/markets/bonds/securities/{secid}` | История облигаций |
-## MOEX ISS Types
+## MOEX ISS types
Все MOEX-типы описаны в `apps/backend/src/modules/moex-client/moex-client.types.ts`:
diff --git a/apps/docs/docs/backend/overview.md b/apps/docs/docs/backend/overview.md
index 3b918e4..2702eec 100644
--- a/apps/docs/docs/backend/overview.md
+++ b/apps/docs/docs/backend/overview.md
@@ -1,8 +1,8 @@
-# Backend Overview
+# Обзор backend
Backend — NestJS-приложение, единственная точка доступа к MOEX ISS.
-## Entry Point
+## Точка входа
`apps/backend/src/main.ts` — bootstrap:
@@ -12,16 +12,16 @@ Backend — NestJS-приложение, единственная точка д
- JwtAuthGuard (глобально, `@Public()` для открытых эндпоинтов)
- cookie-parser (для refresh token)
- CORS включён (`credentials: true`)
-- Порт из `PORT` env (default: 3000)
+- Порт из `PORT` env (по умолчанию: 3000)
-## Root Module
+## Корневой модуль
`apps/backend/src/app.module.ts` импортирует:
- `ConfigModule.forRoot` (глобальный, из `config/configuration.ts`)
- Импортируются 8 feature-модулей (Prisma + Cache + MoexClient — глобальные)
-## Source Layout
+## Структура исходников
```
apps/backend/src/
diff --git a/apps/docs/docs/backend/portfolio.md b/apps/docs/docs/backend/portfolio.md
index 92914f4..8953965 100644
--- a/apps/docs/docs/backend/portfolio.md
+++ b/apps/docs/docs/backend/portfolio.md
@@ -1,19 +1,19 @@
-# Portfolio Module
+# PortfolioModule
-Portfolio module позволяет пользователям создавать и вести виртуальные инвестиционные портфели для
+`PortfolioModule` позволяет пользователям создавать и вести виртуальные инвестиционные портфели для
аналитики и отслеживания позиций.
-## Overview
+## Обзор
- **Backend:** `PortfolioModule` (`apps/backend/src/modules/portfolio/`)
-- **Frontend:** Protected pages at `/portfolios` and `/portfolios/:id`
-- **Database:** `Portfolio` and `Position` models (Prisma + SQLite)
+- **Frontend:** защищённые страницы `/portfolios` и `/portfolios/:id`
+- **База данных:** модели `Portfolio` и `Position` (Prisma + SQLite)
-## API Endpoints
+## API endpoints
-Все endpoints требуют JWT authentication (`JwtAuthGuard`).
+Все endpoints требуют JWT authentication через `JwtAuthGuard`.
-| Endpoint | Method | Description |
+| Endpoint | Method | Описание |
|---|---|---|
| `/api/v1/portfolios` | GET | Список портфелей пользователя |
| `/api/v1/portfolios` | POST | Создать портфель |
@@ -25,7 +25,7 @@ Portfolio module позволяет пользователям создават
| `/api/v1/portfolios/:id/positions/:positionId` | PATCH | Обновить позицию |
| `/api/v1/portfolios/:id/positions/:positionId` | DELETE | Удалить позицию |
-## Domain Model
+## Доменная модель
```
Portfolio
@@ -50,56 +50,63 @@ Position
couponPeriod, bondType, offerDate
```
-## Analytics and PnL
+## Analytics и PnL
-The backend calculates performance metrics for each position and the portfolio as a whole:
+Backend рассчитывает метрики доходности для каждой позиции и портфеля целиком:
-- **Total Cost:** `buyPrice * quantity` (adjusted for bonds: `(buyPrice / 100) * faceValue * quantity`).
+- **Total cost:** `buyPrice * quantity`; для облигаций: `(buyPrice / 100) * faceValue * quantity`.
- **Unrealized PnL:** `currentValue - totalCost`.
- **PnL %:** `(PnL / totalCost) * 100`.
-- **Weighted Yield:** Portfolio-wide average yield based on position weights.
+- **Weighted yield:** средняя доходность портфеля с учётом весов позиций.
-Analytics are available via the `/analytics` endpoint or as a `summary` object in the `/portfolios/:id` detail response.
+Analytics доступны через endpoint `/analytics` или как объект `summary` в детальном response
+`/portfolios/:id`.
-## Security Type Detection
+## Определение типа инструмента
-When adding a position, the backend fetches `getSecurityDescription(secid)` from MOEX. If `desc.group === 'stock_bonds'`, the position type is set to `bond`; otherwise `share`. This determines which enrichment path is used on read.
+При добавлении позиции backend получает из MOEX `getSecurityDescription(secid)`. Если
+`desc.group === 'stock_bonds'`, тип позиции становится `bond`; иначе используется `share`. От типа
+зависит путь обогащения при чтении портфеля.
-## Price Computation (Shares)
+## Расчёт цены акций
-For shares: `currentPrice = marketData.last`, `currentValue = price * quantity`, with `change` and `changePercent` from `lastChange` and `lastChangePrcnt`.
+Для акций: `currentPrice = marketData.last`, `currentValue = price * quantity`; `change` и
+`changePercent` берутся из `lastChange` и `lastChangePrcnt`.
-## Price Computation (Bonds)
+## Расчёт цены облигаций
-For bonds, MOEX returns prices as a percentage of face value (e.g., 98.5 = 98.5% of 1000 RUB).
+Для облигаций MOEX возвращает цены в процентах от номинала: например, 98.5 означает 98.5% от
+1000 RUB.
- `currentPrice = marketData.last` (% of face value)
- `currentValue = (price / 100) * faceValue * quantity`
-Bond enrichment also fetches:
-| Field | Source | Description |
+Обогащение облигаций также получает:
+| Поле | Источник | Описание |
|---|---|---|
| `yieldToMaturity` | `getBondMarketData().yield` | YTM (%) |
-| `duration` | `getBondMarketData().duration` | Modified duration (years) |
-| `couponValue` | `getBondData().couponValue` | Coupon amount in RUB |
-| `couponPercent` | `getBondData().couponPercent` | Coupon rate (%) |
-| `couponPeriod` | `getBondData().couponPeriod` | Days between payments |
-| `nextCouponDate` | `getBondData().nextCoupon` | Next coupon date |
-| `matDate` | `getBondData().matDate` | Maturity date |
-| `offerDate` | `getBondData().offerDate` | Early redemption date |
-| `accruedInt` | `getBondData().accruedInt` | Accrued interest per bond (RUB) |
+| `duration` | `getBondMarketData().duration` | Modified duration в годах |
+| `couponValue` | `getBondData().couponValue` | Размер купона в RUB |
+| `couponPercent` | `getBondData().couponPercent` | Ставка купона (%) |
+| `couponPeriod` | `getBondData().couponPeriod` | Дней между выплатами |
+| `nextCouponDate` | `getBondData().nextCoupon` | Дата следующего купона |
+| `matDate` | `getBondData().matDate` | Дата погашения |
+| `offerDate` | `getBondData().offerDate` | Дата досрочного погашения |
+| `accruedInt` | `getBondData().accruedInt` | НКД на облигацию (RUB) |
| `bondType` | `getBondData().bondType` | "ОФЗ", "Корпоративная", etc. |
-| `bid` / `offer` | `getBondMarketData()` | Current bid/ask (% of face) |
+| `bid` / `offer` | `getBondMarketData()` | Текущий bid/ask в % от номинала |
-## MOEX Board Selection
+## Выбор MOEX board
-The backend uses the following default MOEX boards:
-- **Shares:** `TQBR` (Т+: Акции — безадрес.)
-- **Bonds:** `TQCB` (Т+: Корпоративные облигации — безадрес.)
+Backend использует следующие MOEX boards по умолчанию:
+- **Акции:** `TQBR` (Т+: Акции — безадрес.)
+- **Облигации:** `TQCB` (Т+: Корпоративные облигации — безадрес.)
-Some bonds (primarily OFZ government bonds) trade on `TQOB` board. If the requested board has no trading data, the backend falls back to any board with a non-null `LAST` price. Data is cached with standard market data TTL (900s).
+Некоторые облигации, в первую очередь ОФЗ, торгуются на board `TQOB`. Если на запрошенном board нет
+торговых данных, backend выбирает любой board с ненулевой ценой `LAST`. Данные кешируются со
+стандартным market data TTL (900s).
-## Future Phases
+## Будущие этапы
1. **Positions v2** — pie chart, filtering
2. **Transactions** — buy/sell history, average cost basis
diff --git a/apps/docs/docs/backend/securities.md b/apps/docs/docs/backend/securities.md
index 7e766f3..7a52d40 100644
--- a/apps/docs/docs/backend/securities.md
+++ b/apps/docs/docs/backend/securities.md
@@ -1,41 +1,43 @@
-# Securities Module
+# SecuritiesModule
-The Securities module provides search and filtering capabilities for MOEX financial instruments.
+`SecuritiesModule` отвечает за поиск и фильтрацию финансовых инструментов MOEX.
-## Overview
+## Обзор
- **Backend:** `SecuritiesModule` (`apps/backend/src/modules/securities/`)
-- **Frontend:** Search bar in layout, dedicated Screener page at `/screener`
-- **Source:** MOEX ISS API
+- **Frontend:** поисковая строка в layout и отдельная страница Screener по `/screener`
+- **Источник:** MOEX ISS API
## Search API
`GET /api/v1/securities/search?q={query}&type={type}&limit={limit}`
-Searches for securities by ticker or name.
-- **type:** `all` (default), `share`, `bond`.
-- **Results:** List of `SearchResultItem` with `secid`, `isin`, `shortName`, `type`, `listLevel`.
+Ищет ценные бумаги по тикеру или названию.
+- **type:** `all` по умолчанию, `share`, `bond`.
+- **Результат:** список `SearchResultItem` с `secid`, `isin`, `shortName`, `type`, `listLevel`.
## Security Screener
`GET /api/v1/securities/screener`
-Allows filtering the entire MOEX board (shares or bonds) by market parameters.
+Фильтрует весь board MOEX для акций или облигаций по рыночным параметрам.
-### Query Parameters
+### Query parameters
-| Parameter | Type | Description |
+| Параметр | Тип | Описание |
|---|---|---|
-| `type` | `share` \| `bond` | **Required.** Board type to scan. |
-| `priceMin` / `Max` | number | Price range (RUB for shares, % for bonds) |
-| `volumeMin` | number | Minimum daily trading volume |
-| `yieldMin` / `Max` | number | YTM range (Bonds only) |
-| `durationMin` / `Max`| number | Duration range in years (Bonds only) |
-| `changePercentMin` | number | Minimum daily price change % (Shares only) |
-| `sortBy` | string | Field to sort by (`price`, `volume`, `yieldToMaturity`, etc.) |
-| `sortOrder` | `asc` \| `desc` | Sort direction |
-| `page` / `pageSize` | number | Pagination parameters |
+| `type` | `share` \| `bond` | **Обязателен.** Тип board для сканирования. |
+| `priceMin` / `Max` | number | Диапазон цены: RUB для акций, проценты для облигаций |
+| `volumeMin` | number | Минимальный дневной объём торгов |
+| `yieldMin` / `Max` | number | Диапазон YTM, только для облигаций |
+| `durationMin` / `Max`| number | Диапазон duration в годах, только для облигаций |
+| `changePercentMin` | number | Минимальное дневное изменение цены в %, только для акций |
+| `sortBy` | string | Поле сортировки: `price`, `volume`, `yieldToMaturity`, etc. |
+| `sortOrder` | `asc` \| `desc` | Направление сортировки |
+| `page` / `pageSize` | number | Параметры пагинации |
-### Implementation Details
+### Детали реализации
-The Screener fetches the full market board from MOEX in a single batch request, caches it for 60 seconds (standard market data TTL), and applies filtering/sorting/pagination on the backend. This ensures high performance for complex queries without overwhelming the MOEX API.
+Screener получает полный market board из MOEX одним batch request, кеширует данные на 60 секунд
+или стандартный market data TTL и применяет фильтрацию, сортировку и пагинацию на backend. Так
+сложные запросы остаются быстрыми и не перегружают MOEX API.
diff --git a/apps/docs/docs/development/codegen.md b/apps/docs/docs/development/codegen.md
index 5bd116c..66fe1e4 100644
--- a/apps/docs/docs/development/codegen.md
+++ b/apps/docs/docs/development/codegen.md
@@ -1,10 +1,10 @@
-# Code Generation
+# Генерация кода
-## OpenAPI Types
+## OpenAPI types
Frontend генерирует TypeScript-типы из Swagger-спецификации бэкенда.
-### Generate Types
+### Сгенерировать типы
```bash
npm run codegen -w apps/frontend
@@ -16,17 +16,17 @@ npm run codegen -w apps/frontend
openapi-typescript http://localhost:3000/api/docs-json -o src/api/types.ts
```
-### Prerequisites
+### Требования
- Backend должен быть запущен на `localhost:3000`
- Swagger UI доступен на `http://localhost:3000/api/docs`
-### Output
+### Результат
- `apps/frontend/src/api/types.ts` — сгенерированные типы `paths` и `operations`
- Live Swagger JSON на `http://localhost:3000/api/docs-json` остаётся источником OpenAPI-контракта
-### Verify Artifacts
+### Проверка артефактов
После регенерации OpenAPI artifacts запустите:
@@ -37,6 +37,6 @@ npm run test -w apps/backend -- src/openapi-artifacts.spec.ts
Тест проверяет, что checked-in frontend types содержат актуальные auth, screener и portfolio paths
и не содержат локальный alternate port.
-### Manual Types
+### Ручные типы
Помимо codegen, используются рукописные типы в `apps/frontend/src/api/responses.ts`. Они не полностью соответствуют codegen'овым и поддерживаются вручную.
diff --git a/apps/docs/docs/development/commands.md b/apps/docs/docs/development/commands.md
index aee5e8f..1d66501 100644
--- a/apps/docs/docs/development/commands.md
+++ b/apps/docs/docs/development/commands.md
@@ -1,8 +1,8 @@
-# Commands
+# Команды
-## Root Workspace
+## Корневой workspace
-| Command | Description |
+| Команда | Описание |
|---|---|
| `npm run dev:backend` | Запуск NestJS в режиме watch на :3000 |
| `npm run dev:frontend` | Vite dev-сервер на `:5173`, проксирует `/api` на backend |
@@ -16,9 +16,9 @@
| `npm run format` | Prettier для всех `*.{ts,tsx}` |
| `npm run format:check` | Проверка Prettier для всех `*.{ts,tsx}` |
-## Backend Workspace
+## Backend workspace
-| Command | Description |
+| Команда | Описание |
|---|---|
| `npm run build -w apps/backend` | `nest build` |
| `npm run start:dev -w apps/backend` | `nest start --watch` |
@@ -28,9 +28,9 @@
| `npm run test:watch -w apps/backend` | `vitest` |
| `npm run test:integration -w apps/backend` | Opt-in live MOEX integration tests, требуется network access |
-## Frontend Workspace
+## Frontend workspace
-| Command | Description |
+| Команда | Описание |
|---|---|
| `npm run dev -w apps/frontend` | `vite` |
| `npm run build -w apps/frontend` | `tsc -b && vite build` |
@@ -39,15 +39,15 @@
| `npm run lint -w apps/frontend` | ESLint для `src/**/*.{ts,tsx}` |
| `npm run test -w apps/frontend` | Frontend Vitest suite |
-## Docs Workspace
+## Docs workspace
-| Command | Description |
+| Команда | Описание |
|---|---|
| `npm run dev -w apps/docs` | Docusaurus dev-server |
| `npm run build -w apps/docs` | Production build документации |
| `npm run serve -w apps/docs` | Локальная проверка production build |
-## Single Test
+## Один тест
```bash
npx vitest run apps/backend/src/modules/shares/shares.service.spec.ts -w apps/backend
diff --git a/apps/docs/docs/development/conventions.md b/apps/docs/docs/development/conventions.md
index 63b5426..be82837 100644
--- a/apps/docs/docs/development/conventions.md
+++ b/apps/docs/docs/development/conventions.md
@@ -1,6 +1,6 @@
-# Code Conventions
+# Соглашения по коду
-## Formatting
+## Форматирование
Prettier (`.prettierrc`):
@@ -26,17 +26,17 @@ ESLint запускается для backend (`apps/backend`) и frontend (`apps
Запуск: `npm run lint`
-## Naming (backend)
+## Именование в backend
- ПаскальКейс для модулей, контроллеров, сервисов
- `const` для всех переменных (кроме случаев, где нужна мутация)
- DTO-файлы в `dto/` внутри каждого модуля
-## Imports (backend)
+## Imports в backend
Алиас: `@/*` → `apps/backend/src/*`
-## Imports (frontend)
+## Imports в frontend
Алиас: `@/*` → `apps/frontend/src/*`
@@ -50,7 +50,7 @@ ESLint запускается для backend (`apps/backend`) и frontend (`apps
- Strict: true
- ESModule interop: true
-## Backend-specific
+## Специфика backend
- Использует SWC через `unplugin-swc` (vitest config)
- Контроллеры содержат только маршрутизацию и вызовы сервисов
diff --git a/apps/docs/docs/development/testing.md b/apps/docs/docs/development/testing.md
index 85bcf1f..0b5e1d9 100644
--- a/apps/docs/docs/development/testing.md
+++ b/apps/docs/docs/development/testing.md
@@ -1,12 +1,12 @@
-# Testing
+# Тестирование
-## Backend Tests
+## Тесты backend
Фреймворк: **vitest** (с `unplugin-swc` для быстрой компиляции, не ts-jest).
Конфигурация vitest в `apps/backend/` использует SWC для трансформации TypeScript.
-### Run All Tests
+### Запустить все тесты
```bash
npm run test:backend
@@ -14,21 +14,21 @@ npm run test:backend
npm run test -w apps/backend
```
-### Run Single Test
+### Запустить один тест
```bash
npx vitest run apps/backend/src/modules/shares/shares.service.spec.ts -w apps/backend
```
-### Watch Mode
+### Watch mode
```bash
npm run test:watch -w apps/backend
```
-## Test Files
+## Тестовые файлы
-| File | What it tests |
+| Файл | Что проверяет |
|---|---|
| `shares.service.spec.ts` | SharesService |
| `bonds.service.spec.ts` | BondsService |
@@ -37,7 +37,7 @@ npm run test:watch -w apps/backend
| `securities.service.spec.ts` | SecuritiesService |
| `moex-client.service.spec.ts` | MoexClientService |
-## Frontend Tests
+## Тесты frontend
Фреймворк: **Vitest 4** + **React Testing Library** + **MSW**.
@@ -51,7 +51,7 @@ npm run test -w apps/frontend
Тесты покрывают API-клиент, auth context, hooks, базовые pages и shared components.
-## Live MOEX Integration Tests
+## Live MOEX integration tests
Live MOEX checks вынесены из default backend suite.
diff --git a/apps/docs/docs/frontend/api-client.md b/apps/docs/docs/frontend/api-client.md
index f974de8..e6e916a 100644
--- a/apps/docs/docs/frontend/api-client.md
+++ b/apps/docs/docs/frontend/api-client.md
@@ -1,6 +1,6 @@
-# API Client
+# API client
-## Client (`api/client.ts`)
+## Клиент (`api/client.ts`)
Использует нативный `fetch` с единой обёрткой `request`.
@@ -16,7 +16,7 @@ async function request(
- Парсит JSON-ответ в `ApiEnvelope<{ data: T, meta: ApiResponseMeta }>`
- Выбрасывает `Error` при HTTP-ошибке
-## API Functions
+## API functions
| Function | Method | Path |
|---|---|---|
@@ -48,7 +48,7 @@ async function request(
| `removePosition(portfolioId, positionId)` | DELETE | `/api/v1/portfolios/:id/positions/:positionId` |
| `getPortfolioAnalytics(portfolioId)` | GET | `/api/v1/portfolios/:id/analytics` |
-## Types
+## Типы
Ручные типы ответов в `api/responses.ts`:
diff --git a/apps/docs/docs/frontend/components.md b/apps/docs/docs/frontend/components.md
index f581e1e..4b5b3e5 100644
--- a/apps/docs/docs/frontend/components.md
+++ b/apps/docs/docs/frontend/components.md
@@ -1,4 +1,4 @@
-# Components
+# Компоненты
## Layout (`components/Layout.tsx`)
diff --git a/apps/docs/docs/frontend/hooks.md b/apps/docs/docs/frontend/hooks.md
index 5b67ac6..79376cc 100644
--- a/apps/docs/docs/frontend/hooks.md
+++ b/apps/docs/docs/frontend/hooks.md
@@ -1,8 +1,8 @@
-# Hooks
+# Хуки
Все хуки используют TanStack Query v5.
-| Hook | Query Key | Stale Time | Description |
+| Хук | Query key | Stale time | Описание |
|---|---|---|---|
| `useSearch(query)` | `['securities', 'search', query]` | 60s | Поиск инструментов (enabled: query ≥ 2 символов) |
| `useStock(secid)` | `['stock', secid]` | 900s | Спецификация акции |
@@ -11,7 +11,7 @@
| `useBond(secid)` | `['bond', secid]` | 900s | Спецификация облигации |
| `useBondCandles(secid, interval, from, till)` | `['bondCandles', secid, interval, from, till]` | 3600s | Свечи облигации |
-## Query Configuration
+## Конфигурация Query
```typescript
const queryClient = new QueryClient({
@@ -25,7 +25,7 @@ const queryClient = new QueryClient({
});
```
-## Hook Pattern
+## Паттерн hook
Каждый хук:
diff --git a/apps/docs/docs/frontend/overview.md b/apps/docs/docs/frontend/overview.md
index 3cb6e60..38010da 100644
--- a/apps/docs/docs/frontend/overview.md
+++ b/apps/docs/docs/frontend/overview.md
@@ -1,8 +1,8 @@
-# Frontend Overview
+# Обзор frontend
React SPA, собранная с Vite.
-## Tech Stack
+## Технологический стек
- React 18
- react-router-dom v6
@@ -12,11 +12,11 @@ React SPA, собранная с Vite.
- Vitest + Testing Library + MSW
- Vite 5
-## Source Layout
+## Структура исходников
```
apps/frontend/src/
-├── main.tsx # Entry point
+├── main.tsx # Точка входа
├── App.tsx # BrowserRouter
├── routes.tsx # Маршруты
├── api/
@@ -65,6 +65,6 @@ apps/frontend/src/
└── styles.css
```
-## Entry Point
+## Точка входа
`main.tsx` рендерит `App.tsx`, который содержит `BrowserRouter` → `AppRoutes`.
diff --git a/apps/docs/docs/frontend/routes.md b/apps/docs/docs/frontend/routes.md
index 8fa479d..350f8cc 100644
--- a/apps/docs/docs/frontend/routes.md
+++ b/apps/docs/docs/frontend/routes.md
@@ -1,8 +1,8 @@
-# Routes
+# Маршруты
Определены в `apps/frontend/src/routes.tsx`.
-| Path | Component | Access | Description |
+| Path | Component | Доступ | Описание |
|---|---|---|---|
| `/` | `HomePage` | Public | Главная страница |
| `/stocks/:secid` | `StockPage` | Public | Страница акции |
@@ -20,7 +20,7 @@
- `` с максимальной шириной 1200px
- `` для контента страницы
-## Route Structure
+## Структура маршрутов
```tsx
diff --git a/apps/docs/docs/frontend/styling.md b/apps/docs/docs/frontend/styling.md
index b3353f0..c566103 100644
--- a/apps/docs/docs/frontend/styling.md
+++ b/apps/docs/docs/frontend/styling.md
@@ -1,10 +1,10 @@
-# Styling
+# Стили
-## Approach
+## Подход
CSS через единый `styles.css` с CSS custom properties. Без CSS-in-JS или Tailwind.
-## CSS Custom Properties
+## CSS custom properties
Определены в `styles.css`:
diff --git a/apps/docs/docs/getting-started.md b/apps/docs/docs/getting-started.md
index a814388..53d4943 100644
--- a/apps/docs/docs/getting-started.md
+++ b/apps/docs/docs/getting-started.md
@@ -1,24 +1,24 @@
-# Getting Started
+# Быстрый старт
-## Prerequisites
+## Требования
- Node.js 20+
- npm 9+
-## Quick Start
+## Запуск для разработки
```bash
-# 1. Clone repository
+# 1. Клонировать репозиторий
git clone
cd moex-vibe
-# 2. Install dependencies
+# 2. Установить зависимости
npm install
-# 3. Start backend (http://localhost:3000)
+# 3. Запустить backend (http://localhost:3000)
npm run dev:backend
-# 4. In another terminal — start frontend (http://localhost:5173)
+# 4. В другом терминале запустить frontend (http://localhost:5173)
npm run dev:frontend
```
@@ -34,9 +34,9 @@ docker compose up --build
- Backend: http://localhost:3000
- Swagger UI: http://localhost:3000/api/docs
-## Verify
+## Проверка
-Open http://localhost:5173 (or http://localhost:80 for Docker). You should see:
+Откройте http://localhost:5173 или http://localhost:80 при запуске через Docker. Должны быть доступны:
1. Страница с поисковой строкой и заголовком "MoexVibe"
2. Swagger UI на http://localhost:3000/api/docs со всеми endpoint'ами
diff --git a/apps/docs/docs/infrastructure/ci.md b/apps/docs/docs/infrastructure/ci.md
index 34bc024..80a8723 100644
--- a/apps/docs/docs/infrastructure/ci.md
+++ b/apps/docs/docs/infrastructure/ci.md
@@ -1,6 +1,6 @@
# CI/CD
-## CI Pipeline
+## CI pipeline
Определён в `.gitea/workflows/ci.yml`. Запускается на push/PR в `main`.
diff --git a/apps/docs/docs/infrastructure/docker.md b/apps/docs/docs/infrastructure/docker.md
index 6ee37d6..28221a8 100644
--- a/apps/docs/docs/infrastructure/docker.md
+++ b/apps/docs/docs/infrastructure/docker.md
@@ -1,10 +1,10 @@
# Docker
-## Architecture
+## Архитектура
```mermaid
graph TD
- User["User"]
+ User["Пользователь"]
Nginx["Nginx :80"]
Backend["Node.js :3000"]
MOEX["MOEX ISS
iss.moex.com"]
@@ -15,7 +15,7 @@ graph TD
Backend -->|"API calls"| MOEX
```
-## Services
+## Сервисы
### Backend (`Dockerfile.backend`)
@@ -57,7 +57,7 @@ CMD ["nginx", "-g", "daemon off;"]
Собирает статику, затем раздаёт через Nginx.
-### Nginx Config (`nginx.conf`)
+### Конфигурация Nginx (`nginx.conf`)
```nginx
server {
@@ -105,7 +105,7 @@ services:
- backend
```
-## Run
+## Запуск
```bash
docker compose up --build
diff --git a/apps/docs/docs/intro.md b/apps/docs/docs/intro.md
index 40e3ccc..e824d09 100644
--- a/apps/docs/docs/intro.md
+++ b/apps/docs/docs/intro.md
@@ -6,21 +6,21 @@ slug: /
Веб-приложение для анализа ценных бумаг Московской биржи (MOEX).
-## Tech Stack
+## Технологический стек
- **Backend:** NestJS, TypeScript, OpenAPI (Swagger)
- **Frontend:** React 18, TypeScript, Vite, TanStack Query v5, lightweight-charts v4
-- **Infrastructure:** Docker, docker-compose
+- **Инфраструктура:** Docker, docker-compose
- **CI:** Gitea Actions
-## Repository Structure
+## Структура репозитория
```
moex-vibe/
├── apps/
│ ├── backend/ # NestJS API (единственная точка доступа к MOEX)
│ ├── frontend/ # React SPA
-│ └── docs/ # Docusaurus documentation site
+│ └── docs/ # Docusaurus-сайт документации
├── docs/
│ └── superpowers/
│ └── specs/ # Согласованные SDD-спецификации
@@ -32,7 +32,7 @@ moex-vibe/
└── tsconfig.base.json
```
-## Key Principles
+## Ключевые принципы
- Backend — единственный клиент MOEX. Frontend никогда не обращается к MOEX напрямую.
- npm workspaces монорепозиторий: `apps/backend`, `apps/frontend` и `apps/docs`.
diff --git a/apps/docs/docusaurus.config.ts b/apps/docs/docusaurus.config.ts
index 7d2308c..1f331f6 100644
--- a/apps/docs/docusaurus.config.ts
+++ b/apps/docs/docusaurus.config.ts
@@ -4,7 +4,7 @@ import type * as Preset from '@docusaurus/preset-classic';
const config: Config = {
title: 'MoexVibe',
- tagline: 'Analysis of MOEX securities',
+ tagline: 'Анализ ценных бумаг MOEX',
favicon: 'img/favicon.ico',
url: 'https://moexvibe.example.com',
diff --git a/apps/docs/sidebars.ts b/apps/docs/sidebars.ts
index 9ae9dda..2d11a82 100644
--- a/apps/docs/sidebars.ts
+++ b/apps/docs/sidebars.ts
@@ -35,12 +35,12 @@ const sidebars: SidebarsConfig = {
},
{
type: 'category',
- label: 'Infrastructure',
+ label: 'Инфраструктура',
items: ['infrastructure/docker', 'infrastructure/ci'],
},
{
type: 'category',
- label: 'Development',
+ label: 'Разработка',
items: [
'development/commands',
'development/testing',
@@ -50,7 +50,7 @@ const sidebars: SidebarsConfig = {
},
{
type: 'category',
- label: 'Architecture Decisions (ADR)',
+ label: 'Архитектурные решения (ADR)',
items: [
'adr/index',
'adr/ADR-001-backend-single-point-of-access',
diff --git a/docs/superpowers/specs/2026-06-15-russian-docs-and-architecture-diagrams.md b/docs/superpowers/specs/2026-06-15-russian-docs-and-architecture-diagrams.md
new file mode 100644
index 0000000..ea299df
--- /dev/null
+++ b/docs/superpowers/specs/2026-06-15-russian-docs-and-architecture-diagrams.md
@@ -0,0 +1,212 @@
+# Русификация документации и исправление архитектурных схем — SDD-спецификация
+
+**Дата:** 2026-06-15
+
+**Статус:** черновик для ревью
+
+## Контекст
+
+Документация проекта публикуется только из `apps/docs` через Docusaurus. Текущая структура уже
+зафиксирована в `docs/superpowers/specs/2026-06-13-docusaurus-docs-design.md`: страницы лежат в
+`apps/docs/docs`, навигация описана в `apps/docs/sidebars.ts`, Mermaid включён через
+`@docusaurus/theme-mermaid`.
+
+Сейчас большая часть человекочитаемого текста в опубликованной документации написана на английском:
+заголовки, подписи таблиц, описания endpoint'ов, ADR summary, названия разделов sidebar. Проект
+ведётся на русском, поэтому документация должна быть русскоязычной, оставляя на английском только
+технические названия, идентификаторы и общепринятые инженерные термины.
+
+На странице `apps/docs/docs/architecture.md` первая Mermaid-схема `System Architecture` смешивает
+в одном `graph TD` внешний контур приложения и внутреннее устройство backend. Из-за этого Docusaurus
+рендерит слишком широкую и высокую схему: блоки и подписи стрелок визуально накладываются друг на
+друга, особенно вокруг `Backend (NestJS)`, `Browser (React SPA)`, `NestJS API`, cache, MOEX и SQLite.
+
+## Цели
+
+1. Перевести опубликованную человекочитаемую документацию в `apps/docs` на русский язык.
+2. Оставить без перевода технические названия и идентификаторы, которые должны совпадать с кодом,
+ API, библиотеками или файлами.
+3. Исправить читаемость Mermaid-схем в `apps/docs/docs/architecture.md`, убрав наложение блоков и
+ подписей.
+4. Сохранить текущую структуру Docusaurus и URL/slugs, чтобы не ломать существующие ссылки.
+5. Задать проверяемые критерии приёмки для последующей реализации.
+
+## Не цели
+
+- Не менять backend, frontend, OpenAPI contract, runtime-конфигурацию или Docker-инфраструктуру.
+- Не переименовывать файлы документации, ADR-файлы, route slugs и ссылки между страницами, если это
+ не требуется для исправления битой ссылки.
+- Не внедрять i18n с несколькими локалями: сайт остаётся одноязычным с `defaultLocale: 'ru'`.
+- Не переписывать архитектурные решения по сути. ADR переводятся как документация, но их смысл,
+ статус и последствия сохраняются.
+- Не менять визуальную тему Docusaurus сверх минимальной поддержки читаемых Mermaid-схем.
+
+## Область изменений
+
+### Опубликованная документация
+
+Перевод и редактура затрагивают:
+
+- `apps/docs/docusaurus.config.ts`
+- `apps/docs/sidebars.ts`
+- `apps/docs/docs/intro.md`
+- `apps/docs/docs/getting-started.md`
+- `apps/docs/docs/architecture.md`
+- `apps/docs/docs/backend/*.md`
+- `apps/docs/docs/frontend/*.md`
+- `apps/docs/docs/development/*.md`
+- `apps/docs/docs/infrastructure/*.md`
+- `apps/docs/docs/adr/*.md`
+
+### Стили
+
+`apps/docs/src/css/custom.css` можно менять только если после разбиения Mermaid-схем остаются
+проблемы с шириной, переносами или горизонтальной прокруткой. CSS не должен быть основным способом
+исправления сломанной схемы.
+
+## Правила русификации
+
+### Переводить
+
+- Заголовки страниц и разделов: `Architecture` -> `Архитектура`, `Getting Started` -> `Быстрый старт`.
+- Sidebar labels: `Infrastructure` -> `Инфраструктура`, `Development` -> `Разработка`,
+ `Architecture Decisions (ADR)` -> `Архитектурные решения (ADR)`.
+- Описания таблиц и колонок: `Description` -> `Описание`, `Required` -> `Обязателен`,
+ `Default` -> `По умолчанию`.
+- Поясняющий текст, инструкции, списки, summaries, примечания к flow/sequence diagrams.
+- Подписи Mermaid-стрелок, если они не являются точным API, cookie/header или именем метода:
+ `fetches` -> `запрашивает`, `uses` -> `использует`, `raw data` -> `сырые данные`.
+- Ошибочные или неактуальные формулировки, найденные при переводе, если их можно исправить по
+ текущему коду или уже существующим SDD/ADR.
+
+### Не переводить
+
+- Названия технологий и библиотек: `NestJS`, `React`, `Vite`, `TanStack Query`, `Prisma`,
+ `SQLite`, `Docusaurus`, `Docker`, `Nginx`, `Vitest`, `MSW`, `Swagger`, `OpenAPI`,
+ `lightweight-charts`, `openapi-fetch`.
+- Архитектурные технические существительные, уже используемые в проекте как термины: `backend`, `frontend`,
+ `middleware`, `endpoint`, `request`, `response`, `cache`, `cookie`, `access token`,
+ `refresh token`, `rate limiter`, `circuit breaker`, `codegen`, `workspace`, `build`, `lint`.
+- Имена модулей, классов, DTO, методов, env vars, scripts, файлов и директорий:
+ `AuthModule`, `MoexClientService`, `CACHE_MARKET_DATA_TTL`, `npm run dev:backend`,
+ `apps/backend/src/modules/auth`.
+- HTTP methods, URL paths, JSON keys, TypeScript/Prisma identifiers, enum values и code blocks.
+- ADR file names and IDs: `ADR-001-backend-single-point-of-access.md`, `ADR-010`, etc.
+
+### Стиль русского текста
+
+- Писать для разработчика проекта, без маркетингового тона.
+- Использовать короткие предложения и конкретные глаголы.
+- Сохранять технические термины в одном написании по всему сайту.
+- Не переводить термин, если перевод ухудшает связь с кодом или общепринятой практикой.
+
+## План исправления `architecture.md`
+
+Текущую первую схему нужно заменить набором меньших схем:
+
+1. `Архитектура системы` — внешний контур: Browser/React SPA, NestJS API, SQLite, cache,
+ MOEX ISS API. Направление лучше сделать `flowchart LR`, чтобы схема читалась слева направо.
+2. `Внутренние модули backend` — отдельная схема только для NestJS feature/global modules:
+ AuthModule, SharesModule, BondsModule, CandlesModule, SecuritiesModule, PortfolioModule,
+ HealthModule, MoexClientModule, CacheModule/CacheService, PrismaModule/PrismaService.
+3. `Поток авторизованного запроса` — существующий `sequenceDiagram` оставить как отдельный
+ сценарий, но перевести человекочитаемые подписи и сверить путь запроса с актуальными docs/API.
+
+Для первой схемы недопустимо помещать большой `subgraph Backend_Internal` внутрь графа внешней
+архитектуры. Если нужно показать, что `NestJS API` состоит из модулей, это должно быть ссылкой
+текстом или отдельной диаграммой ниже.
+
+## Рекомендуемый Mermaid-паттерн
+
+```mermaid
+flowchart LR
+ Browser["Browser
(React SPA)"]
+ API["NestJS API
:3000"]
+ Cache["In-memory cache
(cache-manager)"]
+ MOEX["MOEX ISS API
iss.moex.com"]
+ DB[("SQLite
(Prisma)")]
+
+ Browser -->|"/api/v1/*"| API
+ Browser -->|"Authorization: Bearer"| API
+ Browser -->|"Cookie: refreshToken"| API
+ API -->|"getOrFetch()"| Cache
+ API -->|"GET /iss/*.json"| MOEX
+ API -->|"Prisma ORM"| DB
+ Cache -->|"данные"| API
+ MOEX -->|"сырые данные"| API
+ DB -->|"пользователи и портфели"| API
+```
+
+В реализации можно менять конкретное расположение узлов, если итоговая схема проходит визуальную
+проверку и остаётся семантически эквивалентной.
+
+## План реализации
+
+1. Создать отдельную ветку `codex/russian-docs-architecture-diagrams`, если текущая ветка не
+ предназначена для этой работы.
+2. Пройти по navigation/config files:
+ `apps/docs/docusaurus.config.ts` и `apps/docs/sidebars.ts`.
+3. Перевести overview-страницы:
+ `intro.md`, `getting-started.md`, `architecture.md`.
+4. В `architecture.md` заменить первую Mermaid-схему на две небольшие схемы и перевести
+ человекочитаемые подписи в `sequenceDiagram`.
+5. Перевести backend-раздел, сохраняя имена модулей, endpoints, DTO, env vars и code examples.
+6. Перевести frontend-раздел, сохраняя названия React/Vite/TanStack Query и имена hooks/components.
+7. Перевести infrastructure/development-разделы, сохраняя команды, scripts, имена workflow jobs,
+ Dockerfile paths и package names.
+8. Перевести ADR index и ADR pages без изменения сути решений, статусов и ссылок.
+9. Проверить Markdown-ссылки, таблицы и fenced code blocks.
+10. Запустить `npm run build:docs`.
+11. Если build проходит, открыть локальный Docusaurus и визуально проверить `architecture.md`:
+ нет наложения блоков, подписи стрелок читаемы, схема не уезжает за viewport на desktop.
+
+## Критерии приёмки
+
+- Все опубликованные страницы в `apps/docs/docs/**/*.md` имеют русские заголовки и русский
+ человекочитаемый текст, кроме разрешённых технических названий.
+- `apps/docs/sidebars.ts` показывает русские labels для всех человекочитаемых категорий.
+- `apps/docs/docusaurus.config.ts` содержит русскую tagline или нейтральную русскоязычную фразу.
+- Имена файлов, route slugs, ADR IDs, endpoint paths, env vars, code blocks и API examples не
+ переименованы ради перевода.
+- `apps/docs/docs/architecture.md` не содержит одной большой Mermaid-схемы, которая одновременно
+ показывает внешний контур и внутренние backend modules.
+- Mermaid-схемы на странице `architecture.md` рендерятся без наложения узлов и подписей.
+- `npm run build:docs` завершается успешно.
+- После сборки нет новых broken links или broken Markdown links сверх текущей политики
+ `onBrokenLinks: 'warn'`, `onBrokenMarkdownLinks: 'warn'`.
+
+## Проверка качества
+
+### Автоматическая
+
+```bash
+npm run build:docs
+```
+
+Ожидаемый результат: Docusaurus build завершается без ошибки.
+
+### Ручная
+
+1. Запустить `npm run dev:docs`.
+2. Открыть страницу `/architecture`.
+3. Проверить, что первая схема помещается в контентную область на desktop без наложения.
+4. Проверить, что sequence diagram читается и подписи на русском там, где они не являются
+ техническими идентификаторами.
+5. Выборочно открыть по одной странице из разделов Backend, Frontend, Инфраструктура, Разработка и
+ ADR, чтобы подтвердить единый стиль перевода.
+
+## Риски и решения
+
+| Риск | Решение |
+|---|---|
+| Чрезмерный перевод ломает связь с кодом | Следовать списку "Не переводить" и оставлять code/API terms как есть |
+| Mermaid всё ещё строит слишком широкую схему | Делить диаграмму ещё мельче, а не пытаться лечить layout только CSS |
+| ADR после перевода могут звучать как новые решения | Сохранять ID, статус, контекст, consequences и не менять смысл |
+| Перевод таблиц может сломать Markdown | После каждой группы страниц запускать build или локально просматривать diff |
+
+## Решение для ревью
+
+Рекомендуемый путь: сначала выполнить русификацию и разбиение схем без изменения информационной
+архитектуры сайта. После этого отдельно оценить, нужно ли делать полноценную редакторскую
+нормализацию терминов или добавлять глоссарий. Для текущей задачи глоссарий в отдельной странице
+не нужен: достаточно единых правил в этой спецификации и аккуратного применения по docs.