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