# Tasks Issue-координатор: [WealthLedger/main#48](https://git.personal.ksv741.ru/WealthLedger/main/issues/48). В каждом affected repo ветка `feature/48-account-top-positions` создаётся от актуального `develop`; PR открывается в `develop`. Тело PR: Summary, Linked Issue `WealthLedger/main#48`, Changes, Verification, ссылки на связанные PR других repo — по `.ai/conventions/engineering-workflow.md` («Analysis → Branch», «Validation → PR»). Обозначения **PC**, **PVΔ**: PC — `portfolio-concentration`, PVΔ — delta `portfolio-view`. ## 1. Backend: хранение, классификация, DTO, OpenAPI - [ ] 1.1 [backend] Verification: ветка `feature/48-account-top-positions` от актуального `develop` - **Covers:** N/A — организационный шаг cross-repo delivery - **Source:** `.ai/conventions/engineering-workflow.md` «Analysis → Branch», «Branch → Implementation» - **Verification:** `git -C backend branch --show-current` выводит `feature/48-account-top-positions`; `git log origin/develop..HEAD` пуст до первой задачи - **Depends on:** none - [ ] 1.2 [backend] Migration: аддитивная модель `TbankInstrumentClassification` и миграция `CREATE TABLE` - **Covers:** `portfolio-view` — `Свежесть и fallback полного Portfolio Snapshot` - **Source:** design.md «Data / Storage Changes», B1; «Migration / Rollout / Rollback» - **Verification:** `npx prisma validate`; `npx prisma generate`; `npm run typecheck:app`; миграция применена к disposable копии БД с существующими Snapshot, прежние таблицы и строки не изменились; `DROP TABLE "TbankInstrumentClassification"` на копии возвращает её к исходной схеме - **Depends on:** 1.1 - [ ] 1.3 [backend] Implementation: `TbankClient.getEtf`, `getAssetBrand` и opt-in NOT_FOUND для новых вызовов классификации - **Covers:** `portfolio-view` — `Результат получения сектора и эмитента` - **Source:** design.md B4 «Новые методы», «NOT_FOUND»; analysis/data-mapping.md «Источники-кандидаты в T-Bank» - **Verification:** `npm test -- src/integrations/tbank/tbank.client.spec.ts`: NOT_FOUND даёт `NotFoundException` для `bondBy`/`etfBy`/`getAssetBy` только при opt-in; прежние `getBond`/`getAssetDescription` по-прежнему дают Unavailable; `PERMISSION_DENIED` даёт `InvalidToken` - **Depends on:** 1.1 - [ ] 1.4 [backend] Implementation: `PositionClassificationService` — правило 24 ч, четыре исхода по каждому полю, UID→FIGI после NOT_FOUND, дедупликация по инструменту и `asset_uid` - **Covers:** `portfolio-view` — `Обновление сектора и эмитента`; `portfolio-view` — `Результат получения сектора и эмитента`; `portfolio-view` — `Нормализованный состав Portfolio` - **Source:** design.md B3, B4, B5; analysis/failure-model.md «Классификация отказов по исходам PVΔ «Результат получения сектора и эмитента»», F02–F12 - **Verification:** `npm test -- src/modules/investments/portfolio`: границы 23:59 и 24:00 от `walkStartedAt`; при отсутствии значения получение идёт в каждом обходе; 4 исхода × {sector, brand}; mismatch не меняет поле; повтор по FIGI после NOT_FOUND по UID; один `GetAssetBy` на общий `asset_uid`; используется сохранённый `assetUid`; `InvalidToken` пробрасывается, остальные ошибки — нет; типы кроме STOCK/BOND/FUND не запрашиваются - **Depends on:** 1.2, 1.3 - [ ] 1.5 [backend] Implementation: шаг классификации в `PortfolioService.queueSnapshot` до publish; `walkStartedAt` из `TbankHourlyScheduler`; mapper пишет `sector`/`issuer` - **Covers:** `portfolio-view` — `Свежесть и fallback полного Portfolio Snapshot`; `portfolio-view` — `Нормализованный состав Portfolio` - **Source:** design.md B2, B3; analysis/failure-model.md F01, F08 - **Verification:** `npm test -- src/modules/investments/portfolio/application/portfolio.service.spec.ts`: отказ token в классификации → Snapshot не опубликован, вызван `rejectMembershipToken`; прочая ошибка → Snapshot опубликован, `lastSuccessfulAt` и `isStale` не изменены, поля — прежние известные; ошибка GetPortfolio → публикации нет независимо от классификации - **Depends on:** 1.4 - [ ] 1.6 [backend] Implementation: DTO `sector` и `PortfolioIssuerDto` (nullable), нормализация старых Snapshot в `saved()` - **Covers:** `portfolio-view` — `Нормализованный состав Portfolio` - **Source:** design.md B6, B7; analysis/system-analysis.md «Семантическая delta `PortfolioPositionDto`», «Совместимость и миграция» - **Verification:** `npm run test:e2e`: GET `/api/v1/brokerage-accounts/{accountId}/portfolio` и `/all/portfolio` возвращают у каждой позиции `sector` и `issuer`; для payload без этих полей — `null`; после отказа token — `400 INVALID_TBANK_TOKEN` - **Depends on:** 1.5 - [ ] 1.7 [backend] Contract Parity: OpenAPI-artifact с новыми полями совпадает с target-delta - **Covers:** `portfolio-view` — `Нормализованный состав Portfolio` - **Source:** design.md «Contract Implementation»; analysis/system-analysis.md «Семантическая delta `PortfolioPositionDto`» - **Verification:** `npm run openapi:generate` и `npm run test:openapi-contract` проходят; в корневом `openapi.json` у `PortfolioPositionDto` поля `sector` (string, nullable, required) и `issuer` (nullable, required, `id`/`name` обязательные строки); версия OpenAPI 3.0.x; запросы, маршруты и статусы не изменились (diff схемы с `frontend/src/shared/api/spec/openapi.json` содержит только эти добавления) - **Depends on:** 1.6 - [ ] 1.8 [backend] Verification: полный gate и PR в `develop` со ссылкой на WealthLedger/main#48 - **Covers:** N/A — финальный gate repo - **Source:** backend `.ai/conventions/engineering.md` «Проверки и завершение»; `.ai/conventions/engineering-workflow.md` «Validation → PR» - **Verification:** `npm run verify` зелёный на финальной ревизии; PR открыт, в теле `WealthLedger/main#48` и OpenAPI-artifact приложен как вход для frontend - **Depends on:** 1.7 ## 2. Frontend: контракт, модель, UI - [ ] 2.1 [frontend] Verification: ветка `feature/48-account-top-positions` от актуального `develop` - **Covers:** N/A — организационный шаг cross-repo delivery - **Source:** `.ai/conventions/engineering-workflow.md` «Analysis → Branch» - **Verification:** `git -C frontend branch --show-current` выводит `feature/48-account-top-positions` - **Depends on:** none - [ ] 2.2 [frontend] Implementation: предоставленный backend snapshot в `src/shared/api/spec/openapi.json` и generated `schema.d.ts` - **Covers:** `portfolio-view` — `Нормализованный состав Portfolio` - **Source:** design.md B6; frontend `.ai/conventions/data.md` «Типы и contract» - **Verification:** `npm run openapi:generate`; `npm run openapi:check` без diff; `npm run typecheck`; generated `paths` содержит `sector` и `issuer` у позиции - **Depends on:** 1.7, 2.1 - [ ] 2.3 [frontend] Implementation: `pages/account/model/concentration.ts` — состав разрезов, учитываемые позиции, валюты, точные суммы и доли, топ-5, «Прочее», порядок равных, имя эмитента, группы «не указан» - **Covers:** `portfolio-concentration` — `Состав разрезов`; `portfolio-concentration` — `Названия элементов`; `portfolio-concentration` — `Название эмитента при разных названиях бренда`; `portfolio-concentration` — `Позиции, учитываемые в разрезе`; `portfolio-concentration` — `Сноска о не учтённых позициях`; `portfolio-concentration` — `Одна валюта за раз`; `portfolio-concentration` — `Доступные валюты вкладки`; `portfolio-concentration` — `Топ-5 и строка «Прочее»`; `portfolio-concentration` — `Порядок элементов с равной суммой`; `portfolio-concentration` — `Расчёт долей`; `portfolio-concentration` — `Показ долей`; `portfolio-concentration` — `Суммарная доля топ-5`; `portfolio-concentration` — `Группы «Сектор не указан» и «Эмитент не указан»` - **Source:** design.md F1; analysis/data-mapping.md M3, I1–I8; analysis/test-cases/conc.md - **Verification:** `npm run test:run -- src/pages/account/model/concentration.test.ts` по oracle TC-CONC-007, TC-CONC-008, TC-CONC-016, TC-CONC-020; сумма точных долей = 100 %; 6,25 → «6,3 %»; «< 0,1 %»; FUND исключён из «Эмитентов» - **Depends on:** 2.2 - [ ] 2.4 [frontend] Implementation: state `{accountId, tab, slice, currency}` в `AccountPage.vm.ts`, управляемые `Tabs`, вкладка только для одного счёта - **Covers:** `portfolio-concentration` — `Вкладка «Концентрация» страницы счёта`; `portfolio-concentration` — `Вкладка в состояниях страницы`; `portfolio-concentration` — `Переключение разрезов`; `portfolio-concentration` — `Начальное состояние вкладки`; `portfolio-concentration` — `Независимый выбор валюты вкладки`; `portfolio-concentration` — `Состояния Portfolio на вкладке «Концентрация»` - **Source:** design.md F2, F3; analysis/test-cases/conc.md TC-CONC-002, TC-CONC-005, TC-CONC-006, TC-CONC-015 - **Verification:** `npm run test:run -- src/pages/account/ui/AccountPage`: вкладки нет для `all`, при загрузке, ошибке и пустом Portfolio; по умолчанию открыта «Позиции»; разрез и валюта сохраняются при переключении вкладок и сбрасываются при смене счёта; при исчезновении валюты выбирается первая - **Depends on:** 2.3 - [ ] 2.5 [frontend] Implementation: `Concentration.view` — список, раскрытие «Прочее», пустое состояние, сноска, выбор валюты, Alert «Данные неполные», подавление страничного Alert на вкладке - **Covers:** `portfolio-concentration` — `Раскрытие строки «Прочее»`; `portfolio-concentration` — `Строки без переходов`; `portfolio-concentration` — `Пустое состояние разреза`; `portfolio-concentration` — `Предупреждение о неполных данных` - **Source:** design.md F2, F4, «UI Implementation»; analysis/ux-design.md «UI States», «Responsive Behavior»; visuals v2 (SHA-256 по ux-design «Materials» сверены до начала) - **Verification:** `npm run test:run -- src/pages/account/ui/AccountPage`: при `partial` на «Концентрации» одно сообщение «Данные неполные», на других вкладках — прежний Alert; строки не являются ссылками; browser-тест 320 px по образцу `position-layout.browser.test.tsx` без горизонтального скролла страницы - **Depends on:** 2.4 - [ ] 2.6 [frontend] Implementation: DonutChart — центр-оверлей, обводка и штриховка через `cellProps`, цифры ранга, текстовый эквивалент - **Covers:** `portfolio-concentration` — `Диаграмма распределения разреза`; `portfolio-concentration` — `Центр диаграммы`; `portfolio-concentration` — `Доступность диаграммы без цвета` - **Source:** design.md F5; analysis/ux-design.md «Accessibility», D7; analysis/test-cases/conc.md TC-CONC-027 - **Verification:** `npm run test:run -- src/pages/account/ui/AccountPage`: 7 элементов → 6 сегментов в порядке списка; центр «Топ-5 · 95 %» и «1 000 ₽»; при ≤ 5 элементах только сумма; `role="img"` и `aria-describedby` с названиями, суммами и долями - **Depends on:** 2.5 - [ ] 2.7 [frontend] Implementation: подписи секторов через общий словарь `stock.sectors` + ключ `government` («Государственный сектор»), видимый и в каталогах - **Covers:** `portfolio-concentration` — `Подписи секторов`; `portfolio-concentration` — `Русские подписи известных секторов` - **Source:** design.md F6; решение владельца [3084](https://git.personal.ksv741.ru/WealthLedger/main/issues/48#issuecomment-3084); analysis/test-cases/conc.md TC-CONC-024 - **Verification:** `npm run test:run`: 11 кодов из таблицы PC дают русские подписи; `other` выводится как есть; тест `StockCompany` (страница акции): `sector: "government"` отображается как «Сектор: Государственный сектор» — owner-approved изменение вне Specs (design.md F6, [3084](https://git.personal.ksv741.ru/WealthLedger/main/issues/48#issuecomment-3084)) - **Depends on:** 2.3 - [ ] 2.8 [frontend] Automated Testing: регрессия вкладки «Позиции» и «Структуры портфеля» - **Covers:** `portfolio-concentration` — `Вкладка «Концентрация» страницы счёта` - **Source:** analysis/qa-analysis.md «Regression Impact»; analysis/test-cases/reg.md TC-REG-001 - **Verification:** существующие `AccountPage.test.tsx`, `position-groups.test.tsx`, тесты `widgets/portfolio-structure` проходят без изменения ожиданий - **Depends on:** 2.6, 2.7 - [ ] 2.9 [frontend] Verification: полный gate и PR в `develop` со ссылкой на WealthLedger/main#48 и backend PR - **Covers:** N/A — финальный gate repo - **Source:** frontend `.ai/conventions/engineering.md`; `.ai/conventions/engineering-workflow.md` «Validation → PR» - **Verification:** `npm run verify` зелёный на финальной ревизии; PR открыт со ссылками на `WealthLedger/main#48` и backend PR - **Depends on:** 2.8 ## 3. Review и QA до G4 - [ ] 3.1 [specs] Independent Review: read-only review backend и frontend PR против Specs, design и analysis - **Covers:** `portfolio-view` — `Результат получения сектора и эмитента`; `portfolio-concentration` — `Расчёт долей` - **Source:** design.md целиком; analysis/system-analysis.md; analysis/failure-model.md - **Verification:** отчёт review приложен к Gate Log G4 в process-record; blocker/major закрыты или возвращены в upstream-артефакт - **Depends on:** 1.8, 2.9 - [ ] 3.2 [specs] QA Verification: прогон test-cases на локальном backend + frontend - **Covers:** `portfolio-view` — `Обновление сектора и эмитента`; `portfolio-view` — `Свежесть и fallback полного Portfolio Snapshot`; `portfolio-concentration` — `Состояния Portfolio на вкладке «Концентрация»` - **Source:** analysis/qa-analysis.md «Coverage Matrix», «Entry / Exit Criteria»; analysis/test-cases/conc.md, meta.md, reg.md - **Verification:** результаты TC-CONC-002/005/006/007/008/015/016/020/024/027, TC-META-002/003/004/005, TC-REG-001/004 записаны pass/fail; F01–F16 покрыты; ручной блок (макеты v2, контраст, клавиатура, скринридер) зафиксирован - **Depends on:** 1.8, 2.9 - [ ] 3.3 [specs] QA Verification: exploratory Q-D1/Q-D2/Q-F3 на реальном токене владельца - **Covers:** `portfolio-view` — `Обновление сектора и эмитента`; `portfolio-concentration` — `Группы «Сектор не указан» и «Эмитент не указан»` - **Source:** analysis/qa-analysis.md «Gaps / Blockers», «Test Data and Environment Needs»; решение владельца [3084](https://git.personal.ksv741.ru/WealthLedger/main/issues/48#issuecomment-3084) - **Verification:** записаны полнота сектора и бренда у позиций владельца (доля стоимости без значения, наличие `government` и фондов без сектора), число RPC и длительность первого обхода, отсутствие RESOURCE_EXHAUSTED / queue full; шаг Portfolio укладывается в `WORK_WAIT_BOUND_MS` - **Depends on:** 3.2 - [ ] 3.4 [backend] Verification: откат backend-bundle на копии данных до G4 - **Covers:** N/A — проверка отката (design.md «Migration / Rollout / Rollback») - **Source:** design.md «Migration / Rollout / Rollback» (Backend); `deploy/README.md` «Ручной откат и восстановление»; `docs/architecture/deployment.md` «Откат image» - **Verification:** на disposable копии БД с применённой миграцией `TbankInstrumentClassification`: (1) предыдущий backend-image, запущенный как при ручном откате (`up --no-deps backend`, без сервиса `migration`), проходит healthcheck, GET Portfolio отвечает `200` на Snapshot с полями `sector`/`issuer`; (2) из предыдущего image `node node_modules/prisma/build/index.js migrate status` и `migrate deploy` — записаны exit code и вывод (ожидание: status сообщает о миграции, отсутствующей локально; deploy не падает). Результат записан в Gate Log G4; при падении deploy — finding в design - **Depends on:** 1.8 ## 4. Docs (по analysis/docs-impact.md, после merge кода в `develop`) - [ ] 4.1 [docs] Verification: ветка `feature/48-account-top-positions` от актуального `develop` - **Covers:** N/A — организационный шаг cross-repo delivery - **Source:** `.ai/conventions/engineering-workflow.md` «Analysis → Branch»; analysis/docs-impact.md «Summary» (правки AS-IS только после merge кода) - **Verification:** backend и frontend PR слиты в `develop`; `git -C docs branch --show-current` выводит `feature/48-account-top-positions` - **Depends on:** 1.8, 2.9, 3.3 - [ ] 4.2 [docs] Docs: домен Portfolio — `overview.md`, при необходимости `concentration.md`, `data-reference.md`, `flows/portfolio-loading.md`, условная строка в `all-accounts.md` - **Covers:** `portfolio-concentration` — `Вкладка «Концентрация» страницы счёта`; `portfolio-view` — `Нормализованный состав Portfolio` - **Source:** analysis/docs-impact.md «Documentation Changes» (строки домена и flow) - **Verification:** `npm run verify` в docs; текст соответствует реализованному AS-IS - **Depends on:** 4.1 - [ ] 4.3 [docs] Docs: интеграция и архитектура — `integrations/tbank.md`, `architecture/cross-cutting/data-freshness.md`, `architecture/containers.md` - **Covers:** `portfolio-view` — `Свежесть и fallback полного Portfolio Snapshot`; `portfolio-view` — `Обновление сектора и эмитента` - **Source:** analysis/docs-impact.md «Documentation Changes» (Integration, Architecture); design.md B1, B4 - **Verification:** `npm run verify` в docs - **Depends on:** 4.1 - [ ] 4.4 [docs] Docs: ADR 022 `stored-instrument-classification` (Accepted, AS-IS), строка в `adr/README.md`, ссылка в Status ADR 019 - **Covers:** `portfolio-view` — `Свежесть и fallback полного Portfolio Snapshot` - **Source:** analysis/docs-impact.md «ADR Decision»; design.md «ADR Candidates», «Solution Assessment»; решение владельца [3083](https://git.personal.ksv741.ru/WealthLedger/main/issues/48#issuecomment-3083) - **Verification:** `npm run verify` в docs; текст ADR 019 кроме строки Status не изменён - **Depends on:** 4.1 - [ ] 4.5 [docs] Docs: глоссарий и навигация — `glossary.md`, `overview.md`, `README.md` - **Covers:** `portfolio-concentration` — `Переключение разрезов` - **Source:** analysis/docs-impact.md «Glossary / Navigation Impact» - **Verification:** `npm run verify` в docs; ссылки из навигации ведут на домен Portfolio - **Depends on:** 4.2 - [ ] 4.6 [docs] Verification: PR в `develop` со ссылкой на WealthLedger/main#48 и code PR - **Covers:** N/A — финальный gate repo - **Source:** `.ai/conventions/engineering-workflow.md` «Validation → PR» - **Verification:** `npm run verify` зелёный; PR открыт со ссылками на `WealthLedger/main#48`, backend и frontend PR - **Depends on:** 4.2, 4.3, 4.4, 4.5 ## 5. Specs: G4 и завершение change - [ ] 5.1 [specs] Verification: запись G4 в process-record с evidence задач 1–4 - **Covers:** N/A — gate протокол - **Source:** instruction `process-record` (Readiness Checklist, Gate Log) - **Verification:** `npx -y @fission-ai/openspec@1.13.1 validate account-top-positions --store wealth-ledger-specs --strict` valid; `git diff --check` чист; строка G4 содержит ссылки на PR, отчёты 3.1–3.3 и решение владельца - **Depends on:** 3.1, 3.2, 3.3, 3.4, 4.6 - [ ] 5.2 [specs] Verification: native archive change после G4 и PR в `develop` со ссылкой на WealthLedger/main#48 - **Covers:** N/A — native sync/archive выполняет CLI, canonical specs вручную не правятся - **Source:** schema `wealth-ledger-sdd-lab` apply/archive instruction; `.ai/conventions/engineering-workflow.md` «Merge → Issue Closed» - **Verification:** `npx -y @fission-ai/openspec@1.13.1 archive account-top-positions --store wealth-ledger-specs` завершён; `openspec validate --all --strict` valid; PR specs открыт; Issue main#48 закрывается после merge всех PR - **Depends on:** 5.1