spec: plan account concentration tab (WealthLedger/main#48)

This commit is contained in:
2026-10-31 13:42:27 +03:00
parent b9ec11f5ba
commit e3cc9c8d25
23 changed files with 3441 additions and 0 deletions
@@ -0,0 +1,54 @@
# Process Record
**Mode:** lab
**Class:** complex — межсистемно (backend + frontend + T-Bank), синхронизация внешних данных и финансовые расчёты долей
**Issue:** https://git.personal.ksv741.ru/WealthLedger/main/issues/48
## Change Profile
| Артефакт | Выбран | Причина |
| --- | --- | --- |
| research | yes | Trigger: новый продуктовый функционал и вопрос владельца «какой ещё есть критерий?». Нужно сравнить критерии концентрации и формы визуализации в других продуктах. Итог — `analysis/research.md`. |
| ux-design | yes | Trigger: новый экран, вкладка «Концентрация» с четырьмя разрезами, выбором валюты, диаграммой и «Прочее». Отдельно нужно решить, как она соотносится с существующей «Структурой портфеля». |
| system-analysis | yes | Trigger: меняется контракт Portfolio, в позициях появляются сектор и эмитент. Новые данные берутся из внешней интеграции T-Bank (инструмент, актив и его бренд). |
| data-mapping | yes | Trigger: данные T-Bank (sector, asset → brand) нужно преобразовать в данные позиций Portfolio, а коды секторов — в русские подписи. Нужно правило для пустых и неизвестных значений. |
| failure-model | yes | Trigger: обогащение позиций идёт через внешнюю интеграцию в часовой синхронизации. Отказ или неполнота данных T-Bank не должны ломать Portfolio Snapshot и его свежесть (Open Decision 4 Proposal). |
| nfr | no | Число позиций одного счёта ограничено, вкладка считается по уже загруженному Portfolio. Нагрузку на T-Bank и лимиты запросов рассматривает failure-model; отдельных обязательств по задержке, объёму и хранению нет. |
| observability | no | Новых фоновых процессов нет: обогащение идёт внутри существующей синхронизации с её логированием. Недоступность данных видна пользователю как явные группы «не указан»; частичный успех обогащения (Snapshot сохранён, часть данных не получена) фиксируется в failure-model и существующих логах синхронизации без новых метрик и алертов. |
| security-analysis | no | Модель доступа и trust boundaries не меняются. Сектор и бренд — публичные справочные данные инструмента, к Portfolio того же User добавляются только они. Новые запросы к T-Bank идут под уже существующим токеном подключения того же User, новых секретов и прав нет. |
| test-cases | yes | Trigger: финансовые расчёты (суммы, доли, топ-5, «Прочее», сумма долей = 100 %) и много граничных случаев: одна валюта из нескольких, меньше пяти элементов, равные суммы, нет сектора или бренда, шорт, неоценённые позиции. Для проверки нужны конкретные данные и расчётный oracle. |
| docs-impact | yes | Trigger: несколько docs targets — домен Portfolio, страница интеграции T-Bank (новые используемые данные) и глоссарий (Концентрация, Сектор, Эмитент). |
## Gate Log
| Gate | Дата | Scope | Validation | Findings | Decision |
| --- | --- | --- | --- | --- | --- |
| G1 | 2026-10-30 | proposal, change-profile | `openspec validate` — ожидаемая ошибка «no deltas» до specs; readiness: plan OK (proposal, process-record, research); HTML-комментариев нет; `git diff --check` OK | G1 review independent-reviewer: 1 blocker, 4 major, 6 minor — закрыты решениями владельца ([issuecomment-3077](https://git.personal.ksv741.ru/WealthLedger/main/issues/48#issuecomment-3077)) и правками; повторный review: ready | approved "вроде все ок" |
| G1 | 2026-10-30 | proposal | — | G2 review B1: правило валют вкладки противоречит proposal | invalidated: решение владельца по B1 ([issuecomment-3079](https://git.personal.ksv741.ru/WealthLedger/main/issues/48#issuecomment-3079)) меняет правило валют в proposal |
| G1 | 2026-10-30 | proposal, change-profile | `openspec validate --strict` valid; readiness: plan OK; proposal обновлён по 3079 (свой список валют вкладки) | G2 re-review independent-reviewer: B1 закрыт в proposal | approved "Да, G1 ок" ([issuecomment-3080](https://git.personal.ksv741.ru/WealthLedger/main/issues/48#issuecomment-3080)) |
| G2 | 2026-10-30 | specs | `openspec validate --strict` valid; `git diff --check` OK; readiness: plan OK; self-check по spec-writing.md | G2 review qa-engineer + independent-reviewer: 1 blocker (B1, валюты), 4 major, ~12 minor — закрыты решениями владельца (3079, 3080) и правками; финальный re-review: ready | approved "да, идем дальше" |
| G2 | 2026-10-30 | specs | — | system-analysis/ux-design: эмитент у фондов, обновление metadata, имя эмитента при расхождении, поведение вкладки (сохранение выбора в пределах страницы, скрытый Select, метрика ≤5, partial) | invalidated: решения владельца ([issuecomment-3081](https://git.personal.ksv741.ru/WealthLedger/main/issues/48#issuecomment-3081)) меняют Specs |
| G2 | 2026-10-30 | specs, system-analysis, data-mapping, failure-model | `openspec@1.13.1 validate --strict` valid; `validate --specs` 33/33; `git diff --check` OK; readiness: plan OK; Coverage 32/32 | G2 rev2/rev3 review independent-reviewer + qa-engineer: 1 blocker (B1 — противоречие canonical «Свежесть и fallback»), 3 major, ~8 minor — закрыты решениями владельца ([3081](https://git.personal.ksv741.ru/WealthLedger/main/issues/48#issuecomment-3081), [3082](https://git.personal.ksv741.ru/WealthLedger/main/issues/48#issuecomment-3082)) и правками; финальный re-review: ready | approved "ок, давай дальше" |
| G3 | 2026-10-31 | design, ux-design, qa-analysis, test-cases, docs-impact, tasks | `openspec@1.13.1 validate --strict` valid; `git diff --check` OK; readiness: apply OK; Visual Package v2 SHA-256 сверены reviewer; Coverage 32/32 | G3 review independent-reviewer + frontend-engineer: 4 major, 4 minor, Q2 — закрыты; backend-engineer не выполнен (квота API), backend-утверждения design сверены основным агентом с кодом; повторный review: ready. Включает изменение вне Specs: подпись «Государственный сектор» на странице акции и в каталогах (3084) | approved "да, все окей, идем дальше" |
Примечание к G2 (2026-10-30): GIVEN «сектор и бренд актива позиции ещё ни разу не получены» в Scenario «Недоступность сектора и эмитента не нарушает Snapshot» (finding N1) и замена строки design-обязательства в failure-model (N2) внесены основным агентом до записи approved G2 и входят в утверждённую версию.
## Descoped Work
| Task | Причина | Gate |
| --- | --- | --- |
## Lab Journal
| Стадия | Роль / сабагент | Уникальные находки | Решения | Риски найдены / исключены | Проблемы передачи | Skills | Стоимость | Переносимо |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| research | product-researcher | В Portfolio DTO нет сектора и эмитента, хотя T-Bank их отдаёт. Доля топ-5 понятнее HHI. Общий мультивалютный рейтинг без конвертации некорректен. «Структура» дублировала бы существующий виджет. | MVP: акции, облигации, секторы; позже: страна, maturity, riskLevel, P&L, HHI; не делать: кредитный рейтинг, конвертацию, treemap | Найден: полнота sector неизвестна. Исключено: смешение валют | Нет | — | 1 вызов | Шаблон «must / later / no» для выбора критериев |
| proposal | product-analyst + основной агент | Эмитент требует отдельной идентичности; основной агент нашёл в proto T-Bank `AssetFull.brand` {uid, name, company, sector} (краткий `Asset` бренда не содержит). 8 Open Decisions, 3 из них меняют scope | Владелец: «Эмитенты» в MVP (отклонение от research: Later → MVP), эмитент = бренд T-Bank, фонды не входят, госбумаги — по сектору T-Bank, режим lab | Найден: «Эмитенты» зависят от полноты бренда. Исключено: группировка по названию | Валидация change ждёт delta specs (ожидаемо) | — | 1 вызов + 2 вопроса владельцу | Решать scope-вопросы до G1 через короткий опрос |
| G1 review | independent reviewer + product-analyst | Источник бренда указан неточно (`Asset` вместо `AssetFull.brand`); «Секторы» не задавали набор типов; раскрытие «Прочее» было Open Decision; не зафиксировано отклонение от research | Владелец ([Issue #48, комментарий](https://git.personal.ksv741.ru/WealthLedger/main/issues/48#issuecomment-3077)): «Прочее» раскрывается; без бренда — fallback «Эмитент не указан» без нового G1; «Секторы» только STOCK/BOND/FUND; общий топ-5 вне scope | Найден: несоответствие источника и scope. Исключено: зависимость «Эмитентов» от полноты бренда | Proposal правился после review; Gate Log не заполнялся | — | 1 вызов | Сверять ссылки на proto с исходником до G1 |
| specs | product-analyst | Остальные нормы вкладки не требуют новых источников; в canonical нет перечня вкладок страницы счёта и правил выбора валюты «Структуры портфеля» (они только в коде и docs) — правила валюты, округления, равных сумм определены в Specs и вынесены на подтверждение G2. Эмитент и сектор — новые поля позиции Portfolio | 2 дельты: новая `portfolio-concentration` (23 Requirement), `portfolio-view` MODIFIED «Нормализованный состав Portfolio». Решения владельца ([3078](https://git.personal.ksv741.ru/WealthLedger/main/issues/48#issuecomment-3078)) внесены | Найден: расхождение «Структуры портфеля» (деньги в знаменателе и в валютах) и вкладки (без денег) — различие зафиксировано явно | Нет | — | 1 вызов | Сверять правила «как в существующем виджете» с canonical до написания, а не с кодом |
| G2 review | qa-engineer + independent-reviewer + product-analyst | Валюты вкладки не были определены независимо от «Структуры»; «Прочее» при шести элементах скрывало одну строку; метод наибольшего остатка противоречил показу долей в карточках; подписи секторов без источника; не было граничных сценариев 5/6 элементов и противоречивый GIVEN | Владелец ([3079](https://git.personal.ksv741.ru/WealthLedger/main/issues/48#issuecomment-3079)): свой список валют вкладки; округление как в карточках, «< 0,1 %»; «Прочее» от семи элементов. Автор: список валют включает отрицательные и нулевые позиции STOCK/BOND/FUND; `null` считается в сноске при любой валюте | Найден: расхождение формата долей между спеками и кодом. Исключено: инвариант «100,0 %» на показанных значениях | Gate Log ведёт основной агент (G1 инвалидирован) | — | 1 вызов | Сверять формат чисел с кодом существующего виджета до написания норм |
| specs rev2 | product-analyst | G2 инвалидирован решениями [3081](https://git.personal.ksv741.ru/WealthLedger/main/issues/48#issuecomment-3081): issuer у фондов в данных; обновление sector/brand раз в сутки с последним известным значением; отказ token — как существующая недействительность; trim пробелов; имя эмитента по наибольшей позиции; сохранение выбора в пределах страницы; метрика топ-5 при ≥6 элементах; центр кольца; «Данные неполные» | `portfolio-concentration` 28 Requirement (+5: вкладка в состояниях страницы, название эмитента при разных названиях, центр диаграммы, строки без переходов, предупреждение о неполных данных; «Суммарная доля топ-5» и «Начальное состояние» переписаны); `portfolio-view` MODIFIED + 3 ADDED (срок обновления, последнее известное, ошибки получения) | Найден: «Данные неполные» привязано к `meta` по `tbank-data-sync`, не к неоценённым позициям. Исключено: конфликт metadata/invalid token (отсылка к существующему поведению) | Gate Log ведёт основной агент | — | 1 вызов | Сверять производные analysis-артефакты с Delta Specs до G2 |
| specs rev3 | product-analyst | G2 re-review: хранение sector/brand противоречило «Свежесть и fallback» (только онлайн, без долговременного справочника) и «metadata не определяет успешность»; граница 24 ч была сформулирована как «24 ч + интервал»; «не найдено», пустой ответ и ответ о другом активе не были определены | [3082](https://git.personal.ksv741.ru/WealthLedger/main/issues/48#issuecomment-3082): исключение для sector/brand; «не найдено» = «значения нет»; равные наибольшие позиции эмитента — по алфавиту; предупреждение о неполных данных только на вкладке | `portfolio-view`: MODIFIED «Свежесть и fallback» (полная копия + 2 исключения и 2 сценария), ADDED «Обновление сектора и эмитента» и «Результат получения сектора и эмитента» (таблица исходов), без дублей; `portfolio-concentration`: название эмитента по алфавиту, предупреждение без дубля | Найден: страничного сообщения о неполноте в canonical «Состояния и ошибки Portfolio на клиенте» нет (требует лишь не выдавать неполный итог за окончательный), поэтому MODIFIED для него не нужен. Исключено: дублирование норм об ошибках | Gate Log ведёт основной агент | — | 1 вызов | Если новая норма хранит данные, сверять с общими «только онлайн» правилами до написания |
| design ∥ qa-analysis | solution-architect, qa-engineer | Общий metadata-кэш не подходит под 24 ч и 4 исхода, нужна отдельная таблица классификации; шаг классификации должен идти до publish, иначе отказ token не инвалидирует подключение; DonutChart 9.7.3 хватает без прямого recharts | Таблица `TbankInstrumentClassification`, opt-in NOT_FOUND только для новых вызовов, состояние вкладки поднято в page VM; владелец: ~25 ч, квоты в QA до G4, «Государственный сектор» в общем словаре (3084) | Найдено: изменение страницы акции вне Specs (одобрено 3084); откат образа без миграции | Субагенты не видели полный текст review, findings пересылались вручную | — | 2 вызова + 3 доработки | Сверять design-ссылки на код до G3 |
| test-cases + docs-impact + tasks | qa-engineer, documentation-engineer, solution-architect | 21 из 30 зарезервированных TC ничего не добавляли сверх Scenario и выброшены; «эмитент» конфликтует с термином docs | 15 TC + TC-CONC-020; глоссарий с оговоркой, ADR 022 (3083); 28 tasks по 4 repo | Найдено: 2 неверных oracle (TC-CONC-007, TC-META-003) | — | — | 3 вызова | Пересчитывать oracle независимым ревьюером |
| G3 review | independent-reviewer, frontend-engineer; backend-engineer — сбой по квоте API (3 попытки) | 4 major, 4 minor, Q2 (откат без миграции); frontend API DonutChart подтверждён | Все findings закрыты; backend-утверждения design (до publish, InvalidToken catch, NOT_FOUND в `call()`, `WORK_WAIT_BOUND_MS`) основной агент сверил с кодом вручную | Остаётся: backend-ревью субагентом не выполнено | Квота API | — | 5 вызовов | Ревью backend повторить на G4 |