94 lines
15 KiB
Markdown
94 lines
15 KiB
Markdown
# Engineering Design
|
||
|
||
## Context and Constraints
|
||
|
||
Текущий текстовый поиск акций использует `FindInstrument` и ограничивает результат 30 выпусками до получения подробных сведений и цены. Он не может быть источником скринера. Новый поток использует полный `Shares(INSTRUMENT_STATUS_ALL)` и целевой контракт из [System Analysis](system-analysis/analysis.md); существующие ссылки на страницы отдельных акций продолжают использовать точный UID. Справочные сведения остаются online-only, а личный портфель не является каталогом.
|
||
|
||
## Technical Goals / Non-Goals
|
||
|
||
- Получить полный список один раз при явном поиске, отфильтровать и зафиксировать упорядоченную выдачу до первой страницы; чтение страницы не вызывает новый поиск T-Invest.
|
||
- Изолировать результат от другого пользователя и от другого поколения его подключения; обеспечить ограниченный расход памяти без нового долговременного справочника.
|
||
- Не обогащать все инструменты ценами и фундаментальными показателями и не менять старый текстовый endpoint или поиск облигаций.
|
||
|
||
## Implementation Impact Map
|
||
|
||
### main
|
||
|
||
N/A: workspace, Compose и release orchestration не меняются.
|
||
|
||
### frontend
|
||
|
||
Affected: раздел `/stocks` становится формой скринера с черновыми и применёнными условиями, явным запуском и страницами результата; использует опубликованный API contract.
|
||
|
||
### backend
|
||
|
||
Affected: отдельные операции скринера, получение списка `Shares`, фильтрация и временный результат с безопасной выдачей страниц. Текущий поиск и публичные страницы отдельных акций остаются доступными.
|
||
|
||
### qa
|
||
|
||
N/A: отдельного QA repository пока нет; системная проверка описана в `qa-analysis.md`.
|
||
|
||
## Decisions
|
||
|
||
### main
|
||
|
||
N/A.
|
||
|
||
### frontend
|
||
|
||
На странице акций форма содержит необязательные текст, сектор, тип акции, валюту, площадку, страну риска и верхнюю границу лота. Черновик полей отделён от последнего применённого поиска; изменение поля само по себе не отправляет запрос и не заменяет результат. Нажатие «Найти» всегда отправляет новый POST, сбрасывает номер страницы и связывает ответ со своим `searchId`. Последующие страницы читаются GET; поздний ответ прежнего запуска не заменяет результат нового. Ошибка или потеря снимка отображается отдельно от подтверждённого пустого списка.
|
||
|
||
Повторно использовать имеющуюся карточку `InstrumentSearchCard` с точным `instrumentUid` в ссылке. Текущая карточка цены не подходит для скринера без цены: показывать только доступные каталожные поля и не приписывать им котировку. Список опций берётся из известной предметной лексики и доступных значений каталога, если они предоставлены; фильтрация выполняется на сервере. Если динамические опции требуют дополнительного контракта, вернуть этот вопрос в System Analysis до реализации, а не добавлять неоговорённый запрос.
|
||
|
||
### backend
|
||
|
||
Расширить T-Bank client операцией `Shares` через существующий gRPC transport, очередь, deadline и retry. Перед выбором страницы проверить полноту полученного ответа, обязательные признаки покупки/торговли и все выбранные поля на каждом инструменте. Сначала отбор по инструментам, затем детерминированный представитель по нормализованному ISIN (при отсутствии — UID), сортировка по тикеру/UID, затем страницы. Не переносить имя, режим или характеристики между UID при дедупликации.
|
||
|
||
Временный снимок полного результата на backend, привязанный к пользователю и revision подключения, содержит выбранные элементы и `asOf`; случайный `searchId` — только указатель, не пропуск доступа. Держать снимок в ограниченной RAM с конечным сроком жизни и пределом числа результатов. Истечение, вытеснение или рестарт дают 404 при чтении страницы и предложение начать новый поиск; никогда не пересчитывать старую страницу по новому каталогу. Для одного экземпляра backend достаточно локального хранилища в памяти; распределённое хранение не требуется текущему deployment. При каждом GET проверить ownership и revision до выдачи страницы. Повторный POST всегда обращается к T-Invest, даже при одинаковых условиях.
|
||
|
||
### qa
|
||
|
||
N/A.
|
||
|
||
## Cross-cutting Implementation
|
||
|
||
Не передавать T-Bank token в браузер или результат; служебные события регистрируют исход поиска, время каталога, количество исходных инструментов/выпусков и отсутствие снимка без полного текста поискового условия. При смене подключения прежняя revision теряет доступ к снимку независимо от того, успел ли он вытесниться. Отказ источника не публикует частичный снимок и не превращается в пустую страницу. Ограничить размер памяти/число одновременных результатов; локальный rate limiter остаётся единственным владельцем gRPC-повторов.
|
||
|
||
## Data / Storage Changes
|
||
|
||
Prisma schema и миграция не нужны: новый результат поиска краткоживущий и не является самостоятельной долговременной копией каталога. После перезапуска backend прежние `searchId` более недоступны. Персональные сохранённые данные не меняются.
|
||
|
||
## Contract Implementation
|
||
|
||
Backend DTO и Swagger metadata задают обе операции согласно [target OpenAPI](system-analysis/contracts/stock-screener.openapi.yaml). Валидация входа и статусы 400/401/404/503 соответствуют утверждённым ошибкам; success shape включает `items`, `searchId`, `asOf`, `page`, `hasNext`. Сгенерированный backend OpenAPI сравнить семантически с target contract до переноса snapshot в frontend и проверить опубликованный artifact и сгенерированные клиентские типы. Не подменять утверждённый контракт отдельным frontend DTO. Старый `GET /api/v1/stocks` остаётся совместимым.
|
||
|
||
## Developer Verification Strategy
|
||
|
||
Backend owner проверяет отбор по обоим флагам, все поля условий, граничный лот, дедупликацию после отбора, порядок страниц и привязку к пользователю/revision на собственных тестовых границах; отдельно HTTP validation и соответствие generated OpenAPI target. Frontend owner проверяет разделение черновика и применённых условий, повторный POST, переход между страницами и отображение пустого результата/ошибки на уровне страницы. На этапе реализации применяются соответствующие repo-local verify gates; проверки не требуют живого T-Invest token.
|
||
|
||
## UI/UX Package
|
||
|
||
**Applicability and scope:** new/changed UI для страницы `/stocks`. Пакет полностью inline; графические материалы не требуются.
|
||
|
||
**Package revision:** 1, `wealth-ledger-specs:openspec/changes/stock-screener/design.md`.
|
||
|
||
**Screens, states and interactions:** необязательный текст, шесть фильтров и кнопка «Найти»; до запуска нет утверждения об отсутствии акций. После запуска — загрузка, карточки и время списка либо подтверждённое пустое состояние; ошибка источника отдельно с возможностью повторить поиск. Кнопки перехода к страницам показывают тот же снимок; при недоступном снимке — предложение нового поиска. Черновые изменения не меняют показанный результат до нажатия. Формулировка доступности указывает на признаки каталога, а не на возможность немедленной сделки.
|
||
|
||
**Responsive behavior:** форма перестраивается в одну колонку на узком экране, карточки и навигация доступны без горизонтального прокручивания.
|
||
|
||
**Reused source and differences:** повторно используются текущий раздел акций, его компоненты карточки и состояния загрузки/ошибки; точный вход в текущий AS-IS — `frontend/src/pages/stocks/ui/StocksPage/StocksPage.view.tsx`, `frontend/src/features/instrument-search/ui/InstrumentSearch/InstrumentSearch.view.tsx`. Скринер заменяет автоматический поиск при вводе на явный запуск и не обязан показывать цену. Долговременное обоснование текущего поведения — `docs/domains/instruments/stocks.md`; сам пакет остаётся по locator версии выше и доступен после архива.
|
||
|
||
## Risks / Trade-offs
|
||
|
||
- Полный `Shares(ALL)` может быть объёмным; предельное потребление памяти и допустимую длительность live-поиска нужно измерить до rollout. Локальный снимок экономит повторные вызовы и сохраняет порядок, но теряется при рестарте или вытеснении; предусмотрен явный повторный поиск.
|
||
- Опции фильтров по сектору/стране могут изменяться вместе с каталогом. Нельзя обещать полноту пользовательского выбора, если показывать только фиксированные значения без подтверждения состава каталога.
|
||
- Поток не знает права конкретного счёта и состояние торгов; текст интерфейса не должен обещать немедленную покупку.
|
||
|
||
## Migration / Rollout / Rollback
|
||
|
||
Сначала backend реализует и публикует совместимый контракт, затем frontend получает новый artifact и использует скринер; действующий текстовый endpoint остаётся доступным. При откате frontend продолжает работать со старым endpoint; временные снимки можно удалить без миграции. Изменений личных данных и разрушительных миграций нет.
|
||
|
||
## Solution Assessment
|
||
|
||
Сопоставлены: (1) фильтрация первых 30 результатов `FindInstrument` — дешёвая, но неверная полнота, отклонена; (2) сохранённый постоянный каталог с индексацией — облегчает массовые запросы, но противоречит online-only границе и добавляет синхронизацию; (3) полный online `Shares` и ограниченный краткоживущий снимок результата — соответствует полноте и стабильным страницам при текущем single-instance deployment. Последний вариант принят для изменения. При реальной потребности в нескольких экземплярах потребуется отдельное согласование shared storage или stateless модели страниц, а не молчаливое изменение контракта.
|