spec: finalize wealth-ledger SDD migration
This commit was merged in pull request #26.
This commit is contained in:
@@ -7,41 +7,53 @@
|
||||
## Requirements
|
||||
|
||||
### Requirement: User can retrieve upcoming events for an account
|
||||
Система MUST предоставлять аутентифицированному User read-only список известных будущих событий открытого поддерживаемого Brokerage Account, доступного через текущий T-Bank connection User, от момента запроса до 30 календарных дней после него включительно. Ответ MUST содержать только события coupon, dividend, maturity и offer, отсортированные по дате по возрастанию, и момент расчёта прогноза.
|
||||
Система MUST предоставлять аутентифицированному User read-only `GET /api/v1/brokerage-account-upcoming-events` для выбранного открытого поддерживаемого Brokerage Account либо существующего режима «Все счета» (`all`) в границах текущего T-Bank connection User. Параметры `from` и `to` MUST сохранять существующий контракт: необязательные строгие календарные даты `YYYY-MM-DD` с включительными границами в UTC. Без `from` начало MUST соответствовать текущей календарной дате UTC; без `to` конец MUST соответствовать дате `from` плюс 30 календарных дней. При `from` позже `to` система MUST возвращать существующую ошибку `INVALID_EVENTS_PERIOD`, не подменять период, не запускать загрузку другого периода и не возвращать его прогноз. Обычный GET MUST NOT запускать личную синхронизацию.
|
||||
|
||||
Список будущих событий является прогнозом по текущему наблюдаемому состоянию счёта и MUST NOT означать, что WealthLedger хранит долговременное право владения Brokerage Account независимо от T-Bank connection.
|
||||
Ответ MUST иметь `{ data, meta }`, где `meta` соответствует наблюдаемому состоянию resource по `tbank-data-sync`. Доступный `data` MUST содержать только события coupon, dividend, maturity и offer, отсортированные по дате по возрастанию, `calculatedAt`, а в режиме `all` -- принадлежность каждого события счёту. Прогноз MUST быть рассчитан по количеству бумаг полного Portfolio Snapshot на `calculatedAt`; сохранённый прогноз MUST сохранять свой исходный `calculatedAt` и рассчитанное от него включительное окно, даже когда запрошенное `from`/`to` отличается. При чтении сохранённого прогноза система MUST возвращать только события, чьи даты попадают во включительное пересечение исходного окна прогноза и запрошенного UTC-окна; пустое пересечение либо отсутствие событий в пересечении MUST возвращать успешный пустой список с теми же `calculatedAt`, `meta` и исходным окном. В режиме `all` доступ и полнота обязательных источников MUST проверяться для каждого входящего счёта; отсутствие вклада счёта MUST NOT подменяться полным результатом. Список будущих событий является прогнозом по наблюдаемому состоянию счёта и количеству бумаг на `calculatedAt` и MUST NOT означать долговременное право владения Brokerage Account независимо от T-Bank connection.
|
||||
|
||||
#### Scenario: Получение событий доступного счёта
|
||||
- **WHEN** аутентифицированный User запрашивает будущие события открытого поддерживаемого Brokerage Account, доступного через текущий T-Bank connection
|
||||
- **THEN** система возвращает известные события на следующие 30 календарных дней и момент, на который был выполнен расчёт
|
||||
- **WHEN** аутентифицированный User запрашивает события доступного счёта с последним полным прогнозом
|
||||
- **THEN** система возвращает из сохранённого прогноза события в запрошенном включительном UTC-окне, его исходные `calculatedAt` и рассчитанное окно без обращения к T-Bank
|
||||
|
||||
#### Scenario: Запрошенное окно не пересекается с сохранённым прогнозом
|
||||
- **WHEN** запрошенный включительный UTC-окно не пересекается с исходным окном последнего полного прогноза
|
||||
- **THEN** система возвращает успешный пустой `data` с исходными `calculatedAt`, окном и `meta`
|
||||
|
||||
#### Scenario: Отсутствие событий в периоде
|
||||
- **WHEN** для счёта нет известных событий в ближайшие 30 календарных дней
|
||||
- **THEN** система возвращает успешный пустой список и момент расчёта
|
||||
- **WHEN** пересечение запрошенного и исходного окна прогноза не содержит событий
|
||||
- **THEN** система возвращает успешный пустой список, момент расчёта, исходное окно и `meta`
|
||||
|
||||
#### Scenario: Некорректный период
|
||||
- **WHEN** User передаёт `from` позже `to`
|
||||
- **THEN** система возвращает существующую ошибку `INVALID_EVENTS_PERIOD` без прогноза и без запуска личной синхронизации
|
||||
|
||||
#### Scenario: Прогноз всех счетов
|
||||
- **WHEN** User запрашивает режим `all` и для каждого входящего счёта доступен полный прогноз
|
||||
- **THEN** система возвращает события всех счетов в запрошенном окне с принадлежностью каждого события счёту
|
||||
|
||||
#### Scenario: Отклонение недоступного счёта
|
||||
- **WHEN** аутентифицированный User запрашивает события счёта, который не является открытым поддерживаемым счётом, доступным через его T-Bank connection
|
||||
- **WHEN** User запрашивает события счёта, недоступного через его текущий T-Bank connection
|
||||
- **THEN** система возвращает существующий ответ о недоступности счёта и не раскрывает события
|
||||
|
||||
### Requirement: Forecasts preserve event meaning and uncertainty
|
||||
Система MUST передавать для каждого события его календарную дату, тип, доступные идентификаторы и название Instrument, а также прогнозную сумму до налогов как Money, если она известна. Прогнозная сумма MUST рассчитываться по текущему количеству бумаг на момент запроса. Если денежные условия события неизвестны или неприменимы, прогнозная сумма MUST быть `null`, а не нулём и не парой разрозненных полей. Дивиденд MUST обозначать переданную дату как дату реестра, а offer MUST передаваться без вымышленной суммы.
|
||||
Система MUST передавать для каждого события его календарную дату, тип, доступные идентификаторы и название Instrument, а также прогнозную сумму до налогов как Money, если она известна. Прогнозная сумма MUST рассчитываться по количеству бумаг полного Portfolio Snapshot на `calculatedAt`, использованного для сохранённого прогноза, а не по количеству бумаг на момент HTTP-запроса. Сохранённый прогноз MUST сохранять свой `calculatedAt` и эту исходную зависимость, в том числе после неуспешного обновления, и MUST NOT приписываться более новому Portfolio Snapshot. Если денежные условия события неизвестны или неприменимы, прогнозная сумма MUST быть `null`, а не нулём и не парой разрозненных полей. Дивиденд MUST обозначать переданную дату как дату реестра, а offer MUST передаваться без вымышленной суммы.
|
||||
|
||||
Instrument в Future Cash Event MUST иметь тот же доменный смысл, что и в Portfolio: это финансовый инструмент, к которому относится событие, даже если событие не является Portfolio Position.
|
||||
|
||||
#### Scenario: Прогноз купона по текущей позиции
|
||||
- **WHEN** поддерживаемая облигация в текущей Portfolio Position имеет известный купон в пределах периода
|
||||
- **THEN** система возвращает событие coupon с прогнозной суммой до налогов как Money в исходной валюте, рассчитанной по текущему количеству облигаций
|
||||
- **WHEN** поддерживаемая облигация в Portfolio Snapshot на `calculatedAt` имеет известный купон в пределах исходного окна прогноза
|
||||
- **THEN** система возвращает сохранённое событие coupon с прогнозной суммой до налогов как Money в исходной валюте, рассчитанной по количеству облигаций этого Snapshot
|
||||
|
||||
#### Scenario: Прогноз дивиденда
|
||||
- **WHEN** поддерживаемая акция в текущей Portfolio Position имеет объявленный дивиденд с датой фиксации реестра в пределах периода
|
||||
- **THEN** система возвращает событие dividend с датой, обозначенной как дата реестра (`record_date`), и прогнозной суммой до налогов как Money, равной `dividend_net` на одну бумагу, умноженной на текущее количество акций
|
||||
- **WHEN** поддерживаемая акция в Portfolio Snapshot на `calculatedAt` имеет объявленный дивиденд с датой фиксации реестра в пределах исходного окна прогноза
|
||||
- **THEN** система возвращает сохранённое событие dividend с датой, обозначенной как дата реестра (`record_date`), и прогнозной суммой до налогов как Money, равной `dividend_net` на одну бумагу, умноженной на количество акций этого Snapshot
|
||||
|
||||
#### Scenario: Отменённый дивиденд не попадает в прогноз
|
||||
- **WHEN** запись дивиденда T-Bank имеет тип выплаты `Cancelled`
|
||||
- **THEN** система не добавляет такое событие в результат
|
||||
- **THEN** система не добавляет такое событие в сохранённый прогноз
|
||||
|
||||
#### Scenario: Отображение оферты без суммы
|
||||
- **WHEN** для поддерживаемой облигации известна оферта в пределах периода, но её денежные условия недоступны
|
||||
- **WHEN** для поддерживаемой облигации известна оферта в пределах исходного окна прогноза, но её денежные условия недоступны
|
||||
- **THEN** система возвращает событие offer с прогнозной суммой `null`
|
||||
|
||||
#### Scenario: Неподдерживаемое частичное погашение
|
||||
@@ -49,42 +61,66 @@ Instrument в Future Cash Event MUST иметь тот же доменный с
|
||||
- **THEN** система НЕ создаёт синтетическое событие частичного погашения
|
||||
|
||||
### Requirement: Upcoming events use complete source results
|
||||
Система MUST завершать запрос ошибкой, если необходимые для прогноза данные брокера или MOEX недоступны, и MUST NOT возвращать частичный список как полный результат. Система MUST пропускать инструменты, для которых источник не предоставляет поддерживаемые события, не создавая строки «нет данных».
|
||||
Система MUST публиковать прогноз только после полного получения необходимых данных брокера и MOEX для полного Portfolio Snapshot, по количеству бумаг которого он рассчитан. Неуспешное обновление MUST NOT заменять последний полный прогноз, его `calculatedAt` или окно событий частичным списком. При неуспешном обновлении и существующем последнем полном прогнозе система MUST возвращать отфильтрованную для запрошенного окна часть этого прогноза с его исходными `calculatedAt`, окном и `meta`, отдельно обозначающим неуспех последней попытки, даже если результат фильтрации пуст. При отсутствии последнего полного прогноза система MUST возвращать `data: null` и в `meta` обозначать выполнение первой загрузки либо её неуспех без частичного списка. Система MUST пропускать инструменты, для которых источник не предоставляет поддерживаемые события, не создавая строки «нет данных».
|
||||
|
||||
#### Scenario: Недоступность источника
|
||||
- **WHEN** необходимый источник данных брокера или MOEX временно недоступен
|
||||
- **THEN** система возвращает ошибку запроса будущих событий без частичного списка
|
||||
- **WHEN** необходимый источник данных брокера или MOEX временно недоступен во время часового обновления
|
||||
- **THEN** система не публикует частичный список как полный прогноз
|
||||
|
||||
#### Scenario: Ошибка обновления при сохранённом прогнозе
|
||||
- **WHEN** часовой обход не завершает обновление необходимых источников, а последний полный прогноз существует
|
||||
- **THEN** система возвращает его события только в запрошенном окне, исходные `calculatedAt` и окно, а `meta` отдельно обозначает ошибку обновления
|
||||
|
||||
#### Scenario: Ошибка обновления при сохранённом прогнозе вне запрошенного окна
|
||||
- **WHEN** часовой обход не завершает обновление, последний полный прогноз существует, но его окно не пересекается с запрошенным
|
||||
- **THEN** система возвращает успешный пустой `data` с исходными `calculatedAt`, окном и `meta`, обозначающим ошибку обновления
|
||||
|
||||
#### Scenario: Инструмент без поддерживаемых данных
|
||||
- **WHEN** Portfolio содержит инструмент, для которого источник не предоставляет поддерживаемые будущие события
|
||||
- **THEN** система не добавляет для него синтетическую строку в результат
|
||||
|
||||
#### Scenario: MOEX отвечает без запрошенного блока
|
||||
- **WHEN** MOEX отвечает успешно, но не содержит запрошенного блока данных (например, дивидендов)
|
||||
- **THEN** система трактует ответ как отсутствие данных по инструменту и не завершает запрос ошибкой
|
||||
- **WHEN** MOEX отвечает успешно, но не содержит запрошенного блока данных
|
||||
- **THEN** система трактует ответ как отсутствие данных по инструменту и не завершает обновление ошибкой
|
||||
|
||||
### Requirement: Account page presents an upcoming events list
|
||||
Страница Brokerage Account MUST предоставлять действие перехода на отдельную страницу «Будущие события». Эта страница MUST показывать все полученные события компактным списком без фильтров, пагинации и суммарного итога, с датой, типом, инструментом и прогнозной суммой в исходной валюте, если она известна. Страница MUST объяснять, что прогноз основан на текущей позиции, указан до налогов и может быть неполным из-за доступности данных источника.
|
||||
Страница Brokerage Account MUST предоставлять read-only действие перехода на отдельную страницу «Будущие события». Эта страница MUST показывать события сохранённого полного прогноза компактным списком без фильтров, пагинации, суммарного итога или действия ручной синхронизации, с датой, типом, инструментом и прогнозной суммой в исходной валюте, если она известна. Страница MUST показывать `calculatedAt`, исходное окно прогноза и наблюдаемые freshness, полноту, время последнего успешного прогноза, последнюю часовую попытку и доступность из `meta` понятным текстом или иконкой с доступным именем, а не только цветом. Она MUST объяснять, что прогноз рассчитан по количеству бумаг на `calculatedAt`, а показанные события дополнительно ограничены запрошенным включительным UTC-окном; прогноз указан до налогов и может быть неполным из-за доступности данных источника. Открытие страницы и повтор HTTP-чтения MUST NOT запускать личную синхронизацию.
|
||||
|
||||
#### Scenario: Просмотр ближайших событий
|
||||
- **WHEN** User открывает «Будущие события» из обзора Brokerage Account
|
||||
- **THEN** он видит отсортированный список событий ближайших 30 дней, где offer обозначен без суммы
|
||||
- **THEN** он видит отсортированный read-only список событий сохранённого 30-календарного окна и `calculatedAt`, где offer обозначен без суммы
|
||||
|
||||
#### Scenario: Пустое состояние страницы
|
||||
- **WHEN** запрос будущих событий успешно возвращает пустой список
|
||||
- **THEN** страница сообщает, что на ближайшие 30 дней событий не найдено, и объясняет ограничения покрытия источника
|
||||
- **THEN** страница сообщает, что в сохранённом окне прогноза событий не найдено, показывает `calculatedAt` и объясняет ограничения покрытия источника
|
||||
|
||||
#### Scenario: Первая загрузка либо ошибка без сохранённого прогноза
|
||||
- **WHEN** запрос будущих событий возвращает `data: null`, а `meta` обозначает выполнение первой загрузки либо её неуспех
|
||||
- **THEN** страница показывает доступное состояние загрузки либо ошибки без частичного списка и предоставляет при ошибке действие повторить только HTTP-чтение
|
||||
|
||||
#### Scenario: Ошибка страницы
|
||||
- **WHEN** запрос будущих событий завершается ошибкой
|
||||
- **THEN** страница не показывает частичный или устаревший список и предоставляет действие повторить запрос
|
||||
- **THEN** страница не показывает частичный список и предоставляет действие повторить только HTTP-чтение
|
||||
|
||||
#### Scenario: Ошибка обновления при сохранённом прогнозе на странице
|
||||
- **WHEN** запрос будущих событий возвращает последний полный прогноз вместе с ошибкой его обновления
|
||||
- **THEN** страница показывает этот прогноз с его исходными `calculatedAt` и окном, отдельно сообщает об ошибке обновления и предоставляет действие повторить только HTTP-чтение
|
||||
|
||||
### Requirement: Account overview includes upcoming events
|
||||
Обзор Brokerage Account MUST показывать не более пяти ближайших Future Cash Event и действие перехода на полную страницу «Будущие события». Неуспешный или пустой запрос этого блока MUST оставаться локальным состоянием блока и не заменять успешно отображённый Portfolio.
|
||||
Обзор Brokerage Account MUST показывать не более пяти ближайших Future Cash Event сохранённого полного прогноза в применённом запрошенном окне, его `calculatedAt`, исходное окно, наблюдаемую freshness, полноту, последнюю часовую попытку и доступность из `meta`, а также действие перехода на read-only полную страницу «Будущие события». Статус MUST быть понятен текстом или иконкой с доступным именем, а не только цветом. Неуспешное обновление при сохранённом прогнозе MUST оставаться локальным состоянием блока, сохранять показанные события и их исходное окно и MUST NOT заменять успешно отображённый Portfolio. Если полного прогноза нет, неуспешное обновление MUST оставаться локальным состоянием блока без частичного списка. Обзор MUST NOT предоставлять действие ручной синхронизации; его повтор MUST выполнять только HTTP-чтение.
|
||||
|
||||
#### Scenario: Отображение ближайших событий
|
||||
- **WHEN** обзор счёта успешно загружен и календарь содержит будущие события
|
||||
- **THEN** обзор показывает первые пять событий в порядке даты и действие перехода к полной странице
|
||||
- **WHEN** обзор счёта успешно загружен и сохранённый прогноз содержит будущие события
|
||||
- **THEN** обзор показывает первые пять событий в порядке даты, `calculatedAt` и действие перехода к полной странице
|
||||
|
||||
#### Scenario: Ближайшие события недоступны без сохранённого прогноза
|
||||
- **WHEN** обзор счёта успешно загружен, а события возвращают `data: null` и `meta` обозначает выполнение первой загрузки либо её неуспех
|
||||
- **THEN** Portfolio остаётся доступным, а блок будущих событий отображает собственное доступное состояние загрузки либо ошибки без частичного списка
|
||||
|
||||
#### Scenario: Ближайшие события недоступны
|
||||
- **WHEN** обзор счёта успешно загружен, но запрос будущих событий завершается ошибкой
|
||||
- **THEN** Portfolio остаётся доступным, а блок будущих событий отображает собственную ошибку
|
||||
|
||||
#### Scenario: Неуспешное обновление не скрывает сохранённый прогноз
|
||||
- **WHEN** обзор счёта получает сохранённый полный прогноз вместе с ошибкой его обновления
|
||||
- **THEN** Portfolio и события сохранённого окна остаются доступными, а блок отдельно обозначает ошибку и предлагает повторить только HTTP-чтение
|
||||
|
||||
Reference in New Issue
Block a user