40 KiB
Системный анализ
Контекст и статус
Цель — передать в Portfolio сектор инструмента и бренд его актива, чтобы frontend строил четыре разреза концентрации одного счёта без конвертации валют. Основание: specs/openspec/changes/account-top-positions/proposal.md:11-18,60-63; Issue main#48. Рабочая ветка specs — feature/48-account-top-positions.
Документ приведён в соответствие с решениями владельца 3081 и 3082. G2 по комплекту Specs, system-analysis, data-mapping и failure-model approved (process-record.md, Gate Log, последняя строка G2). Технологии, внутренние компоненты и форма хранения здесь не проектируются.
Обозначения. PC — specs/openspec/changes/account-top-positions/specs/portfolio/portfolio-concentration/spec.md; PVΔ — specs/openspec/changes/account-top-positions/specs/portfolio/portfolio-view/spec.md. Requirement указываются по названию заголовка: PC «Название», PVΔ «Название». Пути с backend/, frontend/, docs/, specs/ отсчитываются от workspace WealthLedger.
Покрытие требований (Requirement Coverage)
Покрыты все 32 Requirement: 28 в PC и 4 в PVΔ (2 MODIFIED, 2 ADDED). Заголовки в первом столбце приведены дословно (проверено сопоставлением с файлами). Требования «Последнее известное значение сектора и эмитента» и «Ошибки получения сектора и эмитента» из Specs удалены: их содержание вошло в PVΔ «Свежесть и fallback полного Portfolio Snapshot» и PVΔ «Результат получения сектора и эмитента».
| Requirement | Flow / контракт | Модуль |
|---|---|---|
PC Вкладка «Концентрация» страницы счёта |
F3: только страница одного счёта; /all вкладки не получает; вкладка по умолчанию прежняя |
Hub |
PC Вкладка в состояниях страницы |
F2 → F3: нет вкладки при загрузке, ошибке, недоступности и пустом Portfolio; есть при ≥1 позиции или денежном остатке; только остатки → пустой разрез | Hub; failure-model F12 |
PC Переключение разрезов |
F3: четыре разреза одного ответа, один видимый; T-Bank не вызывается | Hub |
PC Начальное состояние вкладки |
F3: разрез и валюта сохраняются, пока User на странице; сброс при новом заходе, переключении счёта и перезагрузке; на сервер не передаётся | Hub |
PC Состав разрезов |
F3: Секторы — STOCK/BOND/FUND, Эмитенты — STOCK/BOND; issuer у FUND приходит в API, разрез его отбрасывает |
data-mapping: «Применимость по типу», I3 |
PC Названия элементов |
F2 → F3: displayName, подпись sector, issuer.name; группировка эмитента по issuer.id |
data-mapping |
PC Название эмитента при разных названиях бренда |
F3: имя — issuer.name позиции с наибольшим currentValue в выбранной валюте; при равных — меньшее по алфавитному порядку PC «Порядок элементов с равной суммой»; выбор на клиенте |
data-mapping: I8 |
PC Позиции, учитываемые в разрезе |
F3: только currentValue > 0 в выбранной валюте |
data-mapping: I4 |
PC Сноска о не учтённых позициях |
F3: null в любой валюте и отрицательные в выбранной | data-mapping: I12 |
PC Одна валюта за раз |
F3: одна валюта на все разрезы, без конвертации | data-mapping: I11 |
PC Доступные валюты вкладки |
F3: ненулевые оценки STOCK/BOND/FUND, ISO-порядок; при одной валюте или без валют выбора нет | data-mapping: I11 |
PC Независимый выбор валюты вкладки |
F3: локальный выбор; пропавшая валюта → первая в списке | Hub |
PC Топ-5 и строка «Прочее» |
F3: до 6 элементов — все строки; от 7 — пять и «Прочее» с числом остальных | data-mapping: I7 |
PC Раскрытие строки «Прочее» |
F3: остаток уже входит в ответ; нового запроса нет | Hub |
PC Порядок элементов с равной суммой |
F3: название без регистра: цифры, латиница, кириллица, затем порядок в Portfolio | data-mapping: I8 |
PC Расчёт долей |
F3: знаменатель — сумма учитываемых позиций разреза | data-mapping: I5, I9 |
PC Показ долей |
F3: формат карточек позиций, «< 0,1 %» | data-mapping |
PC Суммарная доля топ-5 |
F3: метрика только при ≥6 элементах | data-mapping: I7 |
PC Группы «Сектор не указан» и «Эмитент не указан» |
F2 → F3: null образует равноправный элемент |
data-mapping: I6; failure-model F06 |
PC Подписи секторов |
F3: известный код → подпись, иначе код как получен | data-mapping |
PC Русские подписи известных секторов |
F3: таблица подписей | data-mapping |
PC Диаграмма распределения разреза |
F3: сегменты совпадают со списком; ≤6 элементов — по сегменту, ≥7 — пять и «Прочее» | data-mapping: I10 |
PC Центр диаграммы |
F3: сумма разреза и подпись «Топ-5 · N %» при показе метрики | data-mapping: I10 |
PC Строки без переходов |
F3: строки без переходов; в «Эмитентах» без логотипов; logoName остаётся в API |
Hub |
PC Доступность диаграммы без цвета |
F3: текстовый эквивалент и сопоставление сегмента со строкой | Hub; ux-design.md |
PC Пустое состояние разреза |
F3: нет учтённых элементов — пустое состояние без доли и диаграммы | data-mapping: I14 |
PC Предупреждение о неполных данных |
F2 → F3: meta обозначает неполноту — «Данные неполные» только на вкладке над разрезом; страница его не дублирует; позиции без оценки и отсутствие sector/issuer предупреждения не вызывают |
failure-model F14; заметка для design ниже |
PC Состояния Portfolio на вкладке «Концентрация» |
F2 → F3: состояния как на «Позициях»; отсутствие sector/issuer не скрывает вкладку и не меняет успешность и свежесть | failure-model F12, F14 |
PVΔ Нормализованный состав Portfolio (MODIFIED) |
F1 → F2: аддитивные sector, issuer; значения только у STOCK/BOND/FUND, включая issuer фонда; обрезка пробелов; остальные типы null; UID → FIGI; исключение — отказ token |
Hub: контракт; data-mapping; failure-model |
PVΔ Свежесть и fallback полного Portfolio Snapshot (MODIFIED) |
F1, F2: два исключения — сектор и бренд хранятся как последнее известное значение (GET читает хранимое, T-Bank не вызывает); отказ token при получении обрабатывается как при обходе Portfolio | Hub; failure-model F08, F11 |
PVΔ Обновление сектора и эмитента (ADDED) |
F1: обновление при первом обходе, начавшемся не ранее чем через 24 ч после последнего успешного получения; пока значение не получено — при каждом обходе | failure-model: «Таймауты и повторы» |
PVΔ Результат получения сектора и эмитента (ADDED) |
F1: четыре исхода определяют поле и отсчёт 24 ч; неуспешное получение не мешает обновлению Portfolio | data-mapping; failure-model F02–F05 |
Системный контекст и границы
Участники: T-Bank Invest API → backend часовой синхронизации → сохранённый Portfolio Snapshot → backend HTTP API → авторизованный frontend. T-Bank владеет исходной классификацией и брендами, WealthLedger — нормализацией и представлением. Подтверждено docs/architecture/context.md:9-38, docs/architecture/containers.md:9-33, docs/integrations/tbank.md:3-21.
Сектор и бренд получает сервер в часовом обходе, не браузер и не открытие вкладки. Обычный GET и его повтор не обращаются к T-Bank (specs/openspec/specs/tbank/tbank-data-sync/spec.md:9-14; PVΔ «Свежесть и fallback полного Portfolio Snapshot», сценарий «Сектор и бренд читаются из хранимого значения»). MOEX, НКР и CDN логотипов не являются источниками sector/issuer.
Владелец расчёта разрезов — frontend над полным ответом Portfolio. Подтверждено: Change Profile — «вкладка считается по уже загруженному Portfolio» (specs/openspec/changes/account-top-positions/process-record.md:16), impact в proposal.md:60. Specs закрепляют клиентский расчёт: имя эмитента выбирается по позиции с наибольшим currentValue в выбранной валюте (PC «Название эмитента при разных названиях бренда»), а валюта и учитываемость зависят от выбора пользователя (PC «Позиции, учитываемые в разрезе», PC «Одна валюта за раз»). Серверный агрегат такой выбор не воспроизвёл бы без параметров запроса, которых контракт не вводит. Backend-агрегаты, endpoint топ-5 и обращение браузера к каталогу не требуются.
As-Is / To-Be
As-Is: фактический путь
- Часовой планировщик независимо от экрана обновляет membership и Portfolio счетов (
backend/src/modules/investments/tbank-sync/tbank-hourly.scheduler.ts:176-209). Интервал и отсутствие перекрытий —specs/openspec/specs/tbank/hourly-investment-sync/spec.md:8-21. - Для счёта параллельно запрашиваются GetPortfolio и GetPositions; после нормализации полный Snapshot публикуется, и только затем запрашивается обновление metadata позиций (
backend/src/modules/investments/portfolio/application/portfolio.service.ts:263-315). Регистрация metadata-запроса не ждёт внешнего lookup (backend/src/integrations/tbank/instrument-metadata.service.ts:63-105). - Snapshot хранится одним JSON payload; публикация транзакционна и ограждена connection revision, attempt и доступностью счёта (
backend/prisma/schema.prisma:60-70;backend/src/modules/investments/portfolio/infrastructure/portfolio.repository.ts:116-147). - Mapper считает
currentValueиз quantity × currentPrice и заполняетlogoNameиз metadata (backend/src/modules/investments/portfolio/infrastructure/tbank-portfolio.mapper.ts:22-31,63-99). Это прецедент необязательного обогащения. - GET читает сохранённый Snapshot и дополняет позиции сохранённой metadata без обращения к T-Bank (
backend/src/modules/investments/portfolio/application/portfolio.service.ts:55-105,122-164); время финансового Snapshot при этом не меняется. - Общая metadata — GetInstrumentBy по UID, затем по FIGI.
Instrumentсодержитasset_uid, но неsector; егоbrand—BrandData(логотип и цвета), а неBrandсuid/name(backend/src/integrations/tbank/proto/contracts/instruments.proto:939-998;backend/src/integrations/tbank/proto/contracts/common.proto:120-124). ЛокальныйTbankInstrumentне объявляетassetUidиsector(backend/src/integrations/tbank/tbank.client.ts:91-99). - Каталог акций и облигаций получает
ShareBy/BondByи переноситsector/assetUid; это отдельный источник, а не готовое обогащение Portfolio (backend/src/modules/investments/instruments/application/catalog-enrichment.service.ts:335-351;backend/src/modules/investments/instruments/application/public-instrument.ts:6-23;backend/src/integrations/tbank/tbank-market.types.ts:8-23). - Расхождения AS-IS с PVΔ «Обновление сектора и эмитента» и PVΔ «Результат получения сектора и эмитента», которые должна закрыть реализация. (а) Общая metadata считается свежей по факту завершённого lookup, без возраста (
backend/src/integrations/tbank/instrument-metadata.repository.ts:314-323); норма требует срок от последнего успешного получения и четыре исхода. (б) Клиент превращает NOT_FOUND вNotFoundExceptionтолько дляshareByиgetInstrumentBy, остальные методы дают Unavailable (backend/src/integrations/tbank/tbank.client.ts:577-583); норма относит «не найдено» к исходу «значения нет». Это требования к реализации, а не выбор между вариантами. - Frontend берёт Portfolio из generated paths существующего GET (
frontend/src/entities/portfolio/api/usePortfolio.ts:1-28). Текущие DTO и опубликованный snapshot новых полей не содержат (backend/src/modules/investments/portfolio/presentation/portfolio.dto.ts:7-55;frontend/src/shared/api/spec/openapi.json:6426-6519;frontend/src/shared/api/generated/schema.d.ts:1059-1085). - Общий справочник инструментов в AS-IS задокументирован как онлайн, без долговременного хранения (
docs/architecture/cross-cutting/data-freshness.md:9-12, ADR 019); PVΔ «Свежесть и fallback полного Portfolio Snapshot» вводит исключение для сектора и бренда.
To-Be: системные обязательства
- Каждая позиция содержит
sectorиissuer; значения только у STOCK, BOND, FUND, у остальных типовnull(PVΔ «Нормализованный состав Portfolio»). - Получение — в часовом обходе: при первом обходе, начавшемся не ранее чем через 24 часа после последнего успешного получения; пока значение не получено — при каждом обходе (PVΔ «Обновление сектора и эмитента»). Поле и отсчёт 24 часов после каждого получения определяет таблица исходов (PVΔ «Результат получения сектора и эмитента»); здесь она не пересказывается.
- Сектор и бренд хранятся как последнее известное значение; GET читает хранимое (PVΔ «Свежесть и fallback полного Portfolio Snapshot»).
- Отказ token при получении обрабатывается как отказ token в обходе Portfolio (PVΔ «Свежесть и fallback полного Portfolio Snapshot»); иные ошибки на обновление Portfolio не влияют (PVΔ «Результат получения сектора и эмитента»).
- Frontend получает полный список позиций и сам вычисляет разрезы; Portfolio не сортируется и не объединяется (PVΔ «Нормализованный состав Portfolio»).
Способ получения (типизированный запрос или ранее сохранённые данные), форма хранения времени успешного получения и порядок вызовов относительно публикации — design; нормы Specs от них не зависят, кроме того, что после отказа token подключение недействительно.
Анализ влияния
| Область | Изменение / обязательство | Не меняется |
|---|---|---|
| T-Bank → backend | Типизированный sector и asset → brand только для STOCK/BOND/FUND; источники-кандидаты в data-mapping | GetPortfolio/GetPositions, права подключения |
| Часовой обход | Дополнительные чтения по инструментам и активам по сроку PVΔ «Обновление сектора и эмитента» | Интервал 60 минут, запрет перекрытия |
| Сохранённый Snapshot и состояние получения | Новые nullable поля позиций; хранение последнего известного значения и времени последнего успешного получения (PVΔ «Свежесть и fallback полного Portfolio Snapshot», PVΔ «Обновление сектора и эмитента») | Количество, деньги, порядок, fetchedAt финансового наблюдения |
| Backend → frontend | Аддитивные поля PortfolioPositionDto в /api/v1 |
Envelope, параметры, прежние статусы |
| Подключение | Отказ token при получении сектора/бренда делает подключение недействительным как при обходе Portfolio | Модель доступа |
| Frontend | Четыре вычисляемых разреза над полным Portfolio; единственное сообщение о неполноте на вкладке (PC «Предупреждение о неполных данных») | Сводка, остальные вкладки |
| Режим «Все счета» | Общая DTO получает те же поля; вкладки и расчёта нет | Агрегирование счетов |
| API artifacts | backend OpenAPI → переданный snapshot → generated types | Автономность репозиториев |
| Docs | Темы Portfolio и T-Bank, глоссарий, исключение из онлайн-справочника в docs/architecture/cross-cutting/data-freshness.md и ADR 019 — по docs-impact |
В этой задаче docs не редактируются |
Общая DTO для двух GET подтверждена backend/src/modules/investments/portfolio/presentation/portfolio.controller.ts:19-31. Подробности — data-mapping и failure-model.
Заметка для design: дублирование предупреждения о неполноте
PC «Предупреждение о неполных данных» требует одно сообщение о неполноте: «Данные неполные» на вкладке, страница его не дублирует. В AS-IS страница уже показывает собственный Alert при неполноте:
frontend/src/pages/account/ui/AccountPage/components/AccountPageLoaded.view.tsx:77-79—<Alert role="status">{t("investment.partial")}</Alert>приprops.partial; признак вычисляется какportfolio.meta?.completeness === "incomplete"(frontend/src/pages/account/ui/AccountPage/AccountPage.vm.ts:124,130);- Alert выводится над
<Tabs>(AccountPageLoaded.view.tsx:108-112, неконтролируемыйdefaultValue="positions"), поэтому виден при любой вкладке, включая «Концентрация»; - текст Alert — «Период загружен не полностью» (
frontend/src/shared/i18n/locales/ru.ts:423), не «Данные неполные».
Следствие: при открытой вкладке «Концентрация» и неполном Portfolio страничный Alert не должен показываться (иначе на странице два сообщения). Содержание других вкладок не меняется (PC «Вкладка «Концентрация» страницы счёта»). Состояние активной вкладки сейчас внутри Tabs и странице недоступно; как это обеспечить — design.
Системы и ответственность
- T-Bank: инструмент, его сектор, связь с активом, идентичность и название бренда; допустимость token.
- Backend sync: полный финансовый результат; получение sector/brand по PVΔ «Обновление сектора и эмитента» и PVΔ «Результат получения сектора и эмитента»; хранение последнего известного значения; обработка отказа token.
- Backend HTTP: проверка доступа, выдача Portfolio с новым shape, явные
nullдля старых Snapshot. - Frontend: вычисление разрезов, единый набор сумм для списка и диаграммы, выбор имени эмитента по PC «Название эмитента при разных названиях бренда», единственное предупреждение о неполноте.
- Change Owner: принимает остаточные факты данных (Q-D1, Q-D2, Q-F3) как результат проверки при реализации; решений по ним не требуется.
Владение данными
| Данные | Источник истины | Пишут | Читают |
|---|---|---|---|
| quantity, price, cash | T-Bank финансовые ответы | Backend публикует Snapshot | GET, frontend |
sector (STOCK/BOND/FUND) |
sector Share/Bond/Etf в T-Bank, обрезанный по краям |
Backend; хранимое последнее известное значение | Frontend: группирует по точному коду |
issuer (STOCK/BOND/FUND) |
AssetFull.brand.uid/name в T-Bank, обрезанные по краям |
Backend; пара целиком либо null |
Frontend: группирует по id, имя по PC «Название эмитента при разных названиях бренда» |
| Время последнего успешного получения sector/brand | Backend | Backend | Backend: условие PVΔ «Обновление сектора и эмитента» |
lastSuccessfulAt, isStale, meta |
Подтверждённое финансовое наблюдение и состояние ресурса | Backend sync / local read | Frontend |
| Разрезы, суммы, доли, топ-5, имя эмитента | Производные от одного ответа Portfolio | Frontend, только вычисляет | Список и диаграмма |
| Русские подписи секторов | Таблица PC «Русские подписи известных секторов» | Frontend | Пользователь |
Потоки взаимодействий
F1 — часовой обмен. Backend проверяет подключение и доступные счета → GetPortfolio + GetPositions → полный финансовый Snapshot. Для позиций STOCK/BOND/FUND, у которых значение не получено либо наступил срок PVΔ «Обновление сектора и эмитента», запрашиваются сектор и бренд актива; результат применяется по PVΔ «Результат получения сектора и эмитента»; отказ token делает подключение недействительным (PVΔ «Свежесть и fallback полного Portfolio Snapshot»). Прочие сбои получения на финансовый результат не влияют.
F2 — локальная выдача. Авторизованный GET проверяет membership и connection → читает Snapshot и хранимые значения → {data, meta}. Для старого JSON без новых ключей возвращаются явные null; GET не обращается к T-Bank (PVΔ «Свежесть и fallback полного Portfolio Snapshot»). data: null — отсутствие полного снимка, а не пустая концентрация (docs/domains/portfolio/flows/portfolio-loading.md:10-21).
F3 — клиентская проекция. Из полного Portfolio frontend определяет список валют, выбирает разрез, отбирает позиции, агрегирует, ранжирует, выбирает имя эмитента и рассчитывает доли. Переключение разрезов и раскрытие «Прочее» новых запросов не требуют.
Диаграммы в отдельных файлах не создаются: работа ограничена тремя Markdown-файлами; три потока выше передают границы без внутреннего устройства.
Контракты
| Контракт | Producer → Consumer | Версия / artifact | Совместимость | Опубликованный источник |
|---|---|---|---|---|
GET /api/v1/brokerage-accounts/{accountId}/portfolio, data.positions[] |
Backend → frontend | API v1; PortfolioPositionDto + delta ниже |
additive | frontend/src/shared/api/spec/openapi.json:6426-6519 — предоставленная версия |
GET /api/v1/brokerage-accounts/all/portfolio, общий shape позиции |
Backend → существующие потребители | Тот же DTO; вкладка не применяется | additive | backend/src/modules/investments/portfolio/presentation/portfolio.controller.ts:19-31 |
| Instrument / Asset lookup | T-Bank → backend | tinkoff.public.invest.api.contract.v1, локальный instruments.proto |
Внешний контракт не меняется; расширяется используемая часть | Источники-кандидаты в data-mapping |
Семантическая delta PortfolioPositionDto
| Поле | Присутствие | Тип / ограничение | Значение |
|---|---|---|---|
sector |
Обязательно в каждой позиции | string или null | Код T-Bank без перекодирования, с обрезанными краями; только у STOCK/BOND/FUND; иначе null |
issuer |
Обязательно в каждой позиции | object или null | {id, name} только у STOCK/BOND/FUND, включая FUND; иначе null |
issuer.id |
Внутри непустого issuer |
Непустая строка после обрезки: brand.uid |
Не asset_uid и не UID инструмента |
issuer.name |
Внутри непустого issuer |
Непустая строка после обрезки: brand.name |
Не brand.company, не название актива |
Пример позиции с данными: {"sector":"financial","issuer":{"id":"brand-uid-example","name":"Сбер"}}; идентификатор условный. Без данных: {"sector":null,"issuer":null}. Поля независимы. Значение из одних пробелов считается отсутствующим (PVΔ «Нормализованный состав Portfolio»).
Изменение добавляет только response-поля: запросы, Money, маршруты, статусы и версия API остаются. Текущий snapshot — OpenAPI 3.0.0 (frontend/src/shared/api/spec/openapi.json:2); синтаксис 3.1 молча не вводится. Таблица — семантическая delta, не полный машиночитаемый target OpenAPI. Протокол требует target-контракт в analysis/contracts/ до реализации (specs/openspec/schemas/wealth-ledger-sdd-lab/schema.yaml:383-393); он не создан, так как работа ограничена тремя файлами. Для G2 нужен отдельный разрешённый шаг публикации и проверки target-контракта. После реализации backend-generated OpenAPI должен семантически совпасть с ним до G4.
Публикация и получение API artifact
Backend генерирует и проверяет OpenAPI; потребителю явно передаётся новый artifact. Frontend обновляет versioned src/shared/api/spec/openapi.json, локально генерирует src/shared/api/generated/schema.d.ts и проверяет соответствие. Generated types вручную не правятся; frontend не читает backend checkout, runtime не требует Swagger. Основания: specs/openspec/specs/backend-platform/api-contract-generation/spec.md:10-35; docs/architecture/http-api.md:5-22; frontend/.ai/conventions/data.md:15-34.
Семантика ошибок
Новых бизнес-ошибок HTTP из-за sector/issuer нет, кроме отказа token. Результат получения определяет PVΔ «Результат получения сектора и эмитента»; 200 с группами «не указан» не означает сбой Snapshot (PVΔ «Нормализованный состав Portfolio»; PC «Группы «Сектор не указан» и «Эмитент не указан»», PC «Состояния Portfolio на вкладке «Концентрация»»).
Отказ token при получении сектора или бренда обрабатывается как отказ token в обходе Portfolio: 400 INVALID_TBANK_TOKEN без fallback Snapshot (PVΔ «Свежесть и fallback полного Portfolio Snapshot», сценарий «Отказ token при получении бренда»; specs/openspec/specs/portfolio/portfolio-view/spec.md:113-115). Это соответствует текущему клиенту, сводящему PERMISSION_DENIED и UNAUTHENTICATED к InvalidToken (backend/src/integrations/tbank/tbank.client.ts:571-575).
Прочие ошибки доступа: нет подключения — 409 TBANK_NOT_CONNECTED, недоступный счёт — 404 (docs/domains/portfolio/flows/portfolio-loading.md:24-35).
Совместимость и миграция
- Старый потребитель → новый backend: аддитивные JSON-поля не меняют существующий shape; известный потребитель — generated paths (
frontend/src/entities/portfolio/api/usePortfolio.ts:7-16). - Новый backend → старый JSON Snapshot: отсутствующие ключи выдаются как
null; Snapshot не пересоздаётся,lastSuccessfulAtне меняется. Значения появляются после обхода, получившего их по PVΔ «Обновление сектора и эмитента». Текущий spread payload этого сам не делает (backend/src/modules/investments/portfolio/application/portfolio.service.ts:88-95); механизм — design. - Новый frontend → старый backend или Client Cached Snapshot: отсутствие ключей трактуется как неизвестные значения. Использование новых полей возможно после получения provider-first artifact.
- Свежесть: в обходах раньше срока значение не меняется (PVΔ «Обновление сектора и эмитента»); sector/brand не влияют на
isStale(PVΔ «Нормализованный состав Portfolio»). /all: общий DTO проверяется, расчёта концентрации нет. Ломающих изменений для существующих потребителей нет.
Подробные модули
- data-mapping.md: источники полей, типы позиций, нормализация, инварианты, риски классификации.
- failure-model.md: сценарии отказов со ссылками на нормы, лимиты, повторы, восстановление.
- Отдельные nfr и observability не выбраны: нагрузка и частичное обогащение рассматриваются в failure-model без выдуманных SLA (
process-record.md:16-17). ux-design.md— представление вкладки; в этом комплекте не дублируется.
Открытые вопросы
Закрыто решениями владельца
| Вопрос | Решение | Где в Specs |
|---|---|---|
issuer у фондов |
Заполняется у STOCK/BOND/FUND; разрез «Эмитенты» фонды отбрасывает (3081) | PVΔ «Нормализованный состав Portfolio»; PC «Состав разрезов» |
Имя при разных названиях одного issuer.id |
Название позиции с наибольшим currentValue; при равных — по алфавиту (3081, 3082) |
PC «Название эмитента при разных названиях бренда» |
| Пробелы | Края обрезаются; значение из одних пробелов отсутствует (3081) | PVΔ «Нормализованный состав Portfolio» |
| Срок и поведение при ошибке | Раз в сутки от последнего успешного получения; исходы по таблице (3081, 3082) | PVΔ «Обновление сектора и эмитента», PVΔ «Результат получения сектора и эмитента» |
| Отказ token | Как отказ token в обходе Portfolio; прочие ошибки не мешают обновлению Portfolio (3081, 3082) | PVΔ «Свежесть и fallback полного Portfolio Snapshot», PVΔ «Результат получения сектора и эмитента» |
| Хранение сектора и бренда вопреки «Свежесть и fallback» | Исключение для сектора и бренда (3082) | PVΔ «Свежесть и fallback полного Portfolio Snapshot» |
| «Не найдено» от T-Bank | Как «значения нет» (3082) | PVΔ «Результат получения сектора и эмитента» |
| Предупреждение о неполноте | Только на вкладке (3082) | PC «Предупреждение о неполных данных» |
Остаётся неизвестным
Это факты данных T-Bank; они проверяются при реализации и решения владельца не требуют.
| ID | Что неизвестно | Чем проверяется |
|---|---|---|
| Q-D1 | Реальное покрытие STOCK/BOND/FUND брендом актива; совпадает ли brand.uid у акций, префов и облигаций одной компании |
См. data-mapping: однократный read-only прогон на подключении владельца с агрегированной сводкой |
| Q-D2 | Какие значения sector фактически отдаются для фондов и государственных облигаций |
См. data-mapping: тот же прогон, список различных значений со счётчиками |
| Q-F3 | Фактические квоты методов T-Bank, число различных инструментов и активов, вид ответа «не найдено» | См. failure-model: официальные лимиты Invest API, измерение числа вызовов в обходе, запрос с несуществующим asset_uid |