Files
specs/openspec/specs/brokerage-account-ledger/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

35 KiB
Raw Blame History

Brokerage Account Ledger Specification

Purpose

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

Requirements

Requirement: User can retrieve an account ledger

Система MUST предоставлять аутентифицированный журнал только для чтения выбранного Brokerage Account либо существующего режима «Все счета» (all) в границах текущего User и его T-Bank connection. В режиме all доступ MUST проверяться для каждого входящего счёта, операции MUST сохранять принадлежность исходному счёту, а полнота периода MUST учитывать покрытие каждого входящего счёта. Журнал MUST содержать только исполненные операции T-Bank и при отсутствии периода по умолчанию охватывать интервал от начала текущего календарного года в UTC до момента запроса.

Система MUST читать сохранённые операции и локальное подтверждение доступа текущего подключения без синхронного обращения к T-Bank, включая запрос списка счетов. Ответ MUST содержать envelope { data, meta } по tbank-data-sync, отражающий полноту запрошенного периода независимо от текущей страницы. Отсутствующие данные MUST возвращаться с data: null и состоянием загрузки, а частично сохранённый период MUST обозначаться как partial. Только подтверждённо полный пустой период MUST обозначать отсутствие операций.

Scenario: Получение журнала с периодом по умолчанию

  • WHEN аутентифицированный User запрашивает журнал принадлежащего ему Brokerage Account без периода
  • THEN система возвращает сохранённые исполненные операции в data и полноту периода в meta за период от начала текущего календарного года в UTC до момента запроса, запрашивая отсутствующие интервалы в фоне

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

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

Scenario: Общий журнал сохраняет принадлежность записей

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

Scenario: Общий журнал содержит неполный счёт

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

Scenario: Пустая загруженная часть не доказывает отсутствие операций

  • WHEN в сохранённой части периода нет записей, но покрытие периода не завершено
  • 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. При временном сбое обновления блок MUST сохранять доступные записи без технического предупреждения или ручного повтора. При отсутствии результата он SHALL показывать первичное ожидание либо нейтральную недоступность, автоматически восстанавливая чтение. Неподтверждённая пустота MUST NOT представляться отсутствием операций.

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 сопровождаться подписью конкретного типа события и знаком: для купонной операции — «Купон», для дивидендной — «Дивиденд», для остальных записей — подпись их категории («Сделка», «Налог», «Комиссия», «Перевод», «Прочее»). Строка MUST NOT повторять подпись типа события во вторичной метадате записи. Знак «+» / «−» и семантический цвет MUST оставаться носителями направления и MUST NOT заменяться только текстом. Страница MUST обозначать записи без движения денег нейтрально и MUST NOT показывать цену или количество. В группе режима «Все счета» дата, тип операции, инструмент и его визуальная идентичность MUST показываться один раз в заголовке группы, а строка каждой записи группы MUST показывать её счёт и денежное изменение с подписью типа и знаком.

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 без подмены или сокрытия доступных данных инструмента

Scenario: Запись внутри группы режима «Все счета»

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

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 пагинация выполняется по последнему применённому фильтру, а черновик в контролах сохраняется

Requirement: Отсутствующий исторический интервал загружается только впервые

Система MUST позволять авторизованному User при первом открытии ещё не загруженного периода операций доступного Brokerage Account начать получение только недостающего интервала без ожидания T-Bank в локальном ответе. Это является исключением первой загрузки и MUST NOT запускать обновление других личных данных. Полностью загруженный прошлый интервал MUST NOT повторно сверяться из-за чтения страницы или часового обхода. Изменяемый хвост операций MUST обновляться часовым обходом. Незавершённый интервал и ошибка загрузки MUST NOT обозначаться полными, а последний подтверждённый результат MUST оставаться доступным.

Scenario: Первый просмотр отсутствующего прошлого периода

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

Scenario: Повторное открытие полного периода

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

Scenario: Ошибка загрузки

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

Requirement: Журнал восстанавливает чтение без ручного обновления

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

Scenario: Обновление журнала временно не удалось

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

Scenario: Следующая страница ещё не получена

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

Requirement: All-accounts ledger groups same-day entries of one instrument

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

Scenario: Купоны одной облигации на двух счетах

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

Scenario: Две операции одного типа на одном счёте

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

Scenario: Разные типы по одной бумаге не объединяются

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

Scenario: Покупка и продажа не объединяются

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

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

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

Scenario: Запись без Instrument

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

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

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

Requirement: All-accounts ledger group shows each entry and an exact total

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

Scenario: Итог по нескольким валютам

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

Scenario: Итог со знаком

  • WHEN группа содержит поступление +150.10 RUB и отрицательную корректировку −50.05 RUB
  • THEN итог группы равен +100.05 RUB, а обе записи видны отдельными строками со своими знаками

Scenario: Нулевой итог

  • WHEN группа содержит +150.10 RUB и −150.10 RUB
  • THEN заголовок показывает итог 0 RUB без знака направления, а обе записи видны со своими знаками

Scenario: Группа с записью без денежного изменения

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

Scenario: Запись с нулевой суммой скрывает итог

  • WHEN группа содержит запись +100 RUB и запись с денежным изменением 0 RUB
  • THEN заголовок группы не показывает итог, а запись с нулём обозначена как запись без движения денег

Scenario: Итог скрыт при неполном периоде

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

Requirement: All-accounts ledger grouping preserves order and limits

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

Scenario: Позиция группы в списке

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

Scenario: Группа на границе страницы

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

Scenario: Группировка в последних записях обзора

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