Files
specs/openspec/specs/brokerage-account-ledger/spec.md
T

130 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Brokerage Account Ledger Specification
## Purpose
Позволяет пользователю просматривать исполненные операции одного брокерского счёта, чтобы понимать фактические денежные и инструментальные движения.
## Requirements
### Requirement: User can retrieve an account ledger
Система MUST предоставлять аутентифицированный журнал только для чтения одного Brokerage Account, принадлежащего текущему User. Журнал MUST содержать только исполненные операции T-Bank и при отсутствии периода по умолчанию охватывать интервал от начала текущего календарного года до момента запроса.
#### Scenario: Получение журнала с периодом по умолчанию
- **WHEN** аутентифицированный User запрашивает журнал принадлежащего ему Brokerage Account без периода
- **THEN** система возвращает его исполненные операции от начала текущего календарного года до момента запроса
#### Scenario: Отклонение недоступного счёта
- **WHEN** аутентифицированный User запрашивает журнал счёта, который не является открытым поддерживаемым счётом, доступным через его T-Bank connection
- **THEN** система возвращает существующий ответ о недоступности счёта и не раскрывает операции
### Requirement: User can browse a filtered ledger
Система MUST позволять User ограничивать журнал включительным периодом дат и любым подмножеством категорий trade, income, tax, fee, transfer, other. Система MUST разбивать упорядоченный результат на страницы с непрозрачным cursor и возвращать признак доступности следующей страницы.
#### Scenario: Фильтрация доходных операций за период
- **WHEN** User запрашивает период дат и категорию income
- **THEN** результат содержит только исполненные доходные операции с датой в пределах этого периода
#### Scenario: Продолжение истории
- **WHEN** User запрашивает cursor следующей страницы, возвращённый предыдущим ответом журнала
- **THEN** система возвращает следующую страницу без повторения записей предыдущей страницы
### Requirement: Ledger entries preserve broker operation meaning
Система MUST представлять одну upstream-операцию одной записью журнала и MUST передавать её timestamp, тип операции, категорию, описание, доступные идентификаторы инструмента, доступную presentation metadata инструмента, денежную выплату, количество, цену, НКД и комиссию. Денежные поля MUST сохранять исходную валюту и точное десятичное значение.
#### Scenario: Отображение купонной выплаты
- **WHEN** T-Bank возвращает исполненную купонную операцию с выплатой и данными инструмента
- **THEN** запись журнала определяет её как income и передаёт исходную сумму выплаты, валюту и данные инструмента
#### Scenario: Отображение комиссии сделки без синтетической записи
- **WHEN** T-Bank возвращает исполненную сделку, содержащую выплату и комиссию
- **THEN** журнал содержит одну запись сделки с комиссией в качестве её детали
#### Scenario: Отображение движения бумаг без денег
- **WHEN** T-Bank возвращает исполненную операцию, изменяющую состав инструментов, но без денежной выплаты
- **THEN** запись журнала передаёт доступное количество и не содержит искусственной денежной выплаты с нулевым значением
#### Scenario: Доступна визуальная идентичность инструмента
- **WHEN** для инструмента исполненной операции доступна presentation metadata бренда
- **THEN** запись журнала передаёт её вместе с доступными идентификаторами инструмента, не меняя остальные сведения операции
### Requirement: Account overview includes recent ledger events
Обзор счёта MUST показывать пять последних исполненных записей журнала этого счёта и действие перехода к его полному журналу. Каждая показанная запись MUST иметь ту же визуальную идентичность инструмента и семантику денежного движения, что и запись полного журнала. Неуспешный или пустой запрос последних записей MUST оставаться локальным состоянием блока последних событий и MUST NOT заменять успешно отображённый обзор Portfolio.
#### Scenario: Отображение последних событий
- **WHEN** загружается обзор счёта и его журнал содержит исполненные операции
- **THEN** обзор показывает не более пяти последних записей и ссылку на полный журнал
#### Scenario: Последние записи недоступны
- **WHEN** обзор счёта успешно загружается, но запрос последних записей завершается ошибкой
- **THEN** обзор Portfolio остаётся доступным, а блок последних событий отображает собственное состояние восстановления
#### Scenario: Последняя запись не содержит visual metadata инструмента
- **WHEN** одна из последних записей не имеет доступной presentation metadata инструмента
- **THEN** обзор показывает для неё понятный текстовый fallback и сохраняет дату, тип, инструмент и денежное изменение записи доступными для чтения
### Requirement: Full ledger presents compact rows
Страница полного журнала MUST показывать для каждой записи дату, тип операции, инструмент и денежное изменение в исходной валюте в компактной структурированной строке. При доступной presentation metadata инструмента строка MUST показывать его визуальную идентичность; при её отсутствии MUST использовать понятный текстовый fallback. Для каждой записи денежное изменение в исходной валюте MUST сопровождаться подписью конкретного типа события и знаком: для купонной операции — «Купон», для дивидендной — «Дивиденд», для остальных записей — подпись их категории («Сделка», «Налог», «Комиссия», «Перевод», «Прочее»). Строка MUST NOT повторять подпись типа события во вторичной метадате записи. Знак «+» / «−» и семантический цвет MUST оставаться носителями направления и MUST NOT заменяться только текстом. Страница MUST обозначать записи без движения денег нейтрально и MUST NOT показывать цену или количество.
#### Scenario: Отображение записи только с инструментом
- **WHEN** полный журнал отображает запись без денежной выплаты
- **THEN** страница определяет её как запись без движения денег
#### Scenario: Отображение поступления
- **WHEN** полный журнал отображает запись с положительной денежной выплатой
- **THEN** строка явно показывает поступление знаком и подписью конкретного типа события («Купон», «Дивиденд» или категории) и его денежное изменение в исходной валюте, не полагаясь только на цвет
#### Scenario: Отображение списания
- **WHEN** полный журнал отображает запись с отрицательной денежной выплатой
- **THEN** строка явно показывает списание знаком и подписью конкретного типа события («Купон», «Дивиденд» или категории) и его денежное изменение в исходной валюте, не полагаясь только на цвет
#### Scenario: Отображение купона
- **WHEN** полный журнал отображает запись с upstream-типом купона
- **THEN** строка показывает подпись «Купон»
#### Scenario: Отображение дивиденда
- **WHEN** полный журнал отображает запись с upstream-типом дивиденда
- **THEN** строка показывает подпись «Дивиденд»
#### Scenario: Подпись типа не дублируется
- **WHEN** полный журнал отображает запись с типом события и вторичной метадатой
- **THEN** подпись типа события присутствует в строке один раз и не повторяется во вторичной метадате записи
#### Scenario: Отсутствует visual metadata инструмента
- **WHEN** полный журнал отображает запись без доступной presentation metadata инструмента
- **THEN** строка показывает понятный текстовый fallback без подмены или сокрытия доступных данных инструмента
### Requirement: Кэш страниц журнала ограничивает локальные ресурсы
Система SHALL кэшировать не более 100 недавно использованных страниц Account Ledger в каждом backend process не более чем на 30 секунд. Cache key MUST включать User, Brokerage Account, нормализованные период и categories, opaque cursor и limit; порядок или дублирование categories MUST NOT создавать отдельную cache identity, а отсутствующий конец периода MUST использовать текущий 30-секундный bucket. Просроченная страница MUST NOT возвращаться, а при переполнении cache MUST удалять наименее недавно использованную страницу.
#### Scenario: Равнозначные фильтры используют одну cache identity
- **WHEN** User в одном 30-секундном bucket повторяет запрос страницы журнала с теми же периодом, cursor и limit, но с categories в другом порядке или с дубликатами
- **THEN** система использует ту же cache identity и не запрашивает upstream повторно для свежей страницы
#### Scenario: Большое число уникальных запросов не увеличивает cache без границы
- **WHEN** backend обслуживает больше 100 уникальных запросов страниц журнала
- **THEN** cache содержит не более 100 страниц и освобождает наименее недавно использованные страницы
#### Scenario: Просроченная страница не возвращается
- **WHEN** cached страница журнала старше 30 секунд
- **THEN** система не возвращает её как свежий результат и получает актуальную страницу из upstream
### Requirement: User can explicitly apply ledger filters
Система MUST позволять User формировать черновик периода и категорий на странице полного журнала и применять его явным действием. Пока черновик изменён, но не применён, видимый список записей и его курсорная пагинация MUST оставаться основанными на последнем применённом состоянии. Явное применение MUST коммитить черновик в применимое состояние и сбрасывать курсорную пагинацию на первую страницу. Сброс фильтров MUST очищать черновик, немедленно применять пустой фильтр (период по умолчанию и все категории) и сбрасывать пагинацию.
#### Scenario: Черновик изменён, но не применён
- **WHEN** User изменил период или категории в контролах фильтров, но не нажал «Применить»
- **THEN** видимый список записей остаётся основанным на последнем применённом фильтре, а новый запрос к серверу не выполняется
#### Scenario: Применение черновика
- **WHEN** User нажал «Применить» на фильтре журнала с изменённым черновиком
- **THEN** черновик коммитится в применимое состояние, курсорная пагинация сбрасывается на первую страницу и выполняется новый запрос с применённым периодом и категориями
#### Scenario: Сброс фильтров
- **WHEN** User нажал «Сбросить» на фильтре журнала
- **THEN** черновик очищается, немедленно применяется пустой фильтр (период по умолчанию и все категории) и курсорная пагинация сбрасывается на первую страницу
#### Scenario: Пагинация по применённому фильтру
- **WHEN** User переходит к следующей или предыдущей странице журнала при изменённом, но не применённом черновике
- **THEN** пагинация выполняется по последнему применённому фильтру, а черновик в контролах сохраняется