From 9cb1c585977abac7f861072b335c9fa0ddcd3c4f Mon Sep 17 00:00:00 2001 From: Sergey Krylov Date: Thu, 5 Nov 2026 07:13:55 +0300 Subject: [PATCH 1/2] spec: align events screen with date filters (WealthLedger/frontend#63) --- .../brokerage/brokerage-account-upcoming-events/spec.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/openspec/specs/brokerage/brokerage-account-upcoming-events/spec.md b/openspec/specs/brokerage/brokerage-account-upcoming-events/spec.md index 397e8e8..b7011e5 100644 --- a/openspec/specs/brokerage/brokerage-account-upcoming-events/spec.md +++ b/openspec/specs/brokerage/brokerage-account-upcoming-events/spec.md @@ -91,13 +91,16 @@ Instrument в Future Cash Event MUST иметь тот же доменный с Страница Brokerage Account MUST предоставлять действие перехода на отдельную страницу «Будущие события». Эта страница MUST показывать все полученные события -компактным списком без фильтров, пагинации и суммарного итога списка, с датой, типом, +компактным списком за выбранный период без пагинации и суммарного итога списка, с датой, типом, инструментом и прогнозной суммой в исходной валюте, если она известна. Прогнозный итог визуальной группы режима «Все счета» не является суммарным итогом списка. Страница MUST объяснять, что прогноз основан на позиции счёта, указан до налогов и может быть неполным из-за доступности данных источника. Это предметное ограничение прогноза MUST NOT превращаться в отчёт о технической синхронизации. +Страница MUST показывать исходные `calculatedAt` и включительное окно сохранённого прогноза, +в том числе когда выбранный период отличается от окна прогноза. + При временном сбое обновления страница MUST сохранять ранее доступный результат той же выборки с его исходными `calculatedAt` и окном, без индикации собственного чтения и ручного повтора до исчерпания автоматических повторов @@ -110,11 +113,11 @@ MUST скрывать личные данные независимо от нал #### Scenario: Просмотр ближайших событий - **WHEN** User открывает «Будущие события» из обзора Brokerage Account -- **THEN** он видит отсортированный список событий ближайших 30 дней, где offer обозначен без суммы +- **THEN** он видит отсортированный список событий выбранного периода (по умолчанию -- ближайшие 7 дней), где offer обозначен без суммы #### Scenario: Пустое состояние страницы - **WHEN** запрос будущих событий успешно возвращает полный пустой список -- **THEN** страница сообщает, что на ближайшие 30 дней событий не найдено, и объясняет предметные ограничения покрытия источника +- **THEN** страница сообщает, что за выбранный период событий не найдено, и объясняет предметные ограничения покрытия источника #### Scenario: Ошибка страницы - **WHEN** запрос будущих событий завершается временной ошибкой -- 2.54.0 From 1f22510cb7ca2857efde27ae8a7e980bb5c7fce3 Mon Sep 17 00:00:00 2001 From: Sergey Krylov Date: Thu, 5 Nov 2026 07:14:05 +0300 Subject: [PATCH 2/2] spec: count currency mirrors once in portfolio (WealthLedger/specs#60) --- .../specs/portfolio/portfolio-value-history/spec.md | 2 ++ openspec/specs/portfolio/portfolio-view/spec.md | 12 ++++++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/openspec/specs/portfolio/portfolio-value-history/spec.md b/openspec/specs/portfolio/portfolio-value-history/spec.md index b3eb22d..54e9404 100644 --- a/openspec/specs/portfolio/portfolio-value-history/spec.md +++ b/openspec/specs/portfolio/portfolio-value-history/spec.md @@ -8,6 +8,8 @@ ### Requirement: Полные оценки Portfolio сохраняются как история Система SHALL сохранять для каждого доступного Brokerage Account точку оценки по каждой исходной валюте только после успешного получения полного Portfolio с вычислимой стоимостью всех его позиций. Точка MUST содержать точное значение без валютной конвертации и фактическое время успешного получения Portfolio. Система MUST NOT подставлять ноль за отсутствующую стоимость и MUST NOT создавать точку для неполной оценки. Повторная публикация одной попытки MUST NOT создавать дубликат точки. +Точки, сохранённые до исправления двойного учёта `CURRENCY`/Cash Balance (WealthLedger/backend#140), сброшены миграцией. История оценок начинается заново после её применения; старые точки MUST NOT показываться как сопоставимые с новыми. + #### Scenario: Полная мультивалютная оценка - **WHEN** успешный полный Portfolio содержит вычислимые итоги RUB и USD - **THEN** система сохраняет две отдельные точки с исходными значениями и валютами diff --git a/openspec/specs/portfolio/portfolio-view/spec.md b/openspec/specs/portfolio/portfolio-view/spec.md index 0417b0b..b6c6532 100644 --- a/openspec/specs/portfolio/portfolio-view/spec.md +++ b/openspec/specs/portfolio/portfolio-view/spec.md @@ -20,7 +20,7 @@ Portfolio Snapshot является результатом наблюдения - **THEN** система отвечает `404` без Portfolio Snapshot ### 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, не скрывая финансовые значения. @@ -124,7 +124,11 @@ Portfolio Snapshot является результатом наблюдения ### Requirement: Точные значения и валютные агрегаты Все количества и финансовые значения в 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 - **WHEN** Portfolio содержит финансовые значения в RUB и USD @@ -282,7 +286,7 @@ Portfolio и предложить возврат к списку счетов. - **THEN** клиент показывает ISIN и не заменяет его тикером #### Scenario: Карточка позиции отображает доступные метрики -- **WHEN** позиция, не относящаяся к `STOCK`, имеет значение текущей стоимости, абсолютного дохода, средней цены покупки и ISIN +- **WHEN** позиция, не относящаяся к `STOCK` или `CURRENCY`, имеет значение текущей стоимости, абсолютного дохода, средней цены покупки и ISIN - **THEN** клиент показывает идентификатор, количество, текущую стоимость, абсолютный доход, среднюю цену покупки, долю позиции среди позиций той же валюты и ISIN #### Scenario: Отсутствующие метрики скрыты @@ -295,7 +299,7 @@ Portfolio и предложить возврат к списку счетов. #### Scenario: Доля позиции в валюте - **WHEN** позиция имеет вычислимую текущую стоимость в валюте -- **THEN** клиент отображает её долю среди всех позиций той же валюты, не включая Cash Balance +- **THEN** клиент отображает её долю среди всех отображаемых позиций той же валюты, не включая Cash Balance и зеркальные `CURRENCY` positions #### Scenario: Одноколоночные секции акций и облигаций - **WHEN** User открывает загруженный Portfolio с позициями `STOCK` или `BOND` на широком экране -- 2.54.0