## Context `GET /api/v1/brokerage-accounts` уже разрешает текущее T-Bank connection и поддерживаемые счета через `TbankAccessService`, сохраняя upstream-порядок и существующее отображение публичных ошибок. Получение Portfolio использует ту же границу доступа и рассчитывает точные итоги по каждой валюте из значений позиций `GetPortfolio` и денежных остатков `GetPositions`. Текущая нормализация также обогащает позиции для экрана деталей счёта, что не нужно для сводки списка счетов. Frontend уже потребляет сгенерированный OpenAPI-тип через один аутентифицированный React Query hook с 60-секундной политикой актуальности. Его page VM сохраняет успешный query snapshot во время фонового refetch и выбирает известные error-состояния списка. Сейчас entity рендерит двухколоночную коллекцию ссылок только с метаданными счета. Новый контракт должен сохранить существующую семантику connection, query, навигации и клавиатуры, обогащая каждый элемент без представления разных валют как единого значения. ## Goals / Non-Goals **Goals:** - Расширить существующий контракт списка явной nullable-сводкой Portfolio для каждого счёта, сохранив policy доступа к счетам и upstream-порядок. - Переиспользовать точный сохраняющий валюты расчёт итогов Portfolio и для snapshot деталей, и для сводок списка без работы с метаданными позиций. - Локализовать временный upstream-сбой на сводке затронутого счёта, сохранив установленную семантику ошибок connection и token на уровне списка. - Сохранить существующий 60-секундный React Query-кэш и policy Client Cached Snapshot, выражая состояния сводки через текущую границу page VM и presentation. - Обновить as-built документацию только после проверки поставленного поведения. **Non-Goals:** - FX-conversion, межвалютный или межсчётный агрегат, отдельное отображение Cash Balance, сортировка/фильтрация/настройки списка или постоянное хранение Portfolio. - Переработка Portfolio на странице деталей счёта, управления T-Bank connection, resilience policy T-Bank client или глобальной frontend fetch policy. ## Decisions ### D1. Обогатить каждый нормализованный счёт nullable-сводкой на существующей границе списка `TbankAccessService` продолжит владеть разрешением current connection, policy поддерживаемых счетов, обработкой token и начальным вызовом `GetAccounts`. Поток списка сделает разрешённый token и ID поддерживаемых upstream-счетов доступными `BrokerageAccountsService`, который сохранит ответственность за формирование ответа списка. Для каждого поддерживаемого счёта он параллельно запросит два upstream-входа Portfolio и сохранит нормализованный счёт на его исходной позиции. Работа по каждому счёту будет собрана без изменения порядка счетов. Результат `TBANK_UNAVAILABLE` любого из входов сводки создаёт `portfolioSummary: null` только для этого счёта. Начальное разрешение connection, обнаружение счетов, перевод invalid-token и непредвиденные ошибки сохраняют текущее поведение Problem Details на уровне списка; они не превращаются молча в частичный успех. *Альтернатива:* вызывать public Portfolio service страницы деталей для каждого счёта. Отклонено, поскольку это свяжет список с cache snapshot деталей, семантикой timestamp и stale-fallback и вызовет нерелевантное обогащение метаданных Instrument. *Альтернатива:* делать любую неуспешную сводку ошибкой списка. Отклонено, поскольку это противоречит требуемому поведению частичной доступности и скрывает в ином случае доступную навигацию к счёту. ### D2. Извлечь общий normalizer точных итогов, а не вторую модель Portfolio Небольшой backend-owned normalizer примет upstream-значения позиций и денежные остатки и вернёт итоги `Money[]`, сгруппированные по нормализованной исходной валюте. Он сохранит существующую decimal-арифметику и правила включения: нулевые значения или значения без валюты исключаются; валидные текущие значения позиций и Cash Balance в одной валюте точно складываются; FX-conversion не выполняется. Portfolio service деталей использует helper при формировании существующего snapshot, а поток списка обернёт тот же результат как `{ totalsByCurrency }`. Это сохраняет денежный инвариант в одном месте и устраняет зависимость от маппинга позиций Portfolio, запросов метаданных, timestamp или cache-поведения. Пустой успешный upstream Portfolio становится пустым массивом итогов и, следовательно, доступной non-null сводкой списка. *Альтернатива:* дублировать текущий aggregation loop в service списка. Отклонено, поскольку две реализации точных денежных расчётов могут разойтись на edge cases денежных остатков или валют. *Альтернатива:* возвращать в списке полный `PortfolioDto`. Отклонено, поскольку контракт списка не требует ни позиций, ни метаданных snapshot/cache и раскроет избыточно дорогой payload. ### D3. Сделать OpenAPI DTO и generated client единственным транспортным изменением `BrokerageAccountDto` получает nullable `portfolioSummary`, единственное поле которой — `totalsByCurrency: MoneyDto[]`. Swagger/OpenAPI описывает эту форму, после чего frontend snapshot и generated types обновляются установленным workflow runtime contract. Frontend-код продолжает выводить тип счёта из сгенерированных `paths`, не создавая параллельный transport interface. Это совместимое аддитивное изменение ответа, но оно остаётся изменением публичного контракта и требует cross-repository gate runtime/OpenAPI. ### D4. Сохранить query-поведение списка; подготовить представление карточек в page VM Существующие query key brokerage-account, 60-секундный `staleTime`, policy без retry и invalidation после замены/удаления T-Bank connection не изменяются. Accounts page VM сохранит текущее top-level выделение loading/error/empty/loaded и поведение cached snapshot. Он подготовит ограниченную display model каждой карточки из generated account: не более двух итогов исходных валют и явный счётчик/индикатор остальных валют либо раздельные локальные состояния недоступной и пустой сводки. Presentation component списка счетов станет одноколоночной последовательностью широких карточек-ссылок. Каждая ссылка сохраняет навигацию по всей карточке и native keyboard focus и показывает локализованные тип, имя, сводку, вторичные метаданные даты открытия и видимое действие открытия. Presentation-код форматирует одно переданное значение Money существующим shared formatter; он не агрегирует и не конвертирует значения. *Альтернатива:* рассчитывать видимые два итога в entity view. Отклонено, поскольку выбор и подсчёт значений из коллекции — derived presentation data, принадлежащие page VM согласно frontend VM boundary. *Альтернатива:* ввести новый client cache или независимо получать Portfolio каждого счёта. Отклонено, поскольку обогащённый endpoint списка уже передаёт нужное компактное наблюдение, а отдельные query добавят fan-out, loading states и пути invalidation. ## Delivery Map | Area | Status | Architectural outcome or N/A reason | | --- | --- | --- | | frontend | `affected` | Regenerated list response type, существующие query policy/invalidation, page VM card display models и доступные одноколоночные summary cards реализуют обогащённые состояния списка. | | backend | `affected` | Существующая account access boundary поставляет поддерживаемые счета; общий normalizer точных итогов Portfolio и orchestration списка формируют сводки по каждому счёту с локальной обработкой upstream-недоступности и аддитивным DTO. | | specs | `affected` | Этот change хранит утверждённые Proposal, Delta Specs, Design и последующие implementation Tasks в canonical store. | | docs | `affected` | После поставки as-built Brokerage Accounts и релевантная system documentation описывают фактический обогащённый контракт списка, частичную доступность сводки и неизменную границу наблюдения current connection. | ### Contract Gate Decision | Gate | Status | Reason | | --- | --- | --- | | runtime-openapi | `required` | Backend изменяет публичный ответ списка, а frontend потребляет его generated OpenAPI snapshot. | ## Risks / Trade-offs - **Upstream fan-out растёт с количеством поддерживаемых счетов.** Список выполняет два Portfolio RPC на счёт, но использует существующие client-wide rate limiter, deadline и retry policy вместо создания другого механизма concurrency. Временно недоступная отдельная сводка деградирует локально. - **Расхождение агрегатов сводки и деталей.** Общая точная нормализация и фокусированные money tests исключают разную обработку Cash Balance, нулевых сумм и исходных валют. - **Частичный ответ может скрыть невалидные учётные данные.** Локальным для сводки является только известный результат недоступности; token и другие сбои уровня списка проходят через существующий error translation. - **Широкие карточки могут стать хуже для сканирования на малых экранах.** Одноколоночный layout сохраняет компактные вторичные метаданные, ограничивает отображаемые валюты двумя и явно обозначает пропущенные. - **Кэшированные данные могут быть временно устаревшими.** Это сохраняет установленный trade-off 60-секундного Client Cached Snapshot: фоновое обновление и его предупреждение остаются видимыми, а известные данные не очищаются. ## Migration Plan 1. Добавить компактный summary DTO и разделить точную нормализацию итогов Portfolio между существующим потоком деталей и обогащённым потоком списка счетов. 2. Расширить orchestration списка для получения сводок внутри существующей T-Bank access и resilience boundary, сохранив порядок счетов и отображая в `null` только недоступность отдельного счёта. 3. Перегенерировать frontend OpenAPI snapshot/types из работающего backend и пройти runtime contract gate. 4. Адаптировать существующие consumer account query, page VM, карточки, переводы и risk-based tests, сохранив loading, error, empty, cache и invalidation behavior. 5. После проверки реализации обновить as-built feature/system documentation до поставленных контракта и поведения. ### Rollback Contract | Field | Decision | | --- | --- | | Status | `required` | | Reason | Аддитивное поле ответа — публичный API contract, потребляемый frontend. | | Trigger | Release-blocking defect в доступности списка счетов, точности денежной сводки или совместимости с generated client. | | Actions | Одновременно откатить frontend consumption и backend additive summary/DTO; перед выпуском откатываемой пары перегенерировать и закоммитить соответствующий frontend OpenAPI snapshot. | | Data safety | Schema, persisted Portfolio, Brokerage Account или данные владения не создаются и не мутируют; rollback лишь убирает из ответа текущее upstream-наблюдение. | | Verification | Запустить canonical gates затронутых backend и frontend, а также runtime OpenAPI gate против откатанного контракта; проверить, что список сохраняет прежние метаданные, навигацию и error states. | ## Open Questions