diff --git a/openspec/changes/account-top-positions/tasks.md b/openspec/changes/account-top-positions/tasks.md index 4c295d7..f8a376b 100644 --- a/openspec/changes/account-top-positions/tasks.md +++ b/openspec/changes/account-top-positions/tasks.md @@ -4,49 +4,49 @@ Issue-координатор: [WealthLedger/main#48](https://git.personal.ksv741 ## 1. Backend: хранение, классификация, DTO, OpenAPI -- [ ] 1.1 [backend] Verification: ветка `feature/48-account-top-positions` от актуального `develop` +- [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 -- [ ] 1.2 [backend] Migration: аддитивная модель `TbankInstrumentClassification` и миграция `CREATE TABLE` +- [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 -- [ ] 1.3 [backend] Implementation: `TbankClient.getEtf`, `getAssetBrand` и opt-in NOT_FOUND для новых вызовов классификации +- [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 -- [ ] 1.4 [backend] Implementation: `PositionClassificationService` — правило 24 ч, четыре исхода по каждому полю, UID→FIGI после NOT_FOUND, дедупликация по инструменту и `asset_uid` +- [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 -- [ ] 1.5 [backend] Implementation: шаг классификации в `PortfolioService.queueSnapshot` до publish; `walkStartedAt` из `TbankHourlyScheduler`; mapper пишет `sector`/`issuer` +- [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 -- [ ] 1.6 [backend] Implementation: DTO `sector` и `PortfolioIssuerDto` (nullable), нормализация старых Snapshot в `saved()` +- [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 -- [ ] 1.7 [backend] Contract Parity: OpenAPI-artifact с новыми полями совпадает с target-delta +- [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 -- [ ] 1.8 [backend] Verification: полный gate и PR в `develop` со ссылкой на WealthLedger/main#48 +- [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 @@ -54,55 +54,55 @@ Issue-координатор: [WealthLedger/main#48](https://git.personal.ksv741 ## 2. Frontend: контракт, модель, UI -- [ ] 2.1 [frontend] Verification: ветка `feature/48-account-top-positions` от актуального `develop` +- [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 -- [ ] 2.2 [frontend] Implementation: предоставленный backend snapshot в `src/shared/api/spec/openapi.json` и generated `schema.d.ts` +- [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 -- [ ] 2.3 [frontend] Implementation: `pages/account/model/concentration.ts` — состав разрезов, учитываемые позиции, валюты, точные суммы и доли, топ-5, «Прочее», порядок равных, имя эмитента, группы «не указан» +- [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 -- [ ] 2.4 [frontend] Implementation: state `{accountId, tab, slice, currency}` в `AccountPage.vm.ts`, управляемые `Tabs`, вкладка только для одного счёта +- [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 -- [ ] 2.5 [frontend] Implementation: `Concentration.view` — список, раскрытие «Прочее», пустое состояние, сноска, выбор валюты, Alert «Данные неполные», подавление страничного Alert на вкладке +- [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 -- [ ] 2.6 [frontend] Implementation: DonutChart — центр-оверлей, обводка и штриховка через `cellProps`, цифры ранга, текстовый эквивалент +- [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 -- [ ] 2.7 [frontend] Implementation: подписи секторов через общий словарь `stock.sectors` + ключ `government` («Государственный сектор»), видимый и в каталогах +- [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 -- [ ] 2.8 [frontend] Automated Testing: регрессия вкладки «Позиции» и «Структуры портфеля» +- [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 -- [ ] 2.9 [frontend] Verification: полный gate и PR в `develop` со ссылкой на WealthLedger/main#48 и backend PR +- [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 @@ -136,37 +136,37 @@ Issue-координатор: [WealthLedger/main#48](https://git.personal.ksv741 ## 4. Docs (по analysis/docs-impact.md, после merge кода в `develop`) -- [ ] 4.1 [docs] Verification: ветка `feature/48-account-top-positions` от актуального `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 -- [ ] 4.2 [docs] Docs: домен Portfolio — `overview.md`, при необходимости `concentration.md`, `data-reference.md`, `flows/portfolio-loading.md`, условная строка в `all-accounts.md` +- [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 -- [ ] 4.3 [docs] Docs: интеграция и архитектура — `integrations/tbank.md`, `architecture/cross-cutting/data-freshness.md`, `architecture/containers.md` +- [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 -- [ ] 4.4 [docs] Docs: ADR 022 `stored-instrument-classification` (Accepted, AS-IS), строка в `adr/README.md`, ссылка в Status ADR 019 +- [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 -- [ ] 4.5 [docs] Docs: глоссарий и навигация — `glossary.md`, `overview.md`, `README.md` +- [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 -- [ ] 4.6 [docs] Verification: PR в `develop` со ссылкой на WealthLedger/main#48 и code PR +- [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