## 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. |