23 KiB
MODIFIED Requirements
Requirement: Нормализованный состав Portfolio
Система MUST возвращать только обычные Portfolio Position с ненулевым количеством и доступные Cash Balance с ненулевой суммой. Cash Balance MUST быть отдельным от Portfolio Position элементом и агрегироваться до одного значения на валюту. Portfolio Position с типом CURRENCY MUST означать позицию по торгуемому валютному Instrument и MUST NOT смешиваться с Cash Balance. Порядок 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, nullable isin, nullable ticker и nullable logoName — имя файла логотипа бренда инструмента, nullable sector — сектор инструмента и nullable issuer — эмитент позиции. Имя файла логотипа MUST заполняться из metadata инструмента, когда upstream предоставляет бренд с непустым именем файла логотипа; во всех остальных случаях оно MUST быть null. Логотип является presentation metadata: он не влияет на количество, стоимость, агрегаты, успешность или свежесть Portfolio Snapshot. Поле sector позиции типа STOCK, BOND или FUND MUST содержать значение сектора из metadata инструмента в том виде, как его определяет T-Bank, без перекодирования и собственной классификации, с обрезанными пробелами в начале и конце; значение из одних пробелов считается отсутствующим. Во всех остальных случаях, включая остальные типы позиций, оно MUST быть null. Поле issuer позиции типа STOCK, BOND или FUND, включая фонд, MUST быть объектом с непустыми строками id — идентификатор бренда актива инструмента — и name — название этого бренда (не название компании), когда T-Bank предоставляет для инструмента бренд актива с идентификатором и названием, непустыми после обрезки пробелов в начале и конце; значение из одних пробелов считается отсутствующим. Во всех остальных случаях, включая остальные типы позиций, оно MUST быть null. Система MUST NOT определять эмитента по совпадению названия, тикера или ISIN. Сектор и эмитент не влияют на количество, стоимость, агрегаты, успешность или свежесть Portfolio Snapshot: их отсутствие либо недоступность MUST NOT делать Snapshot неуспешным или устаревшим и MUST NOT скрывать позицию или её финансовые значения, кроме отказа token по требованию «Свежесть и fallback полного Portfolio Snapshot». Система MUST искать metadata по instrument UID при его наличии, затем по FIGI. Отображаемый идентификатор MUST предпочитать name, затем ticker, а при недоступности metadata использовать FIGI, не скрывая финансовые значения.
Scenario: Позиция с неизвестным upstream-типом
- WHEN upstream возвращает обычную ненулевую позицию с нераспознанным типом инструмента
- THEN система возвращает её в исходном порядке с типом
OTHER
Scenario: Позиция валютного инструмента не является денежным остатком
- WHEN upstream возвращает ненулевую Portfolio Position валютного инструмента и ненулевой Cash Balance той же валюты
- THEN система возвращает Portfolio Position с типом
CURRENCYотдельно от Cash Balance и не объединяет их между собой
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: ISIN инструмента доступен
- WHEN metadata инструмента позиции содержит непустой
isin - THEN система возвращает его в поле
isinпозиции
Scenario: ISIN инструмента отсутствует
- WHEN у инструмента позиции нет ISIN либо metadata недоступна
- THEN система возвращает позицию с
isin: null, не скрывая финансовые значения
Scenario: Тикер инструмента доступен
- WHEN metadata инструмента позиции содержит непустой
ticker - THEN система возвращает его в поле
tickerпозиции
Scenario: Тикер инструмента отсутствует
- WHEN у инструмента позиции нет ticker либо metadata недоступна
- THEN система возвращает позицию с
ticker: null, не скрывая финансовые значения
Scenario: Имя файла логотипа бренда доступно
- WHEN metadata инструмента позиции содержит бренд с непустым именем файла логотипа
- THEN система возвращает его в поле
logoNameпозиции
Scenario: Имя файла логотипа бренда отсутствует
- WHEN у инструмента позиции нет бренда, имя файла логотипа пустое либо metadata недоступна
- THEN система возвращает позицию с
logoName: null, не скрывая финансовые значения
Scenario: Денежные остатки в одной валюте
- WHEN upstream возвращает несколько ненулевых доступных денежных остатков одной валюты
- THEN система возвращает один Cash Balance с их точной суммой
Scenario: Штатный пустой Portfolio
- WHEN у доступного Brokerage Account нет ненулевых обычных позиций и доступных денежных остатков
- THEN система отвечает
200с пустымиpositions,cashBalancesиtotalsByCurrency
Scenario: Сектор инструмента доступен
- WHEN metadata инструмента позиции содержит сектор
energy - THEN система возвращает в поле
sectorпозиции значениеenergyбез изменений
Scenario: Сектор инструмента отсутствует
- WHEN у инструмента позиции нет сектора, сектор пустой либо metadata недоступна
- THEN система возвращает позицию с
sector: null, не скрывая финансовые значения
Scenario: Эмитент позиции доступен
- WHEN T-Bank предоставляет для инструмента позиции бренд актива с идентификатором
brand-1и названием «Сбер» - THEN система возвращает в поле
issuerпозиции идентификаторbrand-1и название «Сбер»
Scenario: Название эмитента — название бренда
- WHEN T-Bank предоставляет для инструмента бренд актива с названием «Сбер» и компанией «ПАО Сбербанк»
- THEN система возвращает в
issuer.name«Сбер»
Scenario: Эмитент у фонда
- WHEN T-Bank предоставляет для инструмента позиции
FUNDбренд актива с идентификаторомbrand-2и названием «Фонд» - THEN система возвращает в
issuerпозиции идентификаторbrand-2и название «Фонд»
Scenario: Пробелы по краям обрезаются
- WHEN T-Bank возвращает сектор " energy " и название бренда " Сбер "
- THEN система возвращает
sectorenergyиissuer.name«Сбер»
Scenario: Значение из одних пробелов
- WHEN T-Bank возвращает сектор " " либо название бренда " "
- THEN система возвращает
sector: nullлибоissuer: nullсоответственно
Scenario: Остальные типы позиций
- WHEN T-Bank предоставляет сектор и бренд актива для инструмента позиции типа
FUTURE - THEN система возвращает у этой позиции
sector: nullиissuer: null
Scenario: Эмитент позиции отсутствует
- WHEN у инструмента позиции нет бренда актива, идентификатор либо название бренда пустые или данные бренда недоступны
- THEN система возвращает позицию с
issuer: null, не скрывая финансовые значения
Scenario: Эмитент не определяется по названию
- WHEN две позиции имеют одинаковое название, но разные идентификаторы бренда актива
- THEN система возвращает в
issuerпозиций разные идентификаторы бренда и не объединяет их
Scenario: Недоступность сектора и эмитента не нарушает Snapshot
- GIVEN сектор и бренд актива позиции ещё ни разу не получены
- WHEN получение сектора и бренда актива завершается ошибкой, а количества и цены позиции получены
- THEN система сохраняет Snapshot с
sector: nullиissuer: nullу затронутых позиций; ихcurrentValue,totalsByCurrency,lastSuccessfulAtиisStaleтакие же, как при доступных сектора и бренде
Requirement: Свежесть и fallback полного Portfolio Snapshot
Система MUST хранить последний полный успешно полученный Portfolio Snapshot с принадлежностью User, экземпляру подключения и Brokerage Account. Обычный GET MUST возвращать { data, meta }, где data является сохранённым Snapshot, а meta соответствует наблюдаемому состоянию resource по tbank-data-sync, читать его без ожидания T-Bank и MUST NOT запускать обновление личного Portfolio, даже если Snapshot отсутствует или старше 60 минут; обновлением владеет часовой обход. Portfolio source MUST считаться свежим в течение 60 минут после успешного получения. Ошибка обхода MUST сохранять прежний полный Snapshot и его время без лимита fallback. Если состав получен полностью, но отдельная позиция не имеет цены, система MUST сохранить новый состав с неполной оценкой; metadata MUST NOT определять успешность или свежесть финансового Snapshot, кроме отказа token при получении сектора или бренда актива позиции: он MUST обрабатываться так же, как отказ token при обходе Portfolio (исключение, источник — #48). Сохранённая доступная identity позиции MAY оставаться доступной при недоступности online-справочника.
Система MUST делать прежние личные datasets недоступными после успешной замены или удаления подключения. Поздняя работа прежнего подключения MUST NOT публиковаться в новом. Самостоятельные справочные сведения и результаты lookup MUST запрашиваться онлайн, а не сохраняться как долговременный справочник; ошибка lookup не подтверждает отсутствие инструмента. Исключение: сектор и бренд актива позиции MUST храниться как последнее известное значение и обновляться по требованию «Обновление сектора и эмитента» (#48).
Scenario: Свежий Snapshot
- WHEN User повторно запрашивает тот же Portfolio в течение 60 минут после успешного получения
- THEN система возвращает свежий Snapshot без нового обращения к T-Bank
Scenario: Медленный lookup инструмента
- WHEN сведения об инструменте ещё запрашиваются онлайн
- THEN система не задерживает локальный ответ Portfolio ради lookup и показывает сохранённую доступную identity
Scenario: Fallback после временной upstream-ошибки
- WHEN часовой обход Portfolio неуспешен из-за временной недоступности T-Bank, а в текущей границе подключения есть полный успешный Snapshot
- THEN система отвечает
200этим Snapshot сisStale: trueи исходнымlastSuccessfulAt
Scenario: Недействительный token не маскируется fallback-данными
- WHEN T-Bank отклоняет token как неаутентифицированный или без прав при часовом обходе Portfolio
- THEN система не возвращает fallback Snapshot и отвечает
400Problem Details с codeINVALID_TBANK_TOKEN
Scenario: Неполная оценка при новом составе
- WHEN успешно получен полный состав позиций, но у одной позиции нет вычислимой цены
- THEN новый состав становится текущим Portfolio Snapshot с неизвестной стоимостью позиции без подстановки нуля
Scenario: Сектор и бренд читаются из хранимого значения
- GIVEN сектор позиции получен 10 часов назад
- WHEN User запрашивает Portfolio
- THEN система возвращает хранимый
sectorбез обращения к T-Bank за сектором
Scenario: Отказ token при получении бренда
- WHEN T-Bank отклоняет token как неаутентифицированный или без прав при получении сектора или бренда актива позиции
- THEN система не возвращает fallback Snapshot и отвечает
400Problem Details с codeINVALID_TBANK_TOKEN, как при отказе token в обходе Portfolio
ADDED Requirements
Requirement: Обновление сектора и эмитента
Система SHALL обновлять сектор и бренд актива позиции при первом обходе по hourly-investment-sync, начавшемся не ранее чем через 24 часа после последнего успешного получения; в остальных обходах значение не меняется. Пока значение не получено, получение повторяется при каждом обходе.
Источник (ненормативно): #48, #48
Scenario: Обход раньше 24 часов
- GIVEN бренд позиции получен в понедельник в 09:00, а затем T-Bank сменил его название
- WHEN обход начинается во вторник в 08:59, то есть через 23 ч 59 мин
- THEN
issuer.nameостаётся прежним
Scenario: Обход через 24 часа
- GIVEN бренд позиции получен в понедельник в 09:00, а затем T-Bank сменил его название
- WHEN обход начинается во вторник в 09:00
- THEN
issuer.nameновое
Requirement: Результат получения сектора и эмитента
Результат получения SHALL определять поле позиции и отсчёт 24 часов по таблице; совпадает ровно одна строка.
| Исход получения | Поле | Отсчёт 24 часов |
|---|---|---|
| Получено значение | Новое значение | Начинается заново |
| Ответ «не найдено» или значения нет | null |
Начинается заново |
| Ответ о другом инструменте или активе | Прежнее значение или null |
Не меняется; получение неуспешно |
| Иная ошибка, кроме отказа token | Прежнее значение или null |
Не меняется; получение неуспешно |
Неуспешное получение SHALL NOT мешать обновлению остальных данных Portfolio; отказ token — по «Свежесть и fallback полного Portfolio Snapshot».
Источник (ненормативно): #48
Scenario: Ответ без значения заменяет известное значение
- GIVEN у позиции известен
sectorenergy, 24 часа прошли - WHEN T-Bank отвечает, что сектора у инструмента нет
- THEN
sector: null
Scenario: «Не найдено» перезапускает отсчёт
- GIVEN у позиции известен
sectorenergy, 24 часа прошли, и обход получает от T-Bank «не найдено» - WHEN следующий обход начинается через час, а T-Bank уже возвращает сектор
financial - THEN
sectorостаётсяnull; значениеfinancialпоявляется при первом обходе, начавшемся не ранее чем через 24 часа после ответа «не найдено»
Scenario: Ответ о другом инструменте
- GIVEN у позиции известен
sectorenergy, 24 часа прошли - WHEN T-Bank возвращает данные другого инструмента
- THEN
sectorостаётсяenergy, а следующий обход повторяет получение
Scenario: Недоступность T-Bank сохраняет известное значение
- GIVEN у позиции известен
sectorenergy, 24 часа прошли - WHEN получение сектора завершается недоступностью T-Bank, а цены и количества получены
- THEN позиция возвращается с
sectorenergy, остальные данные Portfolio обновлены,lastSuccessfulAtновый
Scenario: Значение ещё не известно
- GIVEN новая позиция, сектор которой ни разу не получен успешно
- WHEN получение сектора неуспешно при одном обходе и успешно при следующем
- THEN после первого обхода
sector: null, после следующего значение сектора