From 329f0a86a46b1358f83975714b0c378ebf795e7e Mon Sep 17 00:00:00 2001 From: Sergey Krylov Date: Thu, 27 Aug 2026 00:44:12 +0300 Subject: [PATCH] docs(specs): archive stock ticker change --- .../.openspec.yaml | 2 + .../design.md | 102 ++++++++++++++ .../proposal.md | 35 +++++ .../specs/portfolio-view/spec.md | 125 ++++++++++++++++++ .../tasks.md | 38 ++++++ openspec/specs/portfolio-view/spec.md | 28 +++- 6 files changed, 326 insertions(+), 4 deletions(-) create mode 100644 openspec/changes/archive/2026-08-27-show-stock-tickers-on-position-cards/.openspec.yaml create mode 100644 openspec/changes/archive/2026-08-27-show-stock-tickers-on-position-cards/design.md create mode 100644 openspec/changes/archive/2026-08-27-show-stock-tickers-on-position-cards/proposal.md create mode 100644 openspec/changes/archive/2026-08-27-show-stock-tickers-on-position-cards/specs/portfolio-view/spec.md create mode 100644 openspec/changes/archive/2026-08-27-show-stock-tickers-on-position-cards/tasks.md diff --git a/openspec/changes/archive/2026-08-27-show-stock-tickers-on-position-cards/.openspec.yaml b/openspec/changes/archive/2026-08-27-show-stock-tickers-on-position-cards/.openspec.yaml new file mode 100644 index 0000000..f845845 --- /dev/null +++ b/openspec/changes/archive/2026-08-27-show-stock-tickers-on-position-cards/.openspec.yaml @@ -0,0 +1,2 @@ +schema: wealth-ledger-sdd +created: 2026-08-27 diff --git a/openspec/changes/archive/2026-08-27-show-stock-tickers-on-position-cards/design.md b/openspec/changes/archive/2026-08-27-show-stock-tickers-on-position-cards/design.md new file mode 100644 index 0000000..9d547ec --- /dev/null +++ b/openspec/changes/archive/2026-08-27-show-stock-tickers-on-position-cards/design.md @@ -0,0 +1,102 @@ +## Context + +Portfolio уже нормализуется backend-ом из финансовых данных и metadata +инструмента, после чего frontend получает только OpenAPI-контракт. Metadata +разрешается по instrument UID с fallback на FIGI и уже содержит тикер для +построения отображаемого имени, однако тикер не входит в нормализованную +Portfolio Position. Карточка позиции отображает ISIN независимо от типа +инструмента. + +Изменение расширяет публичный ответ Portfolio nullable presentation metadata и +затрагивает обе стороны committed OpenAPI-контракта. Денежные значения, +расчёты, доступ к счёту, metadata lookup и cache policies остаются прежними. + +## Goals / Non-Goals + +**Goals:** + +- Передавать доступный тикер через нормализованную Portfolio Position без + отдельного источника данных на клиенте. +- Отображать на карточке `STOCK` тикер либо ISIN как определённый fallback. +- Сохранить единый backend-owned API contract и generated frontend types. + +**Non-Goals:** + +- Не менять правила формирования отображаемого имени, порядок, секции или + финансовые данные Portfolio. +- Не менять представление облигаций и иных типов позиций. +- Не вводить новый upstream lookup, cache, endpoint, хранилище или клиентскую + интеграцию с T-Bank. + +## Decisions + +### Nullable ticker остаётся metadata Portfolio Position + +Backend добавляет nullable `ticker` к нормализованной Position и заполняет его +из уже разрешённой metadata инструмента. Пустое значение и отсутствие metadata +нормализуются в `null`, как существующие presentation metadata. Это делает +данные доступными одинаково для всех типов Position, не меняет уже принятый +порядок разрешения metadata и не влияет на финансовый Snapshot. + +Не использовать тикер непосредственно из исходной Portfolio Position: это +обошло бы единый UID/FIGI resolution path и могло бы расходиться с другими +metadata полями. Не запрашивать metadata на frontend: это нарушило бы +server-side boundary T-Bank и создало бы второй источник контрактных данных. + +### OpenAPI — единственный межрепозиторный шов + +DTO backend-а объявляет nullable `ticker`; опубликованный runtime OpenAPI +документ становится источником для committed frontend snapshot и generated +types. Frontend не создаёт ручной transport type и не меняет контракт локально. +Так additive field остаётся синхронным между runtime API и клиентом. + +### Выбор идентификатора принадлежит presentation layer карточки + +Существующая VM продолжает передавать structural Portfolio Position из +generated контракта. Карточка определяет подпись только из уже переданных +`type`, `ticker` и `isin`: для `STOCK` выбирает `ticker ?? isin`, для остальных +типов сохраняет `isin`. Это локальная presentation-логика одного значения, +поэтому не переносит в view fetching, группировку или финансовые вычисления. + +## Delivery Map + +| Area | Status | Architectural outcome or N/A reason | +| --- | --- | --- | +| frontend | `affected` | Получает синхронизированные generated types и отображает на карточке `STOCK` тикер с ISIN fallback, не меняя карточки иных типов. | +| backend | `affected` | Нормализует nullable ticker из существующей metadata и публикует его в Portfolio API/OpenAPI. | +| specs | `affected` | Delta `portfolio-view` закрепляет nullable ticker и type-specific presentation fallback. | +| docs | `affected` | После проверенной реализации feature documentation Portfolio описывает поле ticker и stock-only правило отображения. | + +### Contract Gate Decision + +| Gate | Status | Reason | +| --- | --- | --- | +| runtime-openapi | `required` | Backend публикует additive поле Portfolio API, а frontend потребляет его через committed OpenAPI snapshot и generated types. | + +## Risks / Trade-offs + +- Ticker остаётся best-effort metadata; nullable contract и ISIN fallback не + скрывают Position при его отсутствии. +- Additive публичное поле требует синхронной поставки runtime OpenAPI и + committed frontend snapshot; contract gate предотвращает их расхождение. +- Неэквивалентность тикера и отображаемого имени намеренна: имя сохраняет + существующий приоритет, тикер служит компактной подписью только акции. + +## Migration Plan + +1. Расширить backend-нормализацию и публичный contract nullable ticker без + изменения существующих полей или cache semantics. +2. Синхронизировать generated frontend contract с runtime OpenAPI и применить + type-specific presentation rule в карточке. +3. Обновить as-built Portfolio documentation после подтверждения delivered + поведения. + +### Rollback Contract + +| Field | Decision | +| --- | --- | +| Status | `required` | +| Trigger | Обнаружено, что новое поле или его отображение нарушает совместимость Portfolio API либо неправильно идентифицирует акции. | +| Actions | Сначала вернуть frontend-карточки акций к ISIN. Nullable `ticker` не удалять из backend публичного ответа во время rollback, чтобы не ломать уже синхронизированный клиентский contract; последующее удаление возможно только отдельным compatibility change. | +| Data safety | Тикер — производная metadata текущего in-memory Portfolio Snapshot; миграций, persistent data и денежных расчётов нет. | +| Verification | Подтвердить сохранение ISIN на stock-карточках и соответствие runtime OpenAPI committed frontend snapshot. | diff --git a/openspec/changes/archive/2026-08-27-show-stock-tickers-on-position-cards/proposal.md b/openspec/changes/archive/2026-08-27-show-stock-tickers-on-position-cards/proposal.md new file mode 100644 index 0000000..9307e9d --- /dev/null +++ b/openspec/changes/archive/2026-08-27-show-stock-tickers-on-position-cards/proposal.md @@ -0,0 +1,35 @@ +## Why + +В карточке акции под названием инструмента сейчас показывается ISIN — длинный +международный идентификатор, который неудобен для быстрого распознавания +позиции. Пользователь ожидает видеть привычный биржевой тикер, например `LKOH` +для ЛУКОЙЛ. + +## What Changes + +- Для каждой позиции Portfolio сохранить доступный тикер инструмента наряду с + существующими данными. +- На карточке позиции типа `STOCK` показывать только тикер под названием + инструмента. +- Если для акции тикер недоступен, сохранять отображение её ISIN как fallback. +- Не менять карточки облигаций и других типов позиций, порядок позиций, + финансовые значения, агрегаты Portfolio или навигацию. + +## Capabilities + +### New Capabilities + +Нет. + +### Modified Capabilities + +- `portfolio-view`: Portfolio Position предоставляет ticker, а карточка акции + предпочитает его ISIN с определённым fallback. + +## Impact + +- Пользователь быстрее распознаёт акции в Portfolio по привычному биржевому + тикеру. +- Затрагиваются данные позиции Portfolio и представление карточки акции на + странице счёта; остальные карточки и данные Portfolio сохраняют текущее + поведение. diff --git a/openspec/changes/archive/2026-08-27-show-stock-tickers-on-position-cards/specs/portfolio-view/spec.md b/openspec/changes/archive/2026-08-27-show-stock-tickers-on-position-cards/specs/portfolio-view/spec.md new file mode 100644 index 0000000..eb4aa45 --- /dev/null +++ b/openspec/changes/archive/2026-08-27-show-stock-tickers-on-position-cards/specs/portfolio-view/spec.md @@ -0,0 +1,125 @@ +## 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` — имя файла логотипа бренда инструмента. Имя файла логотипа MUST заполняться из metadata инструмента, когда upstream предоставляет бренд с непустым именем файла логотипа; во всех остальных случаях оно MUST быть `null`. Логотип является presentation metadata: он не влияет на количество, стоимость, агрегаты, успешность или свежесть 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` + +### Requirement: Представление карточек позиций на клиенте +Клиент MUST отображать `STOCK` и `BOND` отдельными секциями. Внутри каждой секции MUST сохраняться upstream-порядок позиций. Позиции иных типов MUST оставаться доступными в отдельной общей секции. + +Карточки секций `STOCK` и `BOND` MUST отображаться по одной в ряду при любой ширине экрана. Карточка акции или облигации MUST показывать визуальный логотип бренда инструмента рядом с отображаемым идентификатором позиции; при отсутствии имени файла логотипа либо неудачной загрузке изображения вместо логотипа MUST отображаться первая буква отображаемого идентификатора позиции. Абсолютный доход в карточке акции или облигации MUST отображаться значением с явным знаком (`+` для положительного, `-` для отрицательного, без знака при нуле) с цветовой индикацией знака и без текстовой подписи доходности. + +Карточка `STOCK` MUST показывать только `ticker` под отображаемым идентификатором позиции, если тикер доступен; при `ticker: null` она MUST сохранять текущее отображение `isin` как fallback. Карточки `BOND` и общей секции иных типов MUST сохранять отображение `isin`. Карточки общей секции иных типов MUST сохранять прежнее поведение: компактная плитка в адаптивном grid (одна колонка на узком экране, несколько колонок на широком), абсолютный доход с текстовой подписью доходности и цветовой индикацией знака, без визуального логотипа. Плитки всех секций MUST показывать отображаемый идентификатор позиции, количество, `currentValue` как главную метрику, `absoluteReturn`, `averagePurchasePrice` и долю позиции среди позиций той же валюты. Отсутствующие метрики MUST быть скрыты, а не отображаться заглушкой. При форматировании денежных значений в русской локали клиент MUST отображать RUB как символ `₽`; другие валюты MUST сохранять понятное обозначение. + +#### Scenario: Отдельные акции и облигации +- **WHEN** Portfolio содержит позиции `STOCK` и `BOND` +- **THEN** клиент отображает их в отдельных секциях «Акции» и «Облигации» без изменения порядка в каждой секции + +#### Scenario: Портфель отображается на мобильном устройстве +- **WHEN** User открывает загруженный Portfolio на узком экране +- **THEN** клиент сохраняет читаемое одноколоночное отображение агрегатов, позиций и Cash Balance + +#### Scenario: Карточка акции с доступным тикером +- **WHEN** позиция `STOCK` имеет непустой `ticker` и ISIN +- **THEN** клиент показывает под отображаемым идентификатором только тикер и не показывает ISIN + +#### Scenario: Карточка акции без тикера +- **WHEN** позиция `STOCK` имеет `ticker: null` и непустой ISIN +- **THEN** клиент показывает ISIN под отображаемым идентификатором + +#### Scenario: Карточка облигации сохраняет ISIN +- **WHEN** позиция `BOND` имеет доступные ticker и ISIN +- **THEN** клиент показывает ISIN и не заменяет его тикером + +#### Scenario: Карточка позиции отображает доступные метрики +- **WHEN** позиция, не относящаяся к `STOCK`, имеет значение текущей стоимости, абсолютного дохода, средней цены покупки и ISIN +- **THEN** клиент показывает идентификатор, количество, текущую стоимость, абсолютный доход, среднюю цену покупки, долю позиции среди позиций той же валюты и ISIN + +#### Scenario: Отсутствующие метрики скрыты +- **WHEN** у позиции отсутствует значение текущей стоимости, средней цены покупки или дохода +- **THEN** клиент скрывает соответствующую метрику вместо отображения заглушки + +#### Scenario: Доход окрашен по знаку +- **WHEN** у позиции абсолютный доход положительный или отрицательный +- **THEN** клиент отображает его зелёным для положительного и красным для отрицательного, без цвета при его отсутствии + +#### Scenario: Доля позиции в валюте +- **WHEN** позиция имеет вычислимую текущую стоимость в валюте +- **THEN** клиент отображает её долю среди всех позиций той же валюты, не включая Cash Balance + +#### Scenario: Одноколоночные секции акций и облигаций +- **WHEN** User открывает загруженный Portfolio с позициями `STOCK` или `BOND` на широком экране +- **THEN** клиент отображает карточки соответствующей секции по одной в ряду + +#### Scenario: Логотип акции или облигации отображается +- **WHEN** позиция `STOCK` или `BOND` содержит непустое имя файла логотипа бренда и изображение успешно загружается +- **THEN** клиент показывает изображение логотипа рядом с отображаемым идентификатором позиции + +#### Scenario: Фолбэк без логотипа у акции или облигации +- **WHEN** у позиции `STOCK` или `BOND` имя файла логотипа отсутствует либо изображение не загружается +- **THEN** вместо логотипа клиент показывает первую букву отображаемого идентификатора позиции + +#### Scenario: Доходность акции или облигации со знаком без подписи +- **WHEN** карточка акции или облигации показывает абсолютный доход +- **THEN** клиент отображает значение с явным знаком `+` или `-` (без знака при нуле) и без текстовой подписи доходности + +#### Scenario: Секция облигаций в едином стиле с акциями +- **WHEN** User открывает загруженный Portfolio с позициями `BOND` +- **THEN** клиент отображает облигации по одной карточке в ряду с визуальным логотипом бренда либо фолбэком первой буквы и абсолютным доходом с явным знаком без текстовой подписи + +#### Scenario: Секция иных типов сохраняет прежний вид +- **WHEN** User открывает загруженный Portfolio с позициями иных типов (не `STOCK` и не `BOND`) +- **THEN** клиент отображает их компактными плитками в адаптивном grid с текстовой подписью доходности и без визуального логотипа diff --git a/openspec/changes/archive/2026-08-27-show-stock-tickers-on-position-cards/tasks.md b/openspec/changes/archive/2026-08-27-show-stock-tickers-on-position-cards/tasks.md new file mode 100644 index 0000000..559d922 --- /dev/null +++ b/openspec/changes/archive/2026-08-27-show-stock-tickers-on-position-cards/tasks.md @@ -0,0 +1,38 @@ +## 1. Backend Portfolio contract + +- [x] 1.1 [backend] Добавить nullable `ticker` в нормализованную Portfolio Position и её OpenAPI DTO, заполняя его из уже разрешённой metadata с `null` для пустого или недоступного значения; расширить focused Portfolio service tests для доступного и отсутствующего ticker. + - **Covers:** `portfolio-view` — `Нормализованный состав Portfolio` + - **Verification:** `npm test -- portfolio.service.spec.ts --runInBand` в `backend` подтверждает ticker и null fallback в ответе Portfolio. +- [x] 1.2 [backend] Опубликовать обновлённый runtime OpenAPI из backend и выполнить canonical backend quality gate. + - **Covers:** `N/A` — runtime schema — технический источник frontend snapshot для утверждённого additive public contract. + - **Verification:** `npm run verify` в `backend`. + +## 2. Frontend generated contract и карточка акции + +- [x] 2.1 [frontend] Скопировать опубликованный backend OpenAPI document в committed snapshot и регенерировать frontend types штатной командой без ручной правки generated файла. + - **Covers:** `N/A` — generated contract синхронизирует утверждённое backend behavior с frontend consumer и не вводит самостоятельное observable behavior. + - **Verification:** `npm run openapi:generate` в `frontend`; snapshot и generated `PortfolioPositionDto` содержат nullable `ticker`. +- [x] 2.2 [frontend] Изменить presentation карточки так, чтобы `STOCK` показывал ticker с ISIN fallback, а `BOND` и другие типы сохраняли ISIN; добавить focused regression coverage этих трёх случаев. + - **Covers:** `portfolio-view` — `Представление карточек позиций на клиенте` + - **Verification:** focused frontend test card/account page подтверждает ticker-only stock, ISIN fallback и неизменённую облигацию. +- [x] 2.3 [frontend] Выполнить canonical frontend gate после обновления generated contract и presentation. + - **Covers:** `N/A` — canonical gate проверяет type safety, UI tests, boundaries и production build изменённого frontend. + - **Verification:** `npm run verify` в `frontend`. + +## 3. As-built documentation и canonical specs + +- [x] 3.1 [docs] После проверенной реализации обновить Portfolio feature documentation: nullable ticker в Position и правило его отображения только на карточке акции с ISIN fallback. + - **Covers:** `N/A` — as-built documentation фиксирует delivered behavior, но не является source of normative requirement. + - **Verification:** `npm run verify` в `docs` и scoped review, что документация не описывает планировочные артефакты. +- [x] 3.2 [specs] Подтвердить, что approved Proposal, Delta Specs и Design остаются единственным canonical описанием change до archive. + - **Covers:** `N/A` — change artifacts уже владеют спецификацией behavior; задача не добавляет отдельного runtime behavior. + - **Verification:** `openspec validate show-stock-tickers-on-position-cards --type change --strict --store wealth-ledger-specs` в `specs`. + +## 4. Cross-area contract и completion review + +- [x] 4.1 [workspace] Проверить обязательное совпадение backend runtime OpenAPI и committed frontend snapshot после их синхронизации. + - **Covers:** `N/A` — cross-repository contract gate подтверждает delivery Decision, не создавая дополнительное observable behavior. + - **Verification:** `npm run test:openapi-contract` в `backend`. +- [x] 4.2 [workspace] Выполнить semantic verification против Proposal, Delta Specs и Design, затем independent read-only review backend mapping, generated contract, stock-card fallback и as-built docs. + - **Covers:** `N/A` — cross-area verification подтверждает реализацию утверждённого behavior и rollback assumptions. + - **Verification:** review фиксирует проверенные области и не содержит Critical или Important findings перед archive. diff --git a/openspec/specs/portfolio-view/spec.md b/openspec/specs/portfolio-view/spec.md index 5fdf955..849a9a2 100644 --- a/openspec/specs/portfolio-view/spec.md +++ b/openspec/specs/portfolio-view/spec.md @@ -22,7 +22,7 @@ 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. -Каждая 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 `logoName` — имя файла логотипа бренда инструмента. Имя файла логотипа MUST заполняться из metadata инструмента, когда upstream предоставляет бренд с непустым именем файла логотипа; во всех остальных случаях оно MUST быть `null`. Логотип является presentation metadata: он не влияет на количество, стоимость, агрегаты, успешность или свежесть 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` — имя файла логотипа бренда инструмента. Имя файла логотипа MUST заполняться из metadata инструмента, когда upstream предоставляет бренд с непустым именем файла логотипа; во всех остальных случаях оно MUST быть `null`. Логотип является presentation metadata: он не влияет на количество, стоимость, агрегаты, успешность или свежесть Portfolio Snapshot. Система MUST искать metadata по instrument UID при его наличии, затем по FIGI. Отображаемый идентификатор MUST предпочитать name, затем ticker, а при недоступности metadata использовать FIGI, не скрывая финансовые значения. #### Scenario: Позиция с неизвестным upstream-типом - **WHEN** upstream возвращает обычную ненулевую позицию с нераспознанным типом инструмента @@ -52,6 +52,14 @@ Portfolio Snapshot является результатом наблюдения - **WHEN** у инструмента позиции нет ISIN либо metadata недоступна - **THEN** система возвращает позицию с `isin: null`, не скрывая финансовые значения +#### Scenario: Тикер инструмента доступен +- **WHEN** metadata инструмента позиции содержит непустой `ticker` +- **THEN** система возвращает его в поле `ticker` позиции + +#### Scenario: Тикер инструмента отсутствует +- **WHEN** у инструмента позиции нет ticker либо metadata недоступна +- **THEN** система возвращает позицию с `ticker: null`, не скрывая финансовые значения + #### Scenario: Имя файла логотипа бренда доступно - **WHEN** metadata инструмента позиции содержит бренд с непустым именем файла логотипа - **THEN** система возвращает его в поле `logoName` позиции @@ -124,7 +132,7 @@ Portfolio Snapshot является результатом наблюдения Карточки секций `STOCK` и `BOND` MUST отображаться по одной в ряду при любой ширине экрана. Карточка акции или облигации MUST показывать визуальный логотип бренда инструмента рядом с отображаемым идентификатором позиции; при отсутствии имени файла логотипа либо неудачной загрузке изображения вместо логотипа MUST отображаться первая буква отображаемого идентификатора позиции. Абсолютный доход в карточке акции или облигации MUST отображаться значением с явным знаком (`+` для положительного, `-` для отрицательного, без знака при нуле) с цветовой индикацией знака и без текстовой подписи доходности. -Карточки общей секции иных типов MUST сохранять прежнее поведение: компактная плитка в адаптивном grid (одна колонка на узком экране, несколько колонок на широком), абсолютный доход с текстовой подписью доходности и цветовой индикацией знака, без визуального логотипа. Плитки всех секций MUST показывать отображаемый идентификатор позиции, количество, `currentValue` как главную метрику, `absoluteReturn`, `averagePurchasePrice`, долю позиции среди позиций той же валюты и `isin`. Отсутствующие метрики MUST быть скрыты, а не отображаться заглушкой. При форматировании денежных значений в русской локали клиент MUST отображать RUB как символ `₽`; другие валюты MUST сохранять понятное обозначение. +Карточка `STOCK` MUST показывать только `ticker` под отображаемым идентификатором позиции, если тикер доступен; при `ticker: null` она MUST сохранять текущее отображение `isin` как fallback. Карточки `BOND` и общей секции иных типов MUST сохранять отображение `isin`. Карточки общей секции иных типов MUST сохранять прежнее поведение: компактная плитка в адаптивном grid (одна колонка на узком экране, несколько колонок на широком), абсолютный доход с текстовой подписью доходности и цветовой индикацией знака, без визуального логотипа. Плитки всех секций MUST показывать отображаемый идентификатор позиции, количество, `currentValue` как главную метрику, `absoluteReturn`, `averagePurchasePrice` и долю позиции среди позиций той же валюты. Отсутствующие метрики MUST быть скрыты, а не отображаться заглушкой. При форматировании денежных значений в русской локали клиент MUST отображать RUB как символ `₽`; другие валюты MUST сохранять понятное обозначение. #### Scenario: Отдельные акции и облигации - **WHEN** Portfolio содержит позиции `STOCK` и `BOND` @@ -134,9 +142,21 @@ Portfolio Snapshot является результатом наблюдения - **WHEN** User открывает загруженный Portfolio на узком экране - **THEN** клиент сохраняет читаемое одноколоночное отображение агрегатов, позиций и Cash Balance +#### Scenario: Карточка акции с доступным тикером +- **WHEN** позиция `STOCK` имеет непустой `ticker` и ISIN +- **THEN** клиент показывает под отображаемым идентификатором только тикер и не показывает ISIN + +#### Scenario: Карточка акции без тикера +- **WHEN** позиция `STOCK` имеет `ticker: null` и непустой ISIN +- **THEN** клиент показывает ISIN под отображаемым идентификатором + +#### Scenario: Карточка облигации сохраняет ISIN +- **WHEN** позиция `BOND` имеет доступные ticker и ISIN +- **THEN** клиент показывает ISIN и не заменяет его тикером + #### Scenario: Карточка позиции отображает доступные метрики -- **WHEN** позиция имеет значение текущей стоимости, абсолютного дохода, средней цены покупки и ISIN -- **THEN** клиент показывает идентификатор, количество, текущую стоимость, абсолютный доход, среднюю цену покупки, долю среди позиций той же валюты и ISIN +- **WHEN** позиция, не относящаяся к `STOCK`, имеет значение текущей стоимости, абсолютного дохода, средней цены покупки и ISIN +- **THEN** клиент показывает идентификатор, количество, текущую стоимость, абсолютный доход, среднюю цену покупки, долю позиции среди позиций той же валюты и ISIN #### Scenario: Отсутствующие метрики скрыты - **WHEN** у позиции отсутствует значение текущей стоимости, средней цены покупки или дохода