103 lines
7.4 KiB
Markdown
103 lines
7.4 KiB
Markdown
## 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. |
|