Files
specs/openspec/specs/portfolio-view/spec.md
T
2026-08-13 22:48:40 +03:00

14 KiB
Raw Blame History

portfolio-view Specification

Purpose

Позволяет авторизованному User просматривать нормализованный текущий состав и агрегаты Portfolio одного доступного Brokerage Account без валютной конвертации.

Requirements

Requirement: Получение Portfolio выбранного Brokerage Account

Система SHALL предоставлять авторизованному User GET /api/v1/brokerage-accounts/:accountId/portfolio. Она MUST возвращать нормализованный полный Portfolio только для открытого поддерживаемого Brokerage Account, доступного через текущий T-Bank connection User. Ответ MUST содержать lastSuccessfulAt в RFC 3339 UTC без миллисекунд, boolean isStale, totalsByCurrency, positions и cashBalances.

Scenario: Получение текущего Portfolio

  • WHEN User запрашивает Portfolio доступного Brokerage Account, а обязательные upstream-данные успешно получены
  • THEN система отвечает 200 полным нормализованным Portfolio с isStale: false и временем успешного получения финансового снимка

Scenario: Недоступный Brokerage Account

  • WHEN accountId не принадлежит User, закрыт, неподдерживаем или больше не доступен через текущий T-Bank connection
  • THEN система отвечает 404 без Portfolio

Requirement: Нормализованный состав Portfolio

Система MUST возвращать только обычные Portfolio Position с ненулевым количеством и доступные Cash Balance с ненулевой суммой. Cash Balance MUST быть отдельным от Portfolio Position элементом и агрегироваться до одного значения на валюту. Порядок Portfolio Position и Cash Balance MUST соответствовать upstream-порядку; система MUST NOT сортировать или объединять Portfolio Position с одинаковым FIGI. Виртуальные позиции и заблокированные денежные остатки MUST NOT входить в Portfolio.

Каждая Portfolio Position MUST содержать нормализованный тип STOCK, BOND, FUND, CURRENCY, FUTURE, OPTION, STRUCTURED_NOTE, DIGITAL_FINANCIAL_ASSET либо OTHER, строковое quantity, nullable currentValue, nullable averagePurchasePrice и nullable absoluteReturn. Система MUST искать metadata по instrument UID при его наличии, затем по FIGI. Отображаемый идентификатор MUST предпочитать name, затем ticker, а при недоступности metadata использовать FIGI, не скрывая финансовые значения.

Scenario: Позиция с неизвестным upstream-типом

  • WHEN upstream возвращает обычную ненулевую позицию с нераспознанным типом инструмента
  • THEN система возвращает её в исходном порядке с типом OTHER

Scenario: Позиция без metadata инструмента

  • WHEN lookup metadata по FIGI не дал ticker или name либо временно недоступен
  • THEN система возвращает позицию с FIGI как fallback-идентификатором и всеми доступными финансовыми значениями

Scenario: Имя инструмента доступно

  • WHEN metadata инструмента по FIGI содержит непустое name
  • THEN система возвращает name как отображаемый идентификатор позиции вместо FIGI или ticker

Scenario: FIGI не разрешается, но доступен instrument UID

  • WHEN FIGI позиции не даёт metadata, а её instrument UID возвращает непустое name
  • THEN система возвращает name, полученное по instrument UID

Scenario: Денежные остатки в одной валюте

  • WHEN upstream возвращает несколько ненулевых доступных денежных остатков одной валюты
  • THEN система возвращает один Cash Balance с их точной суммой

Scenario: Штатный пустой Portfolio

  • WHEN у доступного Brokerage Account нет ненулевых обычных позиций и доступных денежных остатков
  • THEN система отвечает 200 с пустыми positions, cashBalances и totalsByCurrency

Requirement: Точные значения и валютные агрегаты

Все количества и финансовые значения в Portfolio MUST передаваться десятичными строками без экспоненциальной записи и без незначащих завершающих нулей. Денежное значение MUST содержать ISO-код валюты. Система MUST корректно нормализовать отрицательные целую и дробную части upstream-значений. Отсутствующее значение MUST передаваться как null, а не как ноль.

currentValue Portfolio Position MUST быть точным произведением количества на текущую цену при наличии валидных множителей и валюты цены. absoluteReturn MUST быть точной величиной (currentPrice - averagePurchasePrice) * quantity только когда обе цены имеют одинаковую известную валюту. Позиция без вычислимой текущей стоимости MUST оставаться в ответе, но MUST NOT входить в агрегаты. totalsByCurrency MUST содержать точную сумму currentValue позиций и Cash Balance в каждой валюте без валютной конвертации.

Scenario: Мультивалютный Portfolio

  • WHEN Portfolio содержит финансовые значения в RUB и USD
  • THEN система возвращает отдельные агрегаты RUB и USD без общего сконвертированного итога

Scenario: Позиция без вычислимой стоимости

  • WHEN у ненулевой позиции отсутствует либо невалидна цена, количество или валюта цены
  • THEN система возвращает позицию с currentValue: null и не включает её в totalsByCurrency

Scenario: Отрицательная дробная часть upstream-значения

  • WHEN upstream возвращает сумму с units: 0 и отрицательной дробной частью nano: -500000000
  • THEN система передаёт её как строку "-0.5"

Requirement: Свежесть и fallback полного Portfolio Snapshot

Система MUST кэшировать полный успешный Portfolio Snapshot отдельно для каждой пары User и Brokerage Account в памяти на 30 секунд. После истечения свежего кэша система MUST получить новый полный снимок; при временной upstream-недоступности она SHALL вернуть последний успешный полный снимок, если ему не более пяти минут, с isStale: true. Metadata инструмента MUST NOT влиять на успешность или свежесть Portfolio Snapshot, но MUST быть получено до формирования нового Snapshot, чтобы при доступном upstream-ответе вернуть отображаемое имя инструмента.

Система MUST очищать все Portfolio Snapshot User после успешной замены или удаления его T-Bank connection. Успешные metadata инструмента и подтверждённое отсутствие инструмента MUST кэшироваться глобально по FIGI на 24 часа; ошибка metadata lookup MUST NOT кэшироваться.

Scenario: Свежий Snapshot

  • WHEN User повторно запрашивает тот же Portfolio в течение 30 секунд после успешного получения
  • THEN система возвращает свежий Snapshot без нового обращения к T-Bank

Scenario: Медленный lookup инструмента

  • WHEN metadata инструмента ещё отсутствует и его lookup не завершился
  • THEN система ожидает lookup в пределах gRPC deadline и возвращает name при успешном ответе upstream

Scenario: Fallback после временной upstream-ошибки

  • WHEN обновление устаревшего Snapshot неуспешно из-за временной недоступности T-Bank, а последний полный успешный Snapshot получен не более пяти минут назад
  • THEN система отвечает 200 этим Snapshot с isStale: true и исходным lastSuccessfulAt

Scenario: Недействительный token не маскируется fallback-данными

  • WHEN T-Bank отклоняет token как неаутентифицированный или без прав при обновлении Portfolio
  • THEN система не возвращает fallback Snapshot и отвечает 400 Problem Details с code INVALID_TBANK_TOKEN

Requirement: Ошибки и состояния Portfolio на клиенте

Клиент SHALL загружать Portfolio при открытии защищённого маршрута /accounts/:accountId. Он MUST использовать успешный ответ в течение 30 секунд, без automatic retry, polling или refetch при возврате фокуса. После истечения срока клиент MUST скрыть предыдущий ответ и показать loading-состояние до нового результата.

Клиент MUST показать штатное empty-состояние только при пустых positions и cashBalances. При isStale: true клиент MUST показывать Snapshot, lastSuccessfulAt и нейтральное предупреждение о временной недоступности T-Bank. При 404 клиент MUST показать состояние недоступного счёта с действием возврата к списку счетов. При отсутствии Snapshot и TBANK_NOT_CONNECTED, INVALID_TBANK_TOKEN, TBANK_UNAVAILABLE или иной ошибке клиент MUST показать безопасное error-состояние с доступными для ошибки действиями; действие повтора разрешено только для временной и неизвестной ошибки.

Клиент MUST отображать STOCK и BOND отдельными секциями. Внутри каждой секции MUST сохраняться upstream-порядок позиций. Позиции иных типов MUST оставаться доступными в отдельной общей секции. При форматировании денежных значений в русской локали клиент MUST отображать RUB как символ ₽; другие валюты MUST сохранять понятное обозначение.

Scenario: Показ устаревшего Portfolio

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

Scenario: Недоступный счёт на клиенте

  • WHEN клиент получает 404 для Portfolio
  • THEN клиент не показывает Portfolio и предлагает вернуться к списку Brokerage Account без действия повтора

Scenario: Портфель отображается на мобильном устройстве

  • WHEN User открывает загруженный Portfolio на узком экране
  • THEN клиент сохраняет читаемое одноколоночное отображение агрегатов, позиций и Cash Balance

Scenario: Отдельные акции и облигации

  • WHEN Portfolio содержит позиции STOCK и BOND
  • THEN клиент отображает их в отдельных секциях «Акции» и «Облигации» без изменения порядка в каждой секции