spec: add stock screener
This commit is contained in:
@@ -0,0 +1,2 @@
|
|||||||
|
schema: wealth-ledger-sdd-v2
|
||||||
|
created: 2026-10-10
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
# 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 модели страниц, а не молчаливое изменение контракта.
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
# Documentation Impact
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
После реализации нужна актуализация существующих AS-IS страниц: раздел «Акции» перестанет быть только текстовым поиском. Целевой контракт и мотивы выбора временного снимка остаются в change; документация описывает получившуюся функцию и границы данных.
|
||||||
|
|
||||||
|
## Documentation Changes
|
||||||
|
|
||||||
|
| Action | Target | Category | Resulting AS-IS | Source | Verification |
|
||||||
|
| --- | --- | --- | --- | --- | --- |
|
||||||
|
| update | `docs/domains/instruments/stocks.md` | domain | Описать скринер с явной кнопкой поиска, необязательным текстом и шестью условиями, отбор только по подтверждённым признакам покупки и торговли через API, одну карточку на выпуск, стабильные страницы по 30 и время получения каталога. Разъяснить, что это не проверка возможности немедленной сделки и что подтверждённая пустота отличается от отказа источника. Прежнее описание автопоиска на `/stocks` заменить на фактическое поведение; сохранённый текстовый API и переход к отдельной акции описывать отдельно. | [Delta spec](specs/stock-screener/spec.md), [System Analysis](system-analysis/analysis.md), [Design](design.md) | Сверить страницу с реализованным UI, порядком выдачи и доступными полями; не называть признак покупки гарантией сделки или цену условием скринера. |
|
||||||
|
| update | `docs/integrations/tbank.md` | integration | Кратко объяснить источник полного online-каталога для скринера и смысл признаков доступности; не смешивать справочный каталог с личным портфелем или торговым статусом. | [System Analysis](system-analysis/analysis.md#contracts) | Сверить с фактическим вызовом полного списка акций и опубликованным поведением backend, не дублировать протокол целиком. |
|
||||||
|
| update | `docs/architecture/http-api.md` | architecture | Отметить семантику нового запуска поиска и чтения страницы зафиксированного результата: повторный запуск получает новый список, чтение страницы сохраняет состав и время, недоступный результат не становится пустым. Ссылаться на backend-generated OpenAPI для точной формы операций. | [Target contract](system-analysis/contracts/stock-screener.openapi.yaml), [System Analysis](system-analysis/analysis.md#data-and-state-semantics) | Сверить описание с опубликованным backend OpenAPI и фактической обработкой ошибок, не переносить YAML в docs. |
|
||||||
|
| update | `docs/overview.md` | overview | Задача «Изучить конкретный инструмент» включает подбор акций по параметрам наряду с открытием отдельной акции и поиском облигаций. | [Proposal](proposal.md), [Delta spec](specs/stock-screener/spec.md) | Убедиться, что обзор ссылается на актуальную страницу акций и не обещает фильтры по финансовым показателям. |
|
||||||
|
| update | `docs/README.md` | overview | Строка навигации «Акции и облигации» явно называет скринер акций, не создавая новую страницу ради функции. | [Delta spec](specs/stock-screener/spec.md) | Проверить ссылку на существующий раздел акций и согласованность с обзором. |
|
||||||
|
| update | `docs/glossary.md` | domain | Определить «Скринер акций» как подбор выпусков по условиям из каталога с указанием времени получения; отличить его от текстового поиска конкретного инструмента, личного портфеля и подтверждения исполнимости сделки. | [Delta spec](specs/stock-screener/spec.md), [System Analysis](system-analysis/analysis.md#data-and-state-semantics) | Определение не содержит маршрутов, классов, механизма хранения или перечня HTTP-полей. |
|
||||||
|
|
||||||
|
## Diagram Promotion
|
||||||
|
|
||||||
|
По умолчанию не переносить `system-analysis/sequences/stock-screener.mmd`: он объясняет согласование change и не нужен для понимания устойчивого пользовательского поведения сверх тематической страницы. Если после реализации поток страниц окажется существенным для AS-IS архитектуры, пересмотреть отдельно по фактическому состоянию, а не копировать диаграмму автоматически.
|
||||||
|
|
||||||
|
## ADR Decision
|
||||||
|
|
||||||
|
N/A: краткоживущий снимок результатов — обратимый способ обеспечить стабильные страницы при текущем одном экземпляре backend. Альтернативы и последствия уже записаны в Design; нового долгоживущего инфраструктурного стандарта не вводится.
|
||||||
|
|
||||||
|
## Glossary / Navigation Impact
|
||||||
|
|
||||||
|
Добавить один термин в существующий `docs/glossary.md`; обновить существующие строки `docs/README.md` и `docs/overview.md`. Новая страница или раздел индекса не требуются.
|
||||||
|
|
||||||
|
## No-Change Rationale
|
||||||
|
|
||||||
|
`docs/architecture/cross-cutting/data-freshness.md` уже различает online-справочные сведения и сохранённые личные данные; обновление нужно лишь при изменении этой границы при реализации. Прочие страницы, включая облигации, не затрагиваются. Канонический OpenAPI в docs не создаётся.
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
# Proposal
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
Раздел акций сейчас позволяет искать по тексту, но не помогает найти подходящие акции по характеристикам без заранее известного названия или тикера. Пользователю нужен скринер, который проверяет условия по каталогу доступных к покупке акций и показывает результаты без ограничения исходного набора первыми совпадениями текстового поиска.
|
||||||
|
|
||||||
|
## What Changes
|
||||||
|
|
||||||
|
- В разделе акций появляется поиск по выбранным параметрам с явным запуском кнопкой «Найти»; строка названия или тикера остаётся необязательным условием.
|
||||||
|
- Результаты включают только акции, доступные для покупки и торговли через API по сведениям каталога T-Invest на момент получения списка. Это не гарантия исполнения заявки конкретного пользователя прямо сейчас.
|
||||||
|
- Пользователь получает устойчиво упорядоченную постраничную выдачу по 30 выпусков и видит время получения списка.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
### In Scope
|
||||||
|
|
||||||
|
- Необязательные условия по сектору, типу акции, валюте расчётов, площадке, стране риска и максимальному размеру лота; необязательный текстовый поиск.
|
||||||
|
- Одна карточка на выпуск (ISIN), при отсутствии ISIN — отдельная карточка на инструмент; выбор представителя выпуска сохраняет ссылку на конкретный инструмент.
|
||||||
|
- Новый поиск после изменения условий или повторного нажатия «Найти» возвращает на первую страницу; переход между страницами относится к одному полученному списку.
|
||||||
|
- Неизвестное значение выбранного условия не считается совпадением.
|
||||||
|
|
||||||
|
### Out of Scope / Non-Goals
|
||||||
|
|
||||||
|
- Просмотр всех акций, включая недоступные для покупки, и переключатель «Все акции».
|
||||||
|
- Фильтрация по цене, дивидендной доходности, капитализации и финансовым мультипликаторам.
|
||||||
|
- Проверка личной возможности совершить сделку, её исполнения или актуального состояния биржевой сессии.
|
||||||
|
- Изменение страниц облигаций и состава личного портфеля.
|
||||||
|
|
||||||
|
## Capabilities
|
||||||
|
|
||||||
|
### New Capabilities
|
||||||
|
|
||||||
|
- `specs/stock-screener/spec.md` — отбор и просмотр акций по параметрам каталога.
|
||||||
|
|
||||||
|
### Modified Capabilities
|
||||||
|
|
||||||
|
- Нет: существующее требование `specs/online-instrument-information/spec.md` об онлайн-получении сведений остаётся в силе.
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
|
||||||
|
Затронуты пользовательский раздел акций, получение каталога и представление результатов. Скринер зависит от доступности T-Invest; поиск и навигация по выпускам должны согласовываться с уже существующей страницей акции. Личные финансовые данные и портфель не становятся источником полного каталога для скринера.
|
||||||
|
|
||||||
|
## Success Criteria
|
||||||
|
|
||||||
|
- Пользователь без названия или тикера выбирает условия, нажимает «Найти» и получает полный относительно выбранных условий список доступных к покупке выпусков, а не только отфильтрованные первые результаты текстового поиска.
|
||||||
|
- Текстовое условие совместимо с фильтрами; неизвестные значения не создают ложных совпадений.
|
||||||
|
- Повторный поиск показывает результат нового получения каталога с отметкой времени и начинает выдачу с первой страницы; листание ранее полученного результата не даёт пропусков или дублей.
|
||||||
|
|
||||||
|
## Assumptions / Dependencies
|
||||||
|
|
||||||
|
- Каталог T-Invest предоставляет признаки доступности покупки и торговли через API, необходимые характеристики акций и время получения сведений. Отсутствие или недоступность онлайн-сведений не означает подтверждённого отсутствия акций.
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
# QA Analysis
|
||||||
|
|
||||||
|
## Verification Scope
|
||||||
|
|
||||||
|
### In Scope
|
||||||
|
|
||||||
|
Новая capability `stock-screener`: полный online-поиск доступных к покупке акций, шесть фильтров и необязательный текст, выпускная дедупликация, стабильные страницы и различение подтверждённой пустоты от ошибки. Проверка перехода по точному инструменту и совместимости старых адресов акций.
|
||||||
|
|
||||||
|
### Out of Scope
|
||||||
|
|
||||||
|
Финансовые показатели, исполнение заявки, личная возможность покупки, изменение облигаций, проверка цены или актуальности биржевой сессии.
|
||||||
|
|
||||||
|
## Coverage Matrix
|
||||||
|
|
||||||
|
| Requirement / Scenario в `specs/stock-screener/spec.md` | Проверка на уровне системы |
|
||||||
|
| --- | --- |
|
||||||
|
| «Скринер ищет среди доступных к покупке акций» / «Поиск без названия или тикера» | Отправить пустые условия; убедиться, что результат получен без текста и содержит выпуски из полного доступного каталога, включая кандидатов вне первых 30 совпадений старого поиска; показать время списка. |
|
||||||
|
| Тот же requirement / «Акция недоступна для покупки» | Сопоставить инструменты со всеми комбинациями двух флагов: в результат попадают только подтверждённые `true` + `true`. |
|
||||||
|
| «Условия скринера применяются совместно» / «Несколько условий без текста», «Текст совместно с характеристиками» | Выбрать по очереди и в сочетании все шесть условий, дополнить названием/тикером; проверить пересечение по полному каталогу до страниц. |
|
||||||
|
| Тот же requirement / «Неизвестный сектор», «Граница максимального лота» | Отсутствующее выбранное свойство исключает результат; лот, равный границе, включён, больший и неизвестный исключены. |
|
||||||
|
| «Результат представляет отдельные выпуски» / три сценария | Один ISIN в нескольких режимах даёт одну карточку; только подходящий режим даёт ссылку на него; разные ISIN и инструменты без ISIN не склеиваются. |
|
||||||
|
| «Страницы относятся к одному результату поиска» / «Переход к следующей странице» | Выдача более 30 выпусков: страницы имеют общий `asOf`, нет повторов/пропусков, порядок по тикеру и UID; каждая содержит не более 30. |
|
||||||
|
| Тот же requirement / «Повторный поиск», «Поля изменены, но поиск не запущен» | Черновые поля не подменяют выдачу; повторное «Найти» запускает новый поиск, первую страницу и новое время списка. |
|
||||||
|
| «Отсутствие совпадений отличается от недоступности поиска» / оба сценария | Успешный поиск без совпадений показывает пустое состояние; недоступный каталог — ошибку с возможностью повторить, без утверждения об отсутствии акций. |
|
||||||
|
|
||||||
|
## Cross-system Verification
|
||||||
|
|
||||||
|
Сопоставить обе HTTP-операции с [утверждённым target contract](system-analysis/contracts/stock-screener.openapi.yaml): обязательность полей, схема ошибок и статусы, авторизация, страницы одного `searchId`. Проверить отказ или неполноту источника, смену подключения между страницами, недоступный/чужой `searchId`, устаревший ответ прежнего поиска после нового; ни один случай не должен возвращать пустую подтверждённую страницу или пересчитывать старый результат. Проверить, что `Shares` запрашивается для полного каталога до отбора, а переход «Далее» не запускает новый поиск. Дублирования/перестановки на границе страниц не допускаются.
|
||||||
|
|
||||||
|
## Risk-based Priorities
|
||||||
|
|
||||||
|
1. Критично: ложная полнота при фильтрации только первых 30, неправильно подтверждённая доступность покупки, выдача чужого или старого результата подключения.
|
||||||
|
2. Высокий риск: дубли/пропуски выпуска между страницами, смешение атрибутов разных UID, неполный ответ как успешная пустота.
|
||||||
|
3. Средний риск: несоответствие текста и фильтров, различие времени снимка на страницах, сломанные ссылки на отдельную акцию и старый текстовый маршрут.
|
||||||
|
|
||||||
|
## Test Data and Environment Needs
|
||||||
|
|
||||||
|
Управляемый полный каталог со следующими группами: более 30 подходящих выпусков; одинаковый ISIN в разных режимах, разные ISIN одного эмитента и отсутствующий ISIN; значения каждого фильтра, пропуски полей, пограничный размер лота; комбинации двух флагов покупки и торговли. Два пользователя и два последовательных подключения одного пользователя позволяют проверить область доступа. Предусмотреть системный сценарий отказа или неполного ответа T-Invest и недоступного результата между страницами; токены и личные финансовые данные для этого не нужны.
|
||||||
|
|
||||||
|
## Manual / Exploratory Scope
|
||||||
|
|
||||||
|
Проверить поиск пустой формой и заполненной формой на широком и узком экране; состояние до поиска, загрузку, подтверждённую пустоту, ошибку и повтор. С клавиатуры пройти поля, запуск и навигацию страниц; убедиться, что активные условия и время результата различимы, ссылка ведёт к выбранной акции, а подпись «доступна к покупке» не выдаёт каталожный признак за обещание сделки.
|
||||||
|
|
||||||
|
## Automation Candidates
|
||||||
|
|
||||||
|
Black-box проверки HTTP-контракта и сценариев поиска/листинга/доступа; сквозной сценарий формы, фильтров, повторного запуска и страницы акции. Repository automation: **deferred until QA repo exists**. Этот artifact не определяет внутренние тесты frontend/backend.
|
||||||
|
|
||||||
|
## Observability Verification
|
||||||
|
|
||||||
|
На системной границе проверить отличимость успешного пустого поиска от отказа источника, фиксацию `asOf` и количества исходных/подходящих выпусков при успехе, регистрации недоступного результата и отказа чужому пользователю. Секрет подключения и полный текст запроса не должны попадать в наблюдаемые события.
|
||||||
|
|
||||||
|
## Regression Impact
|
||||||
|
|
||||||
|
Старый текстовый `GET /api/v1/stocks?query=...`, прямые страницы по тикеру/UID и поиск облигаций остаются действующими. Смена подключения по-прежнему отделяет личные данные; новая выдача акций не меняет портфель.
|
||||||
|
|
||||||
|
## Gaps / Blockers
|
||||||
|
|
||||||
|
Для управляемой системной проверки нужен способ получить предсказуемый каталог и вызвать отказ источника и потерю результата: тестируемость среды адресуется Engineering Design, без требования к внутренней реализации. Динамическая полнота опций фильтров не определена контрактом; если от неё зависит достижимость акций пользователем, вернуть вопрос в System Analysis до approval Design.
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
## Purpose
|
||||||
|
|
||||||
|
Определяет поиск доступных к покупке акций по характеристикам всего доступного каталога, просмотр совпавших выпусков и навигацию по результату поиска.
|
||||||
|
|
||||||
|
## ADDED Requirements
|
||||||
|
|
||||||
|
### Requirement: Скринер ищет среди доступных к покупке акций
|
||||||
|
Система SHALL по нажатию «Найти» искать по всему доступному на момент запроса каталогу акций, не ограничивая исходный набор первыми совпадениями текстового поиска. В результаты MUST включаться только акции, для которых в полученных сведениях подтверждены и доступность покупки, и возможность торговли через API. Скринер MUST показывать время получения списка и MUST NOT представлять эти признаки как гарантию, что конкретный пользователь сможет исполнить заявку прямо сейчас.
|
||||||
|
|
||||||
|
#### Scenario: Поиск без названия или тикера
|
||||||
|
- **WHEN** пользователь не ввёл текст и нажал «Найти»
|
||||||
|
- **THEN** система ищет по доступному каталогу и показывает выпуски, удовлетворяющие условиям и обоим признакам доступности
|
||||||
|
|
||||||
|
#### Scenario: Акция недоступна для покупки
|
||||||
|
- **WHEN** у акции отсутствует подтверждение доступности покупки или торговли через API
|
||||||
|
- **THEN** система не включает её в результаты скринера независимо от остальных совпавших условий
|
||||||
|
|
||||||
|
### Requirement: Условия скринера применяются совместно
|
||||||
|
Система SHALL позволять независимо задавать необязательные условия по сектору, типу акции, валюте расчётов, площадке, стране риска и максимальному количеству бумаг в лоте, а также необязательный текстовый запрос по названию или тикеру. После нажатия «Найти» система SHALL возвращать только акции, удовлетворяющие всем заданным условиям. Отсутствующее или неизвестное значение выбранного условия MUST NOT считаться совпадением.
|
||||||
|
|
||||||
|
#### Scenario: Несколько условий без текста
|
||||||
|
- **WHEN** пользователь выбрал сектор и валюту, оставил текст пустым и нажал «Найти»
|
||||||
|
- **THEN** в результате остаются только доступные к покупке акции выбранного сектора и валюты
|
||||||
|
|
||||||
|
#### Scenario: Текст совместно с характеристиками
|
||||||
|
- **WHEN** пользователь ввёл название или тикер, выбрал тип акции и нажал «Найти»
|
||||||
|
- **THEN** система отбирает совпадения по тексту и типу среди доступного каталога до разделения результата на страницы
|
||||||
|
|
||||||
|
#### Scenario: Неизвестный сектор
|
||||||
|
- **WHEN** пользователь выбрал сектор, а у акции сектор не указан
|
||||||
|
- **THEN** акция не считается совпадением
|
||||||
|
|
||||||
|
#### Scenario: Граница максимального лота
|
||||||
|
- **WHEN** задано максимальное количество бумаг в лоте
|
||||||
|
- **THEN** акции с известным размером лота не больше указанного включаются при соблюдении остальных условий, а акции с неизвестным или большим лотом исключаются
|
||||||
|
|
||||||
|
### Requirement: Результат представляет отдельные выпуски
|
||||||
|
Система SHALL показывать не более одной карточки для каждого выпуска с одинаковым ISIN; акции с разными ISIN SHALL оставаться отдельными результатами. При отсутствии ISIN отдельным результатом SHALL быть инструмент с собственным идентификатором. Представитель выпуска MUST выбираться среди инструментов, удовлетворяющих заданным условиям, а карточка MUST вести к выбранному инструменту.
|
||||||
|
|
||||||
|
#### Scenario: Несколько подходящих режимов одного выпуска
|
||||||
|
- **WHEN** несколько инструментов с одним ISIN удовлетворяют всем условиям
|
||||||
|
- **THEN** система показывает одну карточку выпуска со ссылкой на один из этих инструментов
|
||||||
|
|
||||||
|
#### Scenario: Один режим не соответствует условиям
|
||||||
|
- **WHEN** два инструмента имеют один ISIN, но только один удовлетворяет всем условиям
|
||||||
|
- **THEN** карточка выпуска ведёт к соответствующему условиям инструменту
|
||||||
|
|
||||||
|
#### Scenario: Разные выпуски одного эмитента
|
||||||
|
- **WHEN** обыкновенная и привилегированная акции имеют разные ISIN и обе удовлетворяют условиям
|
||||||
|
- **THEN** система показывает их как отдельные результаты
|
||||||
|
|
||||||
|
### Requirement: Страницы относятся к одному результату поиска
|
||||||
|
Система SHALL упорядочивать совпадения по тикеру, затем по идентификатору инструмента, и показывать не более 30 выпусков на странице. Переход между страницами одного поиска MUST сохранять состав и порядок полученного результата без пропусков и повторов. Изменение полей без нажатия «Найти» MUST NOT заменять текущий результат; повторное нажатие «Найти», в том числе без изменения условий, SHALL запускать новый поиск и возвращать к первой странице.
|
||||||
|
|
||||||
|
#### Scenario: Переход к следующей странице
|
||||||
|
- **WHEN** пользователь открывает следующую страницу полученного результата
|
||||||
|
- **THEN** он видит следующие до 30 выпусков в том же порядке и с тем же временем получения списка, без повторения выпусков предыдущей страницы
|
||||||
|
|
||||||
|
#### Scenario: Повторный поиск
|
||||||
|
- **WHEN** пользователь изменил условия или оставил их прежними и нажал «Найти» повторно
|
||||||
|
- **THEN** система получает новый результат с новым временем получения списка и показывает его первую страницу
|
||||||
|
|
||||||
|
#### Scenario: Поля изменены, но поиск не запущен
|
||||||
|
- **WHEN** пользователь изменяет условия и ещё не нажал «Найти»
|
||||||
|
- **THEN** ранее полученные страницы не подменяются результатом для новых условий
|
||||||
|
|
||||||
|
### Requirement: Отсутствие совпадений отличается от недоступности поиска
|
||||||
|
Система MUST показывать пустой результат только после успешного поиска без совпадений. Если получить сведения для поиска не удалось, система SHALL сообщать о недоступности поиска и MUST NOT выдавать отсутствие акций за подтверждённый результат.
|
||||||
|
|
||||||
|
#### Scenario: Успешный поиск без совпадений
|
||||||
|
- **WHEN** поиск завершился успешно, но ни одна акция не удовлетворяет условиям
|
||||||
|
- **THEN** система показывает, что подходящие акции не найдены
|
||||||
|
|
||||||
|
#### Scenario: Источник каталога недоступен
|
||||||
|
- **WHEN** поиск не может получить сведения о каталоге
|
||||||
|
- **THEN** система сообщает об ошибке получения результата, не утверждая, что подходящих акций нет
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
# System Analysis
|
||||||
|
|
||||||
|
## Requirement Coverage
|
||||||
|
|
||||||
|
| Требование delta spec | Межсистемная граница и поток |
|
||||||
|
| --- | --- |
|
||||||
|
| Скринер ищет среди доступных к покупке акций | Запуск поиска, полный список Shares, обязательные признаки и время получения: [поиск](sequences/stock-screener.mmd), [контракт](contracts/stock-screener.openapi.yaml). |
|
||||||
|
| Условия скринера применяются совместно | Необязательные параметры в запросе, отбор по всему полученному списку до разделения на страницы; неизвестное не совпадает. |
|
||||||
|
| Результат представляет отдельные выпуски | После отбора группировка по нормализованному ISIN, иначе по UID; карточка выбранного представителя с точным UID. |
|
||||||
|
| Страницы относятся к одному результату поиска | Создание нового результата при каждом запуске, чтение последующих страниц того же результата по непрозрачному идентификатору; не более 30 выпусков на страницу. |
|
||||||
|
| Отсутствие совпадений отличается от недоступности поиска | Успешный пустой набор имеет `200` и `items: []`; ошибка каталога или потеря результата возвращает ошибку, не пустую страницу. |
|
||||||
|
|
||||||
|
## Systems and Responsibilities
|
||||||
|
|
||||||
|
- Пользователь задаёт условия и запускает поиск в приложении.
|
||||||
|
- Frontend хранит черновик условий отдельно от применённого поиска, показывает карточки, время получения и управляет навигацией по страницам. Он не отбирает только первые найденные строки и не склеивает результаты разных поисков.
|
||||||
|
- Backend проверяет локальную сессию и действующее подключение T-Bank, получает полный список акций, применяет обязательные признаки и выбранные условия, выбирает один подходящий инструмент на выпуск, фиксирует результат и выдаёт страницы. Инструмент и личный портфель — разные данные.
|
||||||
|
- T-Invest поставляет список акций с характеристиками и признаками доступности. Это источник сведений для нового поиска, но не подтверждение доступа конкретного счёта к сделке.
|
||||||
|
|
||||||
|
## Interaction Flows
|
||||||
|
|
||||||
|
[stock-screener.mmd](sequences/stock-screener.mmd) описывает новый поиск без текста, переход к следующей странице того же результата, повторный поиск, неподтверждённую доступность инструмента, отказ внешнего источника и отсутствие сохранённого результата. Чтение карточки выбранной акции после перехода по ссылке относится к существующему маршруту инструмента.
|
||||||
|
|
||||||
|
## Contracts
|
||||||
|
|
||||||
|
- **Frontend → Backend**, HTTP JSON, новая пара операций `POST /api/v1/stocks/screener/search` и `GET /api/v1/stocks/screener/search/{searchId}`. Целевой контракт версии `/api/v1`: [stock-screener.openapi.yaml](contracts/stock-screener.openapi.yaml). Каждый POST — новый поиск даже при совпадающих параметрах; повтор того же POST не идемпотентен в смысле получения данных. GET следующей страницы не повторяет обращение к каталогу. Ответ содержит `items`, `asOf`, `searchId`, `page`, `hasNext`. `searchId` непрозрачен и принадлежит аутентифицированному пользователю и текущему подключению; `page` — номер страницы с единицы. Контракт не задаёт срок хранения результата и не позволяет подменить применённые условия в запросе страницы.
|
||||||
|
- **Backend → T-Invest**, существующий gRPC over TLS: `InstrumentsService.Shares(InstrumentsRequest)` с `instrument_status = INSTRUMENT_STATUS_ALL` возвращает список `Share`. Базовый список не должен заранее исключать выпуск, у которого оба необходимых признака подтверждены. Отбор по сектору, типу, валюте, площадке, стране риска и лоту выполняется после получения всего списка: этих параметров нет в `InstrumentsRequest`. Ожидаются `buy_available_flag`, `api_trade_available_flag`, `uid`, `isin`, `ticker`, `name`, `sector`, `share_type`, `currency`, `real_exchange`, `country_of_risk`, `lot`. `FindInstrument` с обязательной поисковой строкой не является источником полного скринера.
|
||||||
|
- Контракт старого `GET /api/v1/stocks?query=...` не меняется в этой версии. Его обязательный текст, ограничение и текущая форма ответа продолжают работать для существующих потребителей; новое представление не выводится из его первых 30 результатов.
|
||||||
|
|
||||||
|
## Data and State Semantics
|
||||||
|
|
||||||
|
Источник истины для нового поиска — полученный целиком ответ Shares, а не накопленные инструменты портфеля или прежние текстовые запросы. Backend фиксирует полный набор совпавших выпусков и время успешного получения каталога до выдачи первой страницы. Пустая выдача подтверждается только успешным получением списка. Результат поиска отделён от долговременного самостоятельного справочника инструментов; правила существующего `online-instrument-information` остаются в силе.
|
||||||
|
|
||||||
|
Признаки `buy_available_flag = true` и `api_trade_available_flag = true` обязательны вместе. Неизвестное либо неподтверждённое значение любого из них исключает инструмент. Эти признаки относятся к снимку каталога, а не к биржевой сессии, счёту пользователя или исполнению заявки.
|
||||||
|
|
||||||
|
Применённые условия сравниваются с данными конкретного инструмента: текст — с названием или тикером, выбранные категории — с соответствующими значениями, `maxLot` — с известным положительным размером лота не больше границы. Отсутствие выбранного свойства исключает инструмент; отсутствие незаданного свойства не мешает совпадению. Страна риска — код страны основного бизнеса, а не юрисдикция регистрации. «Площадка» означает `real_exchange`, а не секцию/режим `class_code` и не дилерский `instrument_exchange`.
|
||||||
|
|
||||||
|
После отбора инструменты с одинаковым нормализованным ISIN представляют один выпуск; пустой ISIN заменяется точным UID, а не общим пустым ключом. При нескольких подходящих инструментах одного выпуска выбор представителя должен быть детерминированным; он определяет ссылку и все сведения карточки, данные режимов не смешиваются. Затем выпуски упорядочиваются по тикеру и UID выбранного инструмента и делятся на страницы по 30. `asOf` и порядок неизменны для всех страниц одного `searchId`. Повторное нажатие «Найти» не использует старый результат как новый, даже если условия совпадают.
|
||||||
|
|
||||||
|
## Error and Recovery Semantics
|
||||||
|
|
||||||
|
- Неверные границы или формат условий / страницы: `400` с публичной ошибкой в стандартном профиле. Отсутствующая сессия: `401`. Отсутствующее/недействительное подключение T-Bank: соответствующая ошибка доступа подключения; секрет и upstream-исключение не раскрываются.
|
||||||
|
- Источник каталога недоступен, возвращает неполный или неинтерпретируемый список: ошибка запроса, не `items: []`. При новом поиске предыдущий успешно показанный результат не становится доказательством нового поиска. Пользователь может повторить запуск.
|
||||||
|
- Идентификатор результата больше не доступен или не принадлежит текущему пользователю/подключению: ошибка вместо пустой страницы или пересчёта по новому каталогу. Frontend предлагает выполнить новый поиск. Потеря результата в рамках действующего поиска — техническая деградация; время и данные прошлого результата не выдаются за новый.
|
||||||
|
- Нет утверждений о «прямо сейчас можно купить»: статус торгов и права конкретного счёта не проверяются. Недоступная цена/логотип не меняет состав подходящих выпусков.
|
||||||
|
|
||||||
|
## Observability Semantics
|
||||||
|
|
||||||
|
Существенны попытка нового поиска, успешное получение каталога (`asOf`, число исходных инструментов, подходящих выпусков), технический отказ источника, чтение страницы недоступного результата и отказ доступа к чужому результату. Секрет подключения, пользовательский токен и полный текст запроса не нужны для диагностики. Успешный пустой результат отличим от отказа источника.
|
||||||
|
|
||||||
|
## Security and NFR at System Boundaries
|
||||||
|
|
||||||
|
Обе HTTP-операции требуют локальной аутентификации; доступ к T-Invest выполняется только на backend с токеном текущего подключения. Идентификатор результата не является правом доступа и не переносит данные между пользователями или поколениями подключения. Полный каталог нужен до пагинации, чтобы 30 карточек не превратились в ограничение области поиска. Значение `asOf` относится к получению каталога, не к актуальности возможности купить позже. Количественная задержка/SLA для получения списка пока не установлена; не следует обещать мгновенную выдачу при сбое внешнего сервиса.
|
||||||
|
|
||||||
|
## Compatibility / Migration
|
||||||
|
|
||||||
|
Новый маршрут добавляется без изменения действующего текстового поиска и адресов отдельных акций. Frontend переходит на новый контракт для скринера; старый маршрут остаётся доступен его текущим потребителям. После реализации backend-generated OpenAPI должен быть семантически совместим с целевым контрактом в архивируемом change. Документ в этом change не становится вторым постоянным источником исполняемой схемы.
|
||||||
|
|
||||||
|
## Durable Documentation Candidates
|
||||||
|
|
||||||
|
- Новое назначение раздела акций, набор фильтров и смысл «доступна к покупке по данным каталога» в описании домена акций.
|
||||||
|
- Граница между online-каталогом, страницей акции и личным портфелем; снимок результата, время получения и стабильные страницы.
|
||||||
|
- Семантика новых HTTP-операций на уровне описания API без дублирования machine-readable OpenAPI. Окончательный выбор принадлежит `docs-impact`.
|
||||||
|
|
||||||
|
## Open Questions
|
||||||
|
|
||||||
|
Нет блокирующих вопросов по согласованным границам поведения. Выбор механизма хранения результата и допустимой внутренней длительности его доступности принадлежит Engineering Design; потеря результата остаётся ошибкой, а не молчаливой заменой поиска.
|
||||||
+177
@@ -0,0 +1,177 @@
|
|||||||
|
openapi: 3.0.3
|
||||||
|
info:
|
||||||
|
title: WealthLedger stock screener target contract
|
||||||
|
version: 1.0.0
|
||||||
|
description: Целевой контракт change; исполняемый HTTP-контракт после реализации генерирует backend.
|
||||||
|
paths:
|
||||||
|
/api/v1/stocks/screener/search:
|
||||||
|
post:
|
||||||
|
operationId: createStockScreenerSearch
|
||||||
|
summary: Получить новый результат скринера по полному каталогу акций
|
||||||
|
security:
|
||||||
|
- bearer: []
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/StockScreenerCriteria'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Первая страница нового результата, включая подтверждённый пустой список
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/StockScreenerPage'
|
||||||
|
'400':
|
||||||
|
$ref: '#/components/responses/Problem'
|
||||||
|
'401':
|
||||||
|
$ref: '#/components/responses/Problem'
|
||||||
|
'503':
|
||||||
|
$ref: '#/components/responses/Problem'
|
||||||
|
/api/v1/stocks/screener/search/{searchId}:
|
||||||
|
get:
|
||||||
|
operationId: readStockScreenerPage
|
||||||
|
summary: Прочитать страницу того же зафиксированного результата
|
||||||
|
security:
|
||||||
|
- bearer: []
|
||||||
|
parameters:
|
||||||
|
- name: searchId
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
description: Непрозрачный идентификатор результата; не является полномочием доступа.
|
||||||
|
- name: page
|
||||||
|
in: query
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
minimum: 1
|
||||||
|
description: Номер страницы с единицы. Условия поиска при чтении страницы не меняются.
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Страница зафиксированного результата с тем же временем получения каталога
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/StockScreenerPage'
|
||||||
|
'400':
|
||||||
|
$ref: '#/components/responses/Problem'
|
||||||
|
'401':
|
||||||
|
$ref: '#/components/responses/Problem'
|
||||||
|
'404':
|
||||||
|
$ref: '#/components/responses/Problem'
|
||||||
|
'503':
|
||||||
|
$ref: '#/components/responses/Problem'
|
||||||
|
components:
|
||||||
|
securitySchemes:
|
||||||
|
bearer:
|
||||||
|
type: http
|
||||||
|
scheme: bearer
|
||||||
|
responses:
|
||||||
|
Problem:
|
||||||
|
description: Ошибка в текущем публичном профиле WealthLedger; отсутствие результата не кодируется пустым списком.
|
||||||
|
content:
|
||||||
|
application/problem+json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Problem'
|
||||||
|
schemas:
|
||||||
|
Problem:
|
||||||
|
type: object
|
||||||
|
required: [status, code]
|
||||||
|
properties:
|
||||||
|
status:
|
||||||
|
type: integer
|
||||||
|
code:
|
||||||
|
type: string
|
||||||
|
title:
|
||||||
|
type: string
|
||||||
|
detail:
|
||||||
|
type: string
|
||||||
|
StockScreenerCriteria:
|
||||||
|
type: object
|
||||||
|
description: Все условия необязательны; отсутствие полей означает поиск по всем доступным к покупке акциям каталога.
|
||||||
|
additionalProperties: false
|
||||||
|
properties:
|
||||||
|
query:
|
||||||
|
type: string
|
||||||
|
maxLength: 80
|
||||||
|
description: Необязательная подстрока названия или тикера; пустая строка равнозначна отсутствию условия.
|
||||||
|
sector:
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
shareType:
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
currency:
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
realExchange:
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
description: Реальная площадка исполнения, не код торгового режима.
|
||||||
|
countryOfRisk:
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
description: Код страны основного бизнеса.
|
||||||
|
maxLot:
|
||||||
|
type: integer
|
||||||
|
minimum: 1
|
||||||
|
description: Включительная верхняя граница числа бумаг в лоте.
|
||||||
|
StockScreenerPage:
|
||||||
|
type: object
|
||||||
|
required: [searchId, asOf, page, hasNext, items]
|
||||||
|
properties:
|
||||||
|
searchId:
|
||||||
|
type: string
|
||||||
|
description: Идентификатор только этого поиска, ограничен пользователем и подключением.
|
||||||
|
asOf:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
description: Время получения полного списка акций от T-Invest, одинаковое на всех страницах результата.
|
||||||
|
page:
|
||||||
|
type: integer
|
||||||
|
minimum: 1
|
||||||
|
hasNext:
|
||||||
|
type: boolean
|
||||||
|
items:
|
||||||
|
type: array
|
||||||
|
maxItems: 30
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/StockScreenerItem'
|
||||||
|
StockScreenerItem:
|
||||||
|
type: object
|
||||||
|
required: [instrumentUid, name, ticker, isin, shareType, sector, currency, realExchange, countryOfRisk, lot]
|
||||||
|
properties:
|
||||||
|
instrumentUid:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
description: Точный выбранный инструмент для ссылки на страницу акции; нельзя подменять неоднозначным тикером.
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
ticker:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
isin:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
shareType:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
sector:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
currency:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
realExchange:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
countryOfRisk:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
lot:
|
||||||
|
type: integer
|
||||||
|
minimum: 1
|
||||||
|
nullable: true
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
sequenceDiagram
|
||||||
|
actor U as Пользователь
|
||||||
|
participant F as WealthLedger frontend
|
||||||
|
participant B as WealthLedger backend
|
||||||
|
participant T as T-Invest API
|
||||||
|
U->>F: Выбирает условия и нажимает «Найти»
|
||||||
|
F->>B: POST /api/v1/stocks/screener/search (условия)
|
||||||
|
B->>B: Проверка сессии и подключения
|
||||||
|
alt Нет доступа или неверные условия
|
||||||
|
B-->>F: 400 / 401 / ошибка подключения
|
||||||
|
F-->>U: Объяснение ошибки без нового результата
|
||||||
|
else Доступ подтверждён
|
||||||
|
B->>T: Shares (INSTRUMENT_STATUS_ALL)
|
||||||
|
alt Каталог получен полностью
|
||||||
|
T-->>B: Share[]
|
||||||
|
B->>B: Проверить доступность, фильтры, выпуски, сортировку и зафиксировать asOf
|
||||||
|
B-->>F: 200, items (до 30), searchId, asOf, hasNext
|
||||||
|
F-->>U: Первая страница или подтверждённый пустой результат
|
||||||
|
opt Есть следующая страница
|
||||||
|
U->>F: Далее
|
||||||
|
F->>B: GET /api/v1/stocks/screener/search/{searchId}?page=2
|
||||||
|
alt Результат доступен тому же пользователю и подключению
|
||||||
|
B-->>F: 200, следующие items, тот же asOf
|
||||||
|
F-->>U: Следующая страница без пропусков и повторов
|
||||||
|
else Результат недоступен или сменилось подключение
|
||||||
|
B-->>F: Ошибка без пересчёта по новому каталогу
|
||||||
|
F-->>U: Результат недоступен, предложить новый поиск
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else Каталог недоступен или неполон
|
||||||
|
T-->>B: Ошибка / неполные сведения
|
||||||
|
B-->>F: Ошибка поиска, не пустая выборка
|
||||||
|
F-->>U: Поиск недоступен, можно повторить
|
||||||
|
end
|
||||||
|
end
|
||||||
|
opt Условия изменены или нажат повторный поиск
|
||||||
|
U->>F: Нажимает «Найти»
|
||||||
|
F->>B: Новый POST с текущими условиями
|
||||||
|
Note over B,T: Новый список и новая первая страница без подмены прежнего searchId
|
||||||
|
end
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
# Tasks
|
||||||
|
|
||||||
|
## 1. Каталог и отбор
|
||||||
|
|
||||||
|
- [x] 1.1 [backend] Получать полный список акций T-Invest для каждого нового запуска скринера, сохраняя действующие границы доступа и обработки недоступности источника.
|
||||||
|
- **Covers:** `stock-screener` — `Скринер ищет среди доступных к покупке акций`; `stock-screener` — `Отсутствие совпадений отличается от недоступности поиска`.
|
||||||
|
- **Source:** `system-analysis/analysis.md` — Contracts, Error and Recovery Semantics; `design.md` — Decisions/backend.
|
||||||
|
- **Verification:** управляемый ответ Shares с подходящей акцией за пределами первых 30 текстовых совпадений и отказом источника; backend developer tests и repo-local verify.
|
||||||
|
- **Depends on:** none
|
||||||
|
|
||||||
|
- [x] 1.2 [backend] Применять обязательные признаки доступности и все необязательные условия к полному списку, выбирать только соответствующего условиям представителя каждого выпуска и упорядочивать результаты до разделения на страницы.
|
||||||
|
- **Covers:** `stock-screener` — `Скринер ищет среди доступных к покупке акций`; `stock-screener` — `Условия скринера применяются совместно`; `stock-screener` — `Результат представляет отдельные выпуски`.
|
||||||
|
- **Source:** `specs/stock-screener/spec.md` — соответствующие Requirements; `system-analysis/analysis.md` — Data and State Semantics; `design.md` — Decisions/backend.
|
||||||
|
- **Verification:** backend developer tests проверяют пересечение условий, неизвестное значение, включительную границу лота, одинаковый и разный ISIN, отсутствие ISIN и ссылку на точный выбранный UID.
|
||||||
|
- **Depends on:** 1.1
|
||||||
|
|
||||||
|
## 2. Зафиксированные страницы и публичный контракт
|
||||||
|
|
||||||
|
- [x] 2.1 [backend] Предоставить новый поиск и чтение страниц одного ограниченного по времени результата с привязкой к пользователю и поколению подключения, отличая пустой успешный поиск от ошибки и не пересчитывая старые страницы.
|
||||||
|
- **Covers:** `stock-screener` — `Страницы относятся к одному результату поиска`; `stock-screener` — `Отсутствие совпадений отличается от недоступности поиска`; `stock-screener` — `Результат представляет отдельные выпуски`.
|
||||||
|
- **Source:** `system-analysis/contracts/stock-screener.openapi.yaml`; `system-analysis/analysis.md` — Error and Recovery Semantics, Security and NFR at System Boundaries; `design.md` — Decisions/backend, Data / Storage Changes.
|
||||||
|
- **Verification:** backend HTTP и access checks: до 30 элементов, одинаковые `asOf` и порядок на следующих страницах, новый POST создаёт новый результат, чужой/истёкший `searchId` и смена подключения не выдают данные.
|
||||||
|
- **Depends on:** 1.2
|
||||||
|
|
||||||
|
- [x] 2.2 [backend] Опубликовать сгенерированный OpenAPI для операций скринера и подтвердить его семантическую совместимость с утверждённым target contract, сохранив старый текстовый поиск.
|
||||||
|
- **Covers:** `stock-screener` — `Условия скринера применяются совместно`; `stock-screener` — `Страницы относятся к одному результату поиска`; `stock-screener` — `Отсутствие совпадений отличается от недоступности поиска`.
|
||||||
|
- **Source:** `system-analysis/contracts/stock-screener.openapi.yaml`; `design.md` — Contract Implementation, Migration / Rollout / Rollback.
|
||||||
|
- **Verification:** `npm run openapi:generate`, `npm run test:openapi-contract`, сравнение двух маршрутов, запросов, ответов и ошибок с target contract; backend repo-local verify и regression старого GET.
|
||||||
|
- **Depends on:** 2.1
|
||||||
|
|
||||||
|
## 3. Пользовательский поиск
|
||||||
|
|
||||||
|
- [x] 3.1 [frontend] Подключить экран акций к опубликованному контракту: сохранять черновые условия отдельно от применённых, отправлять новый поиск только по кнопке и получать страницы по `searchId` без подмены результата поздним ответом.
|
||||||
|
- **Covers:** `stock-screener` — `Условия скринера применяются совместно`; `stock-screener` — `Страницы относятся к одному результату поиска`.
|
||||||
|
- **Source:** `design.md` — Decisions/frontend, Contract Implementation; `system-analysis/contracts/stock-screener.openapi.yaml`.
|
||||||
|
- **Verification:** frontend developer checks для повторного нажатия «Найти», чернового изменения полей, смены страницы и позднего ответа; генерация типов от предоставленного backend OpenAPI и frontend repo-local verify.
|
||||||
|
- **Depends on:** 2.2
|
||||||
|
|
||||||
|
- [x] 3.2 [frontend] Показать фильтры, каталожные карточки выбранных выпусков, время списка, страницы, подтверждённую пустоту и восстановление после недоступности поиска без обещания немедленной покупки.
|
||||||
|
- **Covers:** `stock-screener` — `Скринер ищет среди доступных к покупке акций`; `stock-screener` — `Результат представляет отдельные выпуски`; `stock-screener` — `Страницы относятся к одному результату поиска`; `stock-screener` — `Отсутствие совпадений отличается от недоступности поиска`.
|
||||||
|
- **Source:** `design.md` — UI/UX Package, Decisions/frontend; `qa-analysis.md` — Manual / Exploratory Scope.
|
||||||
|
- **Verification:** на широкой и узкой ширине, в том числе с клавиатуры, проверить запуск пустой формой, применение всех фильтров, точную UID-ссылку, состояния loading/empty/error и переход «Далее»; frontend repo-local verify.
|
||||||
|
- **Depends on:** 3.1
|
||||||
|
|
||||||
|
## 4. Итоговая документация
|
||||||
|
|
||||||
|
- [x] 4.1 [docs] Обновить существующее описание акций, интеграции T-Invest и HTTP API до реализованного поведения скринера, не копируя целевой OpenAPI и change-диаграмму в AS-IS.
|
||||||
|
- **Covers:** `stock-screener` — `Скринер ищет среди доступных к покупке акций`; `stock-screener` — `Страницы относятся к одному результату поиска`; `stock-screener` — `Отсутствие совпадений отличается от недоступности поиска`.
|
||||||
|
- **Source:** `docs-impact.md` — entries `docs/domains/instruments/stocks.md`, `docs/integrations/tbank.md`, `docs/architecture/http-api.md`.
|
||||||
|
- **Verification:** сверить опубликованные страницы с реализованной формой, ответами и backend-generated OpenAPI; docs repo-local Markdown/link checks и `git diff --check`.
|
||||||
|
- **Depends on:** 2.2, 3.2
|
||||||
|
|
||||||
|
- [x] 4.2 [docs] Обновить обзор продукта, существующий навигационный пункт акций и глоссарий с точным определением скринера без технических деталей хранения результата.
|
||||||
|
- **Covers:** `stock-screener` — `Скринер ищет среди доступных к покупке акций`.
|
||||||
|
- **Source:** `docs-impact.md` — entries `docs/overview.md`, `docs/README.md`, `docs/glossary.md`; Glossary / Navigation Impact.
|
||||||
|
- **Verification:** проверить ссылки, согласованность термина с разделом акций, docs repo-local Markdown/link checks и `git diff --check`.
|
||||||
|
- **Depends on:** 3.2
|
||||||
Reference in New Issue
Block a user