Files
specs/openspec/specs/brokerage-account-upcoming-events/spec.md
T
codex-bot a2d2df3b5a
Specs security / security (push) Successful in 6s
Specs verify / verify (push) Successful in 7s
spec: archive group-account-rows-by-instrument and sync canonical specs (WealthLedger/main#47) (#65)
2026-10-30 07:35:30 +03:00

31 KiB
Raw Blame History

Brokerage Account Upcoming Events Specification

Purpose

Позволяет пользователю просматривать известные будущие денежные и связанные корпоративные события одного брокерского счёта на ближайший месяц.

Requirements

Requirement: User can retrieve upcoming events for an account

Система 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 иметь { 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 запрашивает события доступного счёта с последним полным прогнозом
  • THEN система возвращает из сохранённого прогноза события в запрошенном включительном UTC-окне, его исходные calculatedAt и рассчитанное окно без обращения к T-Bank

Scenario: Запрошенное окно не пересекается с сохранённым прогнозом

  • WHEN запрошенный включительный UTC-окно не пересекается с исходным окном последнего полного прогноза
  • THEN система возвращает успешный пустой data с исходными calculatedAt, окном и meta

Scenario: Отсутствие событий в периоде

  • WHEN пересечение запрошенного и исходного окна прогноза не содержит событий
  • THEN система возвращает успешный пустой список, момент расчёта, исходное окно и meta

Scenario: Некорректный период

  • WHEN User передаёт from позже to
  • THEN система возвращает существующую ошибку INVALID_EVENTS_PERIOD без прогноза и без запуска личной синхронизации

Scenario: Прогноз всех счетов

  • WHEN User запрашивает режим all и для каждого входящего счёта доступен полный прогноз
  • THEN система возвращает события всех счетов в запрошенном окне с принадлежностью каждого события счёту

Scenario: Отклонение недоступного счёта

  • WHEN User запрашивает события счёта, недоступного через его текущий T-Bank connection
  • THEN система возвращает существующий ответ о недоступности счёта и не раскрывает события

Requirement: Forecasts preserve event meaning and uncertainty

Система 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 Snapshot на calculatedAt имеет известный купон в пределах исходного окна прогноза
  • THEN система возвращает сохранённое событие coupon с прогнозной суммой до налогов как Money в исходной валюте, рассчитанной по количеству облигаций этого Snapshot

Scenario: Прогноз дивиденда

  • WHEN поддерживаемая акция в Portfolio Snapshot на calculatedAt имеет объявленный дивиденд с датой фиксации реестра в пределах исходного окна прогноза
  • THEN система возвращает сохранённое событие dividend с датой, обозначенной как дата реестра (record_date), и прогнозной суммой до налогов как Money, равной dividend_net на одну бумагу, умноженной на количество акций этого Snapshot

Scenario: Отменённый дивиденд не попадает в прогноз

  • WHEN запись дивиденда T-Bank имеет тип выплаты Cancelled
  • THEN система не добавляет такое событие в сохранённый прогноз

Scenario: Отображение оферты без суммы

  • WHEN для поддерживаемой облигации известна оферта в пределах исходного окна прогноза, но её денежные условия недоступны
  • THEN система возвращает событие offer с прогнозной суммой null

Scenario: Неподдерживаемое частичное погашение

  • WHEN источник не предоставляет расписание будущих частичных погашений или амортизации облигации
  • THEN система НЕ создаёт синтетическое событие частичного погашения

Requirement: Upcoming events use complete source results

Система MUST публиковать прогноз только после полного получения необходимых данных брокера и MOEX для полного Portfolio Snapshot, по количеству бумаг которого он рассчитан. Неуспешное обновление MUST NOT заменять последний полный прогноз, его calculatedAt или окно событий частичным списком. При неуспешном обновлении и существующем последнем полном прогнозе система MUST возвращать отфильтрованную для запрошенного окна часть этого прогноза с его исходными calculatedAt, окном и meta, отдельно обозначающим неуспех последней попытки, даже если результат фильтрации пуст. При отсутствии последнего полного прогноза система MUST возвращать data: null и в meta обозначать выполнение первой загрузки либо её неуспех без частичного списка. Система MUST пропускать инструменты, для которых источник не предоставляет поддерживаемые события, не создавая строки «нет данных».

Scenario: Недоступность источника

  • WHEN необходимый источник данных брокера или MOEX временно недоступен во время часового обновления
  • THEN система не публикует частичный список как полный прогноз

Scenario: Ошибка обновления при сохранённом прогнозе

  • WHEN часовой обход не завершает обновление необходимых источников, а последний полный прогноз существует
  • THEN система возвращает его события только в запрошенном окне, исходные calculatedAt и окно, а meta отдельно обозначает ошибку обновления

Scenario: Ошибка обновления при сохранённом прогнозе вне запрошенного окна

  • WHEN часовой обход не завершает обновление, последний полный прогноз существует, но его окно не пересекается с запрошенным
  • THEN система возвращает успешный пустой data с исходными calculatedAt, окном и meta, обозначающим ошибку обновления

Scenario: Инструмент без поддерживаемых данных

  • WHEN Portfolio содержит инструмент, для которого источник не предоставляет поддерживаемые будущие события
  • THEN система не добавляет для него синтетическую строку в результат

Scenario: MOEX отвечает без запрошенного блока

  • WHEN MOEX отвечает успешно, но не содержит запрошенного блока данных
  • THEN система трактует ответ как отсутствие данных по инструменту и не завершает обновление ошибкой

Requirement: Account page presents an upcoming events list

Страница Brokerage Account MUST предоставлять действие перехода на отдельную страницу «Будущие события». Эта страница MUST показывать все полученные события компактным списком без фильтров, пагинации и суммарного итога списка, с датой, типом, инструментом и прогнозной суммой в исходной валюте, если она известна. Прогнозный итог визуальной группы режима «Все счета» не является суммарным итогом списка. Страница MUST объяснять, что прогноз основан на позиции счёта, указан до налогов и может быть неполным из-за доступности данных источника. Это предметное ограничение прогноза MUST NOT превращаться в отчёт о технической синхронизации.

При временном сбое обновления страница MUST сохранять ранее доступный результат той же выборки с его исходными calculatedAt и окном, без индикации собственного чтения и ручного повтора. Состояние источника из meta, включая неуспех последней часовой попытки и неполноту, MUST представляться по frontend-data-resilience. При отсутствии результата SHALL показываться первичное ожидание либо нейтральная недоступность с автоматическим восстановлением по frontend-data-resilience. Подтверждённая потеря доступа MUST скрывать личные данные независимо от наличия сохранённого результата.

Scenario: Просмотр ближайших событий

  • WHEN User открывает «Будущие события» из обзора Brokerage Account
  • THEN он видит отсортированный список событий ближайших 30 дней, где offer обозначен без суммы

Scenario: Пустое состояние страницы

  • WHEN запрос будущих событий успешно возвращает полный пустой список
  • THEN страница сообщает, что на ближайшие 30 дней событий не найдено, и объясняет предметные ограничения покрытия источника

Scenario: Ошибка страницы

  • WHEN запрос будущих событий завершается временной ошибкой
  • THEN страница сохраняет доступный результат той же выборки либо показывает нейтральную недоступность без ручного повтора, восстанавливая чтение автоматически и не выдавая частичные данные за полный прогноз

Scenario: Прогнозный дивиденд с неизвестной суммой

  • WHEN прогноз содержит дивиденд с известной датой реестра, но без суммы
  • THEN его карточка обозначает дату как дату реестра и сумму как неизвестную, не показывает нулевую или полученную выплату

Scenario: Ошибка обновления при сохранённом прогнозе на странице

  • WHEN запрос будущих событий возвращает последний полный прогноз вместе с ошибкой его обновления
  • THEN страница показывает этот прогноз с его исходными calculatedAt и окном и отдельно обозначает неуспех обновления источника, не предлагая ручной повтор

Scenario: Первая загрузка либо ошибка без сохранённого прогноза

  • WHEN запрос будущих событий возвращает data: null, а meta обозначает выполнение первой загрузки либо её неуспех
  • THEN страница показывает доступное состояние первичного ожидания либо нейтральной недоступности без частичного списка и восстанавливает чтение автоматически

Requirement: Account overview includes upcoming events

Обзор Brokerage Account MUST показывать не более пяти ближайших Future Cash Event и действие перехода на полную страницу «Будущие события». Неуспешный или пустой запрос этого блока MUST оставаться локальным состоянием блока и не заменять успешно отображённый Portfolio. При временном сбое обновления блок MUST сохранять доступные события сохранённого окна без индикации собственного чтения; при отсутствии результата SHALL показываться нейтральная недоступность после первичного ожидания. Состояние источника из meta MUST представляться по frontend-data-resilience. Восстановление MUST выполняться автоматически без ручного повтора и без подмены неизвестного результата подтверждённой пустотой.

Scenario: Отображение ближайших событий

  • WHEN обзор счёта успешно загружен и календарь содержит будущие события
  • THEN обзор показывает первые пять событий в порядке даты и действие перехода к полной странице

Scenario: Ближайшие события недоступны

  • WHEN обзор счёта успешно загружен, но запрос будущих событий завершается временной ошибкой
  • THEN Portfolio остаётся доступным, а блок сохраняет прежние события либо показывает нейтральную недоступность без технической ошибки и ручного повтора

Scenario: Ближайшие события недоступны без сохранённого прогноза

  • WHEN обзор счёта успешно загружен, а события возвращают data: null и meta обозначает выполнение первой загрузки либо её неуспех
  • THEN Portfolio остаётся доступным, а блок будущих событий отображает собственное доступное состояние первичного ожидания либо нейтральной недоступности без частичного списка

Scenario: Неуспешное обновление не скрывает сохранённый прогноз

  • WHEN обзор счёта получает сохранённый полный прогноз вместе с ошибкой его обновления
  • THEN Portfolio и события сохранённого окна остаются доступными, а блок отдельно обозначает неуспех обновления источника, не предлагая ручной повтор

Requirement: User can filter upcoming events by a forecast date range

The events page MUST retain its URL-backed date-range filter. When the URL has no from or to parameters, the applied range MUST include the current date and the following six calendar dates. The page MUST allow selecting today and future dates in its date picker and MUST prevent selecting dates before today.

The date-picker restriction MUST NOT invalidate or rewrite a valid past date range supplied directly in the URL. A malformed date, impossible calendar date, or reversed range MUST be normalized to the default range, removing invalid from and to parameters from the URL. Applying and resetting the filter MUST continue to use the existing URL-backed period behavior.

Scenario: Default range starts today and spans seven calendar dates

  • WHEN the user opens the events page without from or to parameters
  • THEN the applied range starts on the current calendar date
  • AND the inclusive end date is six calendar dates after the start date

Scenario: Future dates can be selected and past dates cannot

  • WHEN the user opens the date picker
  • THEN today and later dates are selectable
  • AND dates before today are not selectable

Scenario: Valid past URL range is preserved

  • WHEN the user opens the events page with valid from and to dates in the past and from is not after to
  • THEN the page applies that URL range without clamping or replacing it

Scenario: Invalid URL range falls back to the default

  • WHEN the URL contains a malformed date, an impossible calendar date, or a range whose from date is after its to date
  • THEN the page applies the default seven-calendar-date range
  • AND invalid from and to parameters are removed from the URL

Requirement: All-accounts upcoming events group same-day events of one instrument

В режиме «Все счета» страница «Будущие события» и ближайшие события обзора MUST объединять в визуальную группу два и более показанных Future Cash Event с одинаковыми датой и видом даты (как на экране), identity Instrument и типом события, даже если они на одном счёте. События без Instrument или без пары MUST показываться как раньше. В режиме одного Brokerage Account группировка MUST NOT применяться.

Scenario: Прогноз купонов одной облигации на двух счетах

  • WHEN в режиме «Все счета» на одну дату ожидается купон по облигации на брокерском счёте 200 RUB и купон по той же облигации на ИИС 100 RUB
  • THEN они показаны одной группой с облигацией, типом «Купон» и прогнозным итогом 300 RUB, а под заголовком видны строки «брокерский счёт 200 RUB» и «ИИС 100 RUB»

Scenario: Разные типы или даты не объединяются

  • WHEN в режиме «Все счета» по одной облигации ожидаются купон и погашение на одну дату либо купоны на разные даты
  • THEN ни одна группа не содержит события разных типов или разных дат

Scenario: Событие без Instrument

  • WHEN в режиме «Все счета» на одну дату показаны два события одного типа без identity Instrument
  • THEN они показаны отдельными строками без группы

Scenario: Режим одного счёта не меняется

  • WHEN User открывает «Будущие события» одного Brokerage Account
  • THEN события показаны отдельными строками без группы, как до изменения

Requirement: All-accounts upcoming event group shows each event and a forecast total

Заголовок группы событий MUST показывать дату (для дивиденда — как дату реестра, если она такая), Instrument, тип и прогнозный итог. Под ним MUST всегда быть видна, без раскрытия, отдельная строка каждого события со счётом и прогнозной суммой (или пометкой о неизвестной сумме). Итог SHALL быть точной суммой со знаком по каждой валюте без конвертации и округления. Итог MUST NOT показываться, если у события группы нет суммы или полнота прогноза не подтверждена.

Scenario: Событие с неизвестной суммой в группе

  • WHEN в режиме «Все счета» группа содержит дивиденд на одном счёте с известной суммой и дивиденд по той же бумаге на ту же дату на другом счёте без суммы
  • THEN заголовок группы не показывает итог, а строка второго счёта обозначает сумму как неизвестную

Scenario: Оферта без сумм

  • WHEN в режиме «Все счета» на одну дату ожидается оферта по одной облигации на двух счетах
  • THEN они показаны одной группой без итога, и каждая строка показывает свой счёт

Scenario: Прогнозный итог по нескольким валютам

  • WHEN группа содержит дивиденд 10 USD на одном счёте и дивиденд 200 RUB по той же бумаге на ту же дату на другом счёте
  • THEN заголовок показывает отдельные прогнозные итоги 10 USD и 200 RUB без конвертации

Scenario: Прогнозный итог скрыт при неполном прогнозе

  • WHEN в режиме «Все счета» прогноз обозначен неполным, и на странице есть группа событий с известными суммами
  • THEN заголовок группы не показывает итог, а строки событий остаются видны со своими суммами

Scenario: Группа дивидендов по дате реестра

  • WHEN группа содержит дивиденды, датированные датой реестра
  • THEN заголовок обозначает дату как дату реестра

Requirement: All-accounts upcoming event grouping preserves order and limits

Группа событий MUST занимать позицию своего первого события в порядке ответа; события внутри группы и остальные строки MUST сохранять относительный порядок ответа. Группировка MUST NOT менять состав, окно, порядок по дате или лимит обзора, который считается по событиям; группы собираются только из показанных событий.

Scenario: Группировка в ближайших событиях обзора

  • WHEN в режиме «Все счета» среди пяти ближайших событий есть два события с одинаковым ключом
  • THEN блок обзора показывает те же пять событий, что и без группировки, два из них одной группой

Scenario: Позиция группы событий

  • WHEN порядок ответа содержит события A, B, C, где A и C образуют группу, а B не входит ни в одну группу
  • THEN список показывает группу (A, C) на месте A, затем B