Files
specs/openspec/changes/account-top-positions/tasks.md
T

188 lines
24 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
- [x] 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
- [x] 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
- [x] 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
- [x] 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
- [x] 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
- [x] 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
- [x] 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
- [x] 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
- [x] 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
- [x] 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
- [x] 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
- [x] 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
- [x] 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
- [x] 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
- [x] 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
- [x] 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
- [x] 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`)
- [x] 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
- [x] 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
- [x] 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
- [x] 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
- [x] 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
- [x] 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