62 lines
6.8 KiB
Markdown
62 lines
6.8 KiB
Markdown
## 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 можно удалить без миграции данных, поскольку состояние не сохраняется.
|