feat: add future events
This commit is contained in:
+2
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-08-14
|
||||
@@ -0,0 +1,61 @@
|
||||
## Context
|
||||
|
||||
См. motivation в `proposal.md`. Текущие Portfolio Position и Account Ledger получаются read-through из T-Bank; календаря или сохранённого расписания событий нет. `references/moex-vibe` подтверждает, что MOEX предоставляет дату и величину дивиденда, ближайший купон, дату и номинал погашения, а также дату оферты. Эти данные не содержат расписания будущей амортизации.
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
- Вычислять account-scoped прогноз без хранения синтетических событий.
|
||||
- Сохранять отличия между датой реестра дивиденда и датой денежного события.
|
||||
- Опубликовать единый OpenAPI-контракт, из которого frontend сгенерирует типы.
|
||||
|
||||
**Non-Goals:**
|
||||
- Расчёт налогов, конвертация валют и общий итог.
|
||||
- Учёт будущих сделок или владения на дату реестра.
|
||||
- История фактических выплат, ручное обновление и фильтры.
|
||||
- Будущие частичные погашения до появления достоверного расписания амортизации.
|
||||
|
||||
## Decisions
|
||||
|
||||
### Read-through projection of current positions
|
||||
|
||||
`GET /api/v1/brokerage-accounts/:accountId/events` получает актуальный Portfolio, обогащает его данными MOEX и собирает только события в rolling-окне 30 календарных дней. Ответ содержит `calculatedAt`; данные не сохраняются.
|
||||
|
||||
Это избегает отдельного хранилища и устаревших расписаний. Альтернатива с persisted calendar потребовала бы синхронизации с позициями и не повышает точность, поскольку расчёт всё равно основан на текущем количестве бумаг.
|
||||
|
||||
### Explicit event contract
|
||||
|
||||
Контракт события содержит тип (`coupon`, `dividend`, `maturity`, `offer`), календарную дату, семантику даты (`event` или `registry`), инструмент, исходную валюту и nullable прогнозную сумму. Для coupon, dividend и maturity сумма равна текущему количеству позиции, умноженному на выплату или номинал на одну бумагу; offer не получает выдуманную сумму.
|
||||
|
||||
События возвращаются без агрегированного итога, поскольку валюты не конвертируются. Альтернатива с одним полем даты скрыла бы отличие даты реестра от даты поступления дивиденда.
|
||||
|
||||
### Strict source completeness
|
||||
|
||||
Если запрос Portfolio или нужные данные MOEX не получены, endpoint отвечает ошибкой и не публикует неполный календарь. Инструмент с успешным ответом, но без поддерживаемого события, просто не добавляет строку.
|
||||
|
||||
Это отделяет техническую неполноту от корректного пустого результата. Альтернатива вернуть частичный список не позволяет пользователю отличить пропуск от отсутствия выплат.
|
||||
|
||||
### Account-local interface using established list patterns
|
||||
|
||||
Frontend добавляет маршрут `/accounts/:accountId/events`, breadcrumb и действие «Будущие события» на обзоре счёта рядом с Account Ledger. Обзор запрашивает тот же endpoint отдельно от Portfolio и показывает первые пять событий в локальном блоке. Страница использует существующие состояния loading/error/empty и компактные responsive rows; она показывает все события результата без локальных фильтров или pagination.
|
||||
|
||||
Это продолжает account-scoped навигацию `/accounts/:accountId/operations`. Альтернатива с глобальным календарём смешивала бы события разных счетов и требовала бы выбора счёта.
|
||||
|
||||
### Coverage limitation is part of the interface
|
||||
|
||||
UI постоянно поясняет, что суммы являются оценками до налогов по текущей позиции, а покрытие зависит от доступных данных MOEX. При пустом списке пояснение дополнительно называет отсутствие известных событий в ближайшие 30 дней. Будущее частичное погашение не имитируется датой maturity.
|
||||
|
||||
Это сохраняет полезный минимальный список без ложной точности. Альтернатива скрыть ограничение противоречит природе прогнозов и данным источника.
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- [Позиция изменится до события] → обозначать сумму как прогноз по позиции на `calculatedAt`, не заявляя право на выплату.
|
||||
- [MOEX публикует дивидендную дату реестра, а не дату зачисления] → передавать и отображать отдельную семантику даты.
|
||||
- [MOEX даёт только ближайший купон] → ограничить окно 30 днями и явно раскрыть неполноту покрытия.
|
||||
- [Нет графика амортизации] → исключить будущие частичные погашения до добавления надёжного источника.
|
||||
|
||||
## Migration Plan
|
||||
|
||||
1. Развернуть backend endpoint и обновлённый OpenAPI.
|
||||
2. Сгенерировать и закоммитить frontend API-контракт, затем выпустить страницу.
|
||||
3. При откате удалить действие и маршрут frontend; endpoint можно удалить без миграции данных, поскольку состояние не сохраняется.
|
||||
@@ -0,0 +1,27 @@
|
||||
## Why
|
||||
|
||||
Пользователь видит только уже исполненные доходы в Account Ledger и не может планировать ближайшие купоны, дивиденды и события облигаций. Нужна отдельная страница счёта с честно обозначенными прогнозами будущих событий.
|
||||
|
||||
## What Changes
|
||||
|
||||
- Добавить страницу «Будущие события» для одного Brokerage Account и переход к ней с обзора счёта.
|
||||
- Добавить на обзор счёта компактный список из пяти ближайших будущих событий и переход к полной странице.
|
||||
- Добавить API прогноза известных событий на ближайшие 30 дней: купонов, дивидендов, полного погашения и оферт.
|
||||
- Рассчитывать прогнозные суммы до налогов по текущему количеству бумаг и не скрывать ограничения источников данных.
|
||||
- Обрабатывать недоступность источников как ошибку всей страницы, не показывая частичный результат.
|
||||
- Зафиксировать реализованное поведение и ограничения в постоянной документации `docs/features`.
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
- `brokerage-account-upcoming-events`: просмотр прогнозируемых будущих денежных и связанных корпоративных событий одного брокерского счёта.
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
- Нет.
|
||||
|
||||
## Impact
|
||||
|
||||
- Backend: T-Bank portfolio integration, новый клиент MOEX и account-scoped endpoint в OpenAPI.
|
||||
- Frontend: сгенерированный API-контракт, страница счёта, breadcrumbs и действие на обзоре счёта.
|
||||
- Docs: новая feature-документация в `docs/features` после реализации.
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
## Purpose
|
||||
|
||||
Позволяет пользователю просматривать известные будущие денежные и связанные корпоративные события одного брокерского счёта на ближайший месяц.
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: User can retrieve upcoming events for an account
|
||||
Система MUST предоставлять аутентифицированному User read-only список известных будущих событий принадлежащего ему Brokerage Account от момента запроса до 30 календарных дней после него включительно. Ответ MUST содержать только события coupon, dividend, maturity и offer, отсортированные по дате по возрастанию, и момент расчёта прогноза.
|
||||
|
||||
#### Scenario: Получение событий доступного счёта
|
||||
- **WHEN** аутентифицированный User запрашивает будущие события принадлежащего ему Brokerage Account
|
||||
- **THEN** система возвращает известные события на следующие 30 календарных дней и момент, на который был выполнен расчёт
|
||||
|
||||
#### Scenario: Отсутствие событий в периоде
|
||||
- **WHEN** для счёта нет известных событий в ближайшие 30 календарных дней
|
||||
- **THEN** система возвращает успешный пустой список и момент расчёта
|
||||
|
||||
#### Scenario: Отклонение недоступного счёта
|
||||
- **WHEN** аутентифицированный User запрашивает события счёта, который не является открытым поддерживаемым счётом, доступным через его T-Bank connection
|
||||
- **THEN** система возвращает существующий ответ о недоступности счёта и не раскрывает события
|
||||
|
||||
### Requirement: Forecasts preserve event meaning and uncertainty
|
||||
Система MUST передавать для каждого события его календарную дату, тип, доступные идентификаторы и название инструмента, исходную валюту и, если она известна, прогнозную сумму до налогов. Прогнозная сумма MUST рассчитываться по текущему количеству бумаг на момент запроса. Дивиденд MUST обозначать переданную дату как дату реестра, а offer MUST передаваться без вымышленной суммы.
|
||||
|
||||
#### Scenario: Прогноз купона по текущей позиции
|
||||
- **WHEN** поддерживаемая облигация в текущей Portfolio Position имеет известный купон в пределах периода
|
||||
- **THEN** система возвращает событие coupon с суммой до налогов в исходной валюте, рассчитанной по текущему количеству облигаций
|
||||
|
||||
#### Scenario: Прогноз дивиденда
|
||||
- **WHEN** поддерживаемая акция в текущей Portfolio Position имеет известный дивиденд в пределах периода
|
||||
- **THEN** система возвращает событие dividend с датой, обозначенной как дата реестра, и суммой до налогов, рассчитанной по текущему количеству акций
|
||||
|
||||
#### Scenario: Отображение оферты без суммы
|
||||
- **WHEN** для поддерживаемой облигации известна оферта в пределах периода, но её денежные условия недоступны
|
||||
- **THEN** система возвращает событие offer без прогнозной суммы
|
||||
|
||||
#### Scenario: Неподдерживаемое частичное погашение
|
||||
- **WHEN** источник не предоставляет расписание будущих частичных погашений или амортизации облигации
|
||||
- **THEN** система НЕ создаёт синтетическое событие частичного погашения
|
||||
|
||||
### Requirement: Upcoming events use complete source results
|
||||
Система MUST завершать запрос ошибкой, если необходимые для прогноза данные брокера или MOEX недоступны, и MUST NOT возвращать частичный список как полный результат. Система MUST пропускать инструменты, для которых источник не предоставляет поддерживаемые события, не создавая строки «нет данных».
|
||||
|
||||
#### Scenario: Недоступность источника
|
||||
- **WHEN** необходимый источник данных брокера или MOEX временно недоступен
|
||||
- **THEN** система возвращает ошибку запроса будущих событий без частичного списка
|
||||
|
||||
#### Scenario: Инструмент без поддерживаемых данных
|
||||
- **WHEN** Portfolio содержит инструмент, для которого источник не предоставляет поддерживаемые будущие события
|
||||
- **THEN** система не добавляет для него синтетическую строку в результат
|
||||
|
||||
### Requirement: Account page presents an upcoming events list
|
||||
Страница Brokerage Account MUST предоставлять действие перехода на отдельную страницу «Будущие события». Эта страница MUST показывать все полученные события компактным списком без фильтров, пагинации и суммарного итога, с датой, типом, инструментом и суммой в исходной валюте, если она известна. Страница MUST объяснять, что прогноз основан на текущей позиции, указан до налогов и может быть неполным из-за доступности данных источника.
|
||||
|
||||
#### Scenario: Просмотр ближайших событий
|
||||
- **WHEN** User открывает «Будущие события» из обзора Brokerage Account
|
||||
- **THEN** он видит отсортированный список событий ближайших 30 дней, где offer обозначен без суммы
|
||||
|
||||
#### Scenario: Пустое состояние страницы
|
||||
- **WHEN** запрос будущих событий успешно возвращает пустой список
|
||||
- **THEN** страница сообщает, что на ближайшие 30 дней событий не найдено, и объясняет ограничения покрытия источника
|
||||
|
||||
#### Scenario: Ошибка страницы
|
||||
- **WHEN** запрос будущих событий завершается ошибкой
|
||||
- **THEN** страница не показывает частичный или устаревший список и предоставляет действие повторить запрос
|
||||
|
||||
### Requirement: Account overview includes upcoming events
|
||||
Обзор Brokerage Account MUST показывать не более пяти ближайших Future Cash Event и действие перехода на полную страницу «Будущие события». Неуспешный или пустой запрос этого блока MUST оставаться локальным состоянием блока и не заменять успешно отображённый Portfolio.
|
||||
|
||||
#### Scenario: Отображение ближайших событий
|
||||
- **WHEN** обзор счёта успешно загружен и календарь содержит будущие события
|
||||
- **THEN** обзор показывает первые пять событий в порядке даты и действие перехода к полной странице
|
||||
|
||||
#### Scenario: Ближайшие события недоступны
|
||||
- **WHEN** обзор счёта успешно загружен, но запрос будущих событий завершается ошибкой
|
||||
- **THEN** Portfolio остаётся доступным, а блок будущих событий отображает собственную ошибку
|
||||
@@ -0,0 +1,19 @@
|
||||
## 1. Backend-проекция событий
|
||||
|
||||
- [x] 1.1 Добавить MOEX-клиент, получающий данные о поддерживаемых дивидендах и событиях облигаций для текущих Portfolio Position.
|
||||
- [x] 1.2 Добавить account-scoped сервис будущих событий и OpenAPI endpoint, который проверяет владение счётом, рассчитывает прогноз на 30 дней и не возвращает частичные данные при ошибках источников.
|
||||
- [x] 1.3 Добавить backend-тесты классификации событий, границ дат, расчёта сумм, неподдерживаемого частичного погашения, недоступности источников и доступа к счёту.
|
||||
|
||||
## 2. API-контракт и frontend-страница
|
||||
|
||||
- [x] 2.1 Перегенерировать и закоммитить frontend OpenAPI snapshot и сгенерированные декларации из backend-контракта.
|
||||
- [x] 2.2 Добавить entity query будущих событий и страницу `/accounts/:accountId/events` с существующими паттернами loading, error, empty и адаптивного списка.
|
||||
- [x] 2.3 Добавить действие на обзоре счёта и breadcrumb для «Будущих событий».
|
||||
- [x] 2.4 Добавить frontend-тесты заполненного списка, оферты без суммы, пустого состояния и состояния ошибки с повторной попыткой.
|
||||
- [x] 2.5 Добавить на обзор счёта локальный список пяти ближайших будущих событий и тесты его состояний.
|
||||
|
||||
## 3. Документация и проверка
|
||||
|
||||
- [x] 3.1 Обновить `CONTEXT.md` согласованным термином глоссария `Future Cash Event`.
|
||||
- [x] 3.2 Добавить `docs/features/brokerage-account-upcoming-events.md` с пользовательским потоком, API-сценарием, семантикой прогноза, покрытием источников и ограничениями.
|
||||
- [x] 3.3 Запустить backend- и frontend-проверки, валидировать OpenSpec change и выполнить `opsx-verify` перед архивированием.
|
||||
@@ -0,0 +1,78 @@
|
||||
# Brokerage Account Upcoming Events Specification
|
||||
|
||||
## Purpose
|
||||
|
||||
Позволяет пользователю просматривать известные будущие денежные и связанные корпоративные события одного брокерского счёта на ближайший месяц.
|
||||
|
||||
## Requirements
|
||||
|
||||
### Requirement: User can retrieve upcoming events for an account
|
||||
Система MUST предоставлять аутентифицированному User read-only список известных будущих событий принадлежащего ему Brokerage Account от момента запроса до 30 календарных дней после него включительно. Ответ MUST содержать только события coupon, dividend, maturity и offer, отсортированные по дате по возрастанию, и момент расчёта прогноза.
|
||||
|
||||
#### Scenario: Получение событий доступного счёта
|
||||
- **WHEN** аутентифицированный User запрашивает будущие события принадлежащего ему Brokerage Account
|
||||
- **THEN** система возвращает известные события на следующие 30 календарных дней и момент, на который был выполнен расчёт
|
||||
|
||||
#### Scenario: Отсутствие событий в периоде
|
||||
- **WHEN** для счёта нет известных событий в ближайшие 30 календарных дней
|
||||
- **THEN** система возвращает успешный пустой список и момент расчёта
|
||||
|
||||
#### Scenario: Отклонение недоступного счёта
|
||||
- **WHEN** аутентифицированный User запрашивает события счёта, который не является открытым поддерживаемым счётом, доступным через его T-Bank connection
|
||||
- **THEN** система возвращает существующий ответ о недоступности счёта и не раскрывает события
|
||||
|
||||
### Requirement: Forecasts preserve event meaning and uncertainty
|
||||
Система MUST передавать для каждого события его календарную дату, тип, доступные идентификаторы и название инструмента, исходную валюту и, если она известна, прогнозную сумму до налогов. Прогнозная сумма MUST рассчитываться по текущему количеству бумаг на момент запроса. Дивиденд MUST обозначать переданную дату как дату реестра, а offer MUST передаваться без вымышленной суммы.
|
||||
|
||||
#### Scenario: Прогноз купона по текущей позиции
|
||||
- **WHEN** поддерживаемая облигация в текущей Portfolio Position имеет известный купон в пределах периода
|
||||
- **THEN** система возвращает событие coupon с суммой до налогов в исходной валюте, рассчитанной по текущему количеству облигаций
|
||||
|
||||
#### Scenario: Прогноз дивиденда
|
||||
- **WHEN** поддерживаемая акция в текущей Portfolio Position имеет известный дивиденд в пределах периода
|
||||
- **THEN** система возвращает событие dividend с датой, обозначенной как дата реестра, и суммой до налогов, рассчитанной по текущему количеству акций
|
||||
|
||||
#### Scenario: Отображение оферты без суммы
|
||||
- **WHEN** для поддерживаемой облигации известна оферта в пределах периода, но её денежные условия недоступны
|
||||
- **THEN** система возвращает событие offer без прогнозной суммы
|
||||
|
||||
#### Scenario: Неподдерживаемое частичное погашение
|
||||
- **WHEN** источник не предоставляет расписание будущих частичных погашений или амортизации облигации
|
||||
- **THEN** система НЕ создаёт синтетическое событие частичного погашения
|
||||
|
||||
### Requirement: Upcoming events use complete source results
|
||||
Система MUST завершать запрос ошибкой, если необходимые для прогноза данные брокера или MOEX недоступны, и MUST NOT возвращать частичный список как полный результат. Система MUST пропускать инструменты, для которых источник не предоставляет поддерживаемые события, не создавая строки «нет данных».
|
||||
|
||||
#### Scenario: Недоступность источника
|
||||
- **WHEN** необходимый источник данных брокера или MOEX временно недоступен
|
||||
- **THEN** система возвращает ошибку запроса будущих событий без частичного списка
|
||||
|
||||
#### Scenario: Инструмент без поддерживаемых данных
|
||||
- **WHEN** Portfolio содержит инструмент, для которого источник не предоставляет поддерживаемые будущие события
|
||||
- **THEN** система не добавляет для него синтетическую строку в результат
|
||||
|
||||
### Requirement: Account page presents an upcoming events list
|
||||
Страница Brokerage Account MUST предоставлять действие перехода на отдельную страницу «Будущие события». Эта страница MUST показывать все полученные события компактным списком без фильтров, пагинации и суммарного итога, с датой, типом, инструментом и суммой в исходной валюте, если она известна. Страница MUST объяснять, что прогноз основан на текущей позиции, указан до налогов и может быть неполным из-за доступности данных источника.
|
||||
|
||||
#### Scenario: Просмотр ближайших событий
|
||||
- **WHEN** User открывает «Будущие события» из обзора Brokerage Account
|
||||
- **THEN** он видит отсортированный список событий ближайших 30 дней, где offer обозначен без суммы
|
||||
|
||||
#### Scenario: Пустое состояние страницы
|
||||
- **WHEN** запрос будущих событий успешно возвращает пустой список
|
||||
- **THEN** страница сообщает, что на ближайшие 30 дней событий не найдено, и объясняет ограничения покрытия источника
|
||||
|
||||
#### Scenario: Ошибка страницы
|
||||
- **WHEN** запрос будущих событий завершается ошибкой
|
||||
- **THEN** страница не показывает частичный или устаревший список и предоставляет действие повторить запрос
|
||||
|
||||
### Requirement: Account overview includes upcoming events
|
||||
Обзор Brokerage Account MUST показывать не более пяти ближайших Future Cash Event и действие перехода на полную страницу «Будущие события». Неуспешный или пустой запрос этого блока MUST оставаться локальным состоянием блока и не заменять успешно отображённый Portfolio.
|
||||
|
||||
#### Scenario: Отображение ближайших событий
|
||||
- **WHEN** обзор счёта успешно загружен и календарь содержит будущие события
|
||||
- **THEN** обзор показывает первые пять событий в порядке даты и действие перехода к полной странице
|
||||
|
||||
#### Scenario: Ближайшие события недоступны
|
||||
- **WHEN** обзор счёта успешно загружен, но запрос будущих событий завершается ошибкой
|
||||
- **THEN** Portfolio остаётся доступным, а блок будущих событий отображает собственную ошибку
|
||||
Reference in New Issue
Block a user