spec: align Events and CURRENCY portfolio behavior (WealthLedger/specs#60) #82

Merged
codex-bot merged 2 commits from fix/63-events-screen-filter-spec into develop 2026-11-05 07:16:09 +03:00
3 changed files with 16 additions and 7 deletions
@@ -91,13 +91,16 @@ Instrument в Future Cash Event MUST иметь тот же доменный с
Страница Brokerage Account MUST предоставлять действие перехода на отдельную Страница Brokerage Account MUST предоставлять действие перехода на отдельную
страницу «Будущие события». Эта страница MUST показывать все полученные события страницу «Будущие события». Эта страница MUST показывать все полученные события
компактным списком без фильтров, пагинации и суммарного итога списка, с датой, типом, компактным списком за выбранный период без пагинации и суммарного итога списка, с датой, типом,
инструментом и прогнозной суммой в исходной валюте, если она известна. Прогнозный итог инструментом и прогнозной суммой в исходной валюте, если она известна. Прогнозный итог
визуальной группы режима «Все счета» не является суммарным итогом списка. Страница визуальной группы режима «Все счета» не является суммарным итогом списка. Страница
MUST объяснять, что прогноз основан на позиции счёта, указан до налогов и может MUST объяснять, что прогноз основан на позиции счёта, указан до налогов и может
быть неполным из-за доступности данных источника. Это предметное ограничение быть неполным из-за доступности данных источника. Это предметное ограничение
прогноза MUST NOT превращаться в отчёт о технической синхронизации. прогноза MUST NOT превращаться в отчёт о технической синхронизации.
Страница MUST показывать исходные `calculatedAt` и включительное окно сохранённого прогноза,
в том числе когда выбранный период отличается от окна прогноза.
При временном сбое обновления страница MUST сохранять ранее доступный результат При временном сбое обновления страница MUST сохранять ранее доступный результат
той же выборки с его исходными `calculatedAt` и окном, без индикации той же выборки с его исходными `calculatedAt` и окном, без индикации
собственного чтения и ручного повтора до исчерпания автоматических повторов собственного чтения и ручного повтора до исчерпания автоматических повторов
@@ -110,11 +113,11 @@ MUST скрывать личные данные независимо от нал
#### Scenario: Просмотр ближайших событий #### Scenario: Просмотр ближайших событий
- **WHEN** User открывает «Будущие события» из обзора Brokerage Account - **WHEN** User открывает «Будущие события» из обзора Brokerage Account
- **THEN** он видит отсортированный список событий ближайших 30 дней, где offer обозначен без суммы - **THEN** он видит отсортированный список событий выбранного периода (по умолчанию -- ближайшие 7 дней), где offer обозначен без суммы
#### Scenario: Пустое состояние страницы #### Scenario: Пустое состояние страницы
- **WHEN** запрос будущих событий успешно возвращает полный пустой список - **WHEN** запрос будущих событий успешно возвращает полный пустой список
- **THEN** страница сообщает, что на ближайшие 30 дней событий не найдено, и объясняет предметные ограничения покрытия источника - **THEN** страница сообщает, что за выбранный период событий не найдено, и объясняет предметные ограничения покрытия источника
#### Scenario: Ошибка страницы #### Scenario: Ошибка страницы
- **WHEN** запрос будущих событий завершается временной ошибкой - **WHEN** запрос будущих событий завершается временной ошибкой
@@ -8,6 +8,8 @@
### Requirement: Полные оценки Portfolio сохраняются как история ### Requirement: Полные оценки Portfolio сохраняются как история
Система SHALL сохранять для каждого доступного Brokerage Account точку оценки по каждой исходной валюте только после успешного получения полного Portfolio с вычислимой стоимостью всех его позиций. Точка MUST содержать точное значение без валютной конвертации и фактическое время успешного получения Portfolio. Система MUST NOT подставлять ноль за отсутствующую стоимость и MUST NOT создавать точку для неполной оценки. Повторная публикация одной попытки MUST NOT создавать дубликат точки. Система SHALL сохранять для каждого доступного Brokerage Account точку оценки по каждой исходной валюте только после успешного получения полного Portfolio с вычислимой стоимостью всех его позиций. Точка MUST содержать точное значение без валютной конвертации и фактическое время успешного получения Portfolio. Система MUST NOT подставлять ноль за отсутствующую стоимость и MUST NOT создавать точку для неполной оценки. Повторная публикация одной попытки MUST NOT создавать дубликат точки.
Точки, сохранённые до исправления двойного учёта `CURRENCY`/Cash Balance (WealthLedger/backend#140), сброшены миграцией. История оценок начинается заново после её применения; старые точки MUST NOT показываться как сопоставимые с новыми.
#### Scenario: Полная мультивалютная оценка #### Scenario: Полная мультивалютная оценка
- **WHEN** успешный полный Portfolio содержит вычислимые итоги RUB и USD - **WHEN** успешный полный Portfolio содержит вычислимые итоги RUB и USD
- **THEN** система сохраняет две отдельные точки с исходными значениями и валютами - **THEN** система сохраняет две отдельные точки с исходными значениями и валютами
@@ -20,7 +20,7 @@ Portfolio Snapshot является результатом наблюдения
- **THEN** система отвечает `404` без Portfolio Snapshot - **THEN** система отвечает `404` без Portfolio Snapshot
### Requirement: Нормализованный состав Portfolio ### 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. Система MUST возвращать только обычные Portfolio Position с ненулевым количеством и доступные Cash Balance с ненулевой суммой. Cash Balance MUST быть отдельным от Portfolio Position элементом и агрегироваться до одного значения на валюту. Portfolio Position с типом `CURRENCY` MUST сохранять исходное количество и идентификаторы для контракта, но отражает тот же кэш, что и Cash Balance, и MUST NOT повторно учитываться в валютных итогах и структуре. Клиент MUST показывать этот кэш один раз как Cash Balance в разделе «Деньги», без отдельной карточки `CURRENCY`. Порядок 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, не скрывая финансовые значения. Каждая 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, не скрывая финансовые значения.
@@ -124,7 +124,11 @@ Portfolio Snapshot является результатом наблюдения
### Requirement: Точные значения и валютные агрегаты ### Requirement: Точные значения и валютные агрегаты
Все количества и финансовые значения в Portfolio Snapshot MUST передаваться десятичными строками без экспоненциальной записи и без незначащих завершающих нулей. Денежное значение MUST быть единым Money-значением с ISO-кодом валюты и десятичной строкой значения. Система MUST корректно нормализовать отрицательные целую и дробную части upstream-значений. Отсутствующее значение MUST передаваться как `null`, а не как ноль. Все количества и финансовые значения в Portfolio Snapshot MUST передаваться десятичными строками без экспоненциальной записи и без незначащих завершающих нулей. Денежное значение MUST быть единым Money-значением с ISO-кодом валюты и десятичной строкой значения. Система MUST корректно нормализовать отрицательные целую и дробную части upstream-значений. Отсутствующее значение MUST передаваться как `null`, а не как ноль.
`currentValue` Portfolio Position MUST быть точным произведением количества на текущую цену при наличии валидных множителей и валюты цены. `absoluteReturn` MUST быть точной величиной `(currentPrice - averagePurchasePrice) * quantity` только когда обе цены имеют одинаковую известную валюту. Позиция без вычислимой текущей стоимости MUST оставаться в ответе, но MUST NOT входить в агрегаты. `totalsByCurrency` MUST содержать точную сумму currentValue позиций и Cash Balance в каждой валюте без валютной конвертации. `currentValue` Portfolio Position MUST быть точным произведением количества на текущую цену при наличии валидных множителей и валюты цены. `absoluteReturn` MUST быть точной величиной `(currentPrice - averagePurchasePrice) * quantity` только когда обе цены имеют одинаковую известную валюту. Позиция без вычислимой текущей стоимости MUST оставаться в ответе, но MUST NOT входить в агрегаты. `totalsByCurrency` MUST содержать точную сумму currentValue позиций, кроме `CURRENCY`, и Cash Balance в каждой валюте без валютной конвертации. Структура Portfolio MUST учитывать `CURRENCY` только как `CASH` из Cash Balance, без второго вклада.
#### Scenario: Валютная позиция отражает денежный остаток
- **WHEN** Portfolio содержит `CURRENCY` position стоимостью 100 RUB и Cash Balance 100 RUB
- **THEN** система сохраняет `CURRENCY` position с количеством в `positions`, но возвращает итог 100 RUB и один вклад `CASH` 100 RUB в структуре; клиент показывает 100 RUB в «Деньги» без отдельной валютной карточки
#### Scenario: Мультивалютный Portfolio #### Scenario: Мультивалютный Portfolio
- **WHEN** Portfolio содержит финансовые значения в RUB и USD - **WHEN** Portfolio содержит финансовые значения в RUB и USD
@@ -282,7 +286,7 @@ Portfolio и предложить возврат к списку счетов.
- **THEN** клиент показывает ISIN и не заменяет его тикером - **THEN** клиент показывает ISIN и не заменяет его тикером
#### Scenario: Карточка позиции отображает доступные метрики #### Scenario: Карточка позиции отображает доступные метрики
- **WHEN** позиция, не относящаяся к `STOCK`, имеет значение текущей стоимости, абсолютного дохода, средней цены покупки и ISIN - **WHEN** позиция, не относящаяся к `STOCK` или `CURRENCY`, имеет значение текущей стоимости, абсолютного дохода, средней цены покупки и ISIN
- **THEN** клиент показывает идентификатор, количество, текущую стоимость, абсолютный доход, среднюю цену покупки, долю позиции среди позиций той же валюты и ISIN - **THEN** клиент показывает идентификатор, количество, текущую стоимость, абсолютный доход, среднюю цену покупки, долю позиции среди позиций той же валюты и ISIN
#### Scenario: Отсутствующие метрики скрыты #### Scenario: Отсутствующие метрики скрыты
@@ -295,7 +299,7 @@ Portfolio и предложить возврат к списку счетов.
#### Scenario: Доля позиции в валюте #### Scenario: Доля позиции в валюте
- **WHEN** позиция имеет вычислимую текущую стоимость в валюте - **WHEN** позиция имеет вычислимую текущую стоимость в валюте
- **THEN** клиент отображает её долю среди всех позиций той же валюты, не включая Cash Balance - **THEN** клиент отображает её долю среди всех отображаемых позиций той же валюты, не включая Cash Balance и зеркальные `CURRENCY` positions
#### Scenario: Одноколоночные секции акций и облигаций #### Scenario: Одноколоночные секции акций и облигаций
- **WHEN** User открывает загруженный Portfolio с позициями `STOCK` или `BOND` на широком экране - **WHEN** User открывает загруженный Portfolio с позициями `STOCK` или `BOND` на широком экране