docs(specs): archive domain money contract change
This commit is contained in:
+8
@@ -0,0 +1,8 @@
|
||||
schema: wealth-ledger-sdd
|
||||
created: 2026-08-25
|
||||
goal: Align WealthLedger investment domain terminology and make Future Cash Event money use the canonical Money contract.
|
||||
affected_areas:
|
||||
- frontend
|
||||
- backend
|
||||
- specs
|
||||
- docs
|
||||
@@ -0,0 +1,163 @@
|
||||
## Context
|
||||
|
||||
Поведенческий contract зафиксирован в delta specs. Текущее состояние уже близко
|
||||
к целевой модели, но терминология и один public API fragment расходятся:
|
||||
Portfolio и Ledger используют Money как `{ currency, value }`, а Future Cash
|
||||
Event возвращает прогнозную сумму двумя nullable полями. Docs и specs местами
|
||||
говорят о счёте как о принадлежащем User, хотя runtime проверяет доступность
|
||||
через текущий T-Bank connection и не хранит Brokerage Account как durable
|
||||
aggregate.
|
||||
|
||||
Backend уже имеет общий Money DTO для Portfolio и Account Ledger, нормализацию
|
||||
денежных значений, проверку account access через T-Bank connection и generated
|
||||
OpenAPI как источник frontend types. Frontend уже умеет форматировать Money
|
||||
через portfolio display helper, но страницы будущих событий и overview читают
|
||||
`amount`/`currency` напрямую. Docs содержит canonical domain glossary, feature
|
||||
docs и system overview, которые должны описывать только as-built состояние
|
||||
после реализации.
|
||||
|
||||
Ограничение SDD: этот change меняет публичный контракт финансовой read-модели,
|
||||
поэтому реализация должна синхронизировать backend runtime OpenAPI, generated
|
||||
frontend types, frontend consumers, tests, specs и docs в одном delivery.
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
|
||||
- Сделать Future Cash Event прогнозную сумму единым nullable Money value во
|
||||
всём runtime contract, generated contract и frontend rendering.
|
||||
- Закрепить единый доменный язык для доступности Brokerage Account через
|
||||
текущий T-Bank connection без durable ownership claim.
|
||||
- Явно различить Portfolio Snapshot, Portfolio Position, Currency Instrument
|
||||
Position и Cash Balance в specs, docs и проверяемом поведении.
|
||||
- Расширить Instrument language на Future Cash Events без изменения смысла
|
||||
существующих инструментальных identifiers.
|
||||
- Обновить glossary и feature docs только после реализации, чтобы они описали
|
||||
фактическое поведение, а не план.
|
||||
|
||||
**Non-Goals:**
|
||||
|
||||
- Не переименовывать публичные routes, пользовательские страницы или legacy
|
||||
controller/service names только ради терминологии.
|
||||
- Не вводить storage для Brokerage Account, Portfolio, Portfolio Snapshot,
|
||||
Ledger Entry или Future Cash Event.
|
||||
- Не менять supported account type policy, T-Bank token lifecycle, upstream
|
||||
data sources, cache TTL, stale fallback или сортировку событий.
|
||||
- Не выполнять широкую модульную перестройку NestJS/React сверх мест,
|
||||
необходимых для contract и terminology alignment.
|
||||
|
||||
## Decisions
|
||||
|
||||
### D1. Сделать Future Cash Event amount единственным nullable Money field
|
||||
|
||||
Backend Upcoming Event DTO должен перейти с пары `amount`/`currency` на одно
|
||||
nullable Money-поле для прогнозной суммы. Coupon, dividend и maturity получают
|
||||
Money в исходной валюте, когда сумма известна; offer и неизвестные денежные
|
||||
условия получают `null`. Расчёт количества, даты, event type, identifiers,
|
||||
sorting и `calculatedAt` сохраняются.
|
||||
|
||||
Rationale: это убирает частный контракт, который противоречит Money в Portfolio
|
||||
и Ledger, и делает неизвестность явной: нет суммы целиком, а не отдельно нет
|
||||
amount или currency. Alternative оставить два поля и описать их как legacy
|
||||
отклонена, потому что она закрепляет второй денежный язык и заставляет
|
||||
frontend каждый раз реконструировать Money вручную.
|
||||
|
||||
### D2. Переиспользовать существующую Money normalization surface
|
||||
|
||||
Design не вводит новую денежную библиотеку или второй DTO. Upcoming Events
|
||||
использует тот же shape и правила десятичных строк, что Portfolio и Account
|
||||
Ledger: ISO currency, decimal string, no exponential notation, missing value as
|
||||
`null`. Tests должны покрыть переходные edge cases: рассчитанная сумма,
|
||||
нулевая/отсутствующая сумма offer, отрицательная или дробная normalization
|
||||
там, где она проходит через общий money formatting path.
|
||||
|
||||
Alternative отдельный `ProjectedAmount` shape отклонена: доменно это тот же
|
||||
Money, только nullable по uncertainty события.
|
||||
|
||||
### D3. Оставить route/UI names стабильными, уточнив domain language
|
||||
|
||||
Публичные routes и пользовательские entrypoints остаются `brokerage-accounts`,
|
||||
Portfolio page и «Будущие события». Термины Portfolio Snapshot, Currency
|
||||
Instrument Position и Cash Balance закрепляются в requirements, glossary,
|
||||
feature docs и test naming там, где они объясняют поведение. Runtime class/file
|
||||
names можно менять только если это локально улучшает читаемость без широкой
|
||||
цепочки churn.
|
||||
|
||||
Rationale: пользователю и API consumers не нужна массовая переадресация из-за
|
||||
уточнения модели; ценность change в согласованном смысле и денежном contract.
|
||||
|
||||
### D4. Считать account access текущим наблюдаемым правом через T-Bank connection
|
||||
|
||||
Implementation не должна добавлять ownership state. Все account-scoped flows
|
||||
продолжают проверять, что account открыт, поддержан и доступен через текущий
|
||||
T-Bank connection User на момент запроса. Specs/docs/client copy используют
|
||||
этот язык; code tests защищают existing observable behavior, если затронутая
|
||||
реализация меняется.
|
||||
|
||||
Alternative хранить список счетов как owned accounts отклонена: это расширяет
|
||||
domain и storage без требования продукта и противоречит текущей брокерской
|
||||
интеграции.
|
||||
|
||||
### D5. Frontend consumes generated Money contract directly
|
||||
|
||||
После backend contract change generated OpenAPI types синхронизируются штатным
|
||||
workflow. Frontend pages, account overview block, event entity tests и MSW
|
||||
fixtures переходят на nullable Money-поле и используют существующий
|
||||
`formatMoney` без ручной сборки `{ currency, value }`. UI copy «без суммы»
|
||||
сохраняется для `null`, а known Money отображается в исходной валюте.
|
||||
|
||||
Alternative временно поддерживать оба shapes во frontend отклонена для этого
|
||||
локального продукта: это усложняет presentation path без совместимого backend
|
||||
режима и оставляет двусмысленность в tests.
|
||||
|
||||
## Delivery Map
|
||||
|
||||
| Area | Status | Architectural outcome or N/A reason |
|
||||
| --- | --- | --- |
|
||||
| frontend | `affected` | Generated API types, event fixtures, account overview and full events page consume nullable Money for projected event amount and preserve existing event UI behavior. |
|
||||
| backend | `affected` | Upcoming Events runtime DTO/service/tests expose projected event amount as nullable Money and reuse existing money normalization semantics; account access behavior remains current-connection based. |
|
||||
| specs | `affected` | Proposal, Delta Specs and this Design define the canonical domain and contract alignment; archive later syncs modified requirements into main specs. |
|
||||
| docs | `affected` | Domain glossary, system/feature docs and any affected README/index references describe as-built canonical terminology after implementation without duplicating SDD artifacts. |
|
||||
|
||||
### Contract Gate Decision
|
||||
|
||||
| Gate | Status | Reason |
|
||||
| --- | --- | --- |
|
||||
| runtime-openapi | `required` | Backend response schema and frontend generated consumer types both change for Upcoming Events, so runtime OpenAPI must be compared and synchronized before completion. |
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- **Breaking consumer contract.** Existing consumers using `amount` and
|
||||
`currency` break. Mitigation: update generated types, fixtures and all
|
||||
in-repo frontend consumers in the same change; no compatibility layer because
|
||||
product is local and the old shape contradicts canonical Money.
|
||||
- **Terminology-only churn hiding behavior changes.** Many docs/spec words
|
||||
change without runtime changes. Mitigation: keep implementation edits scoped
|
||||
to observable contract and targeted regressions; docs update only as-built
|
||||
terms after verification.
|
||||
- **Money uncertainty can be mistaken for zero.** Events without known monetary
|
||||
terms must not show `0`. Mitigation: DTO and UI tests cover `null` projected
|
||||
amount and existing «без суммы» rendering.
|
||||
- **Currency instrument vs cash balance confusion persists in fixtures.**
|
||||
Mitigation: add or preserve focused portfolio fixture/test proving
|
||||
`CURRENCY` Portfolio Position remains separate from Cash Balance.
|
||||
|
||||
## Migration Plan
|
||||
|
||||
1. Update backend Upcoming Events DTO and mapping to expose nullable Money for
|
||||
projected event amount while preserving event selection, sorting and
|
||||
calculation timing.
|
||||
2. Update backend tests for coupon/dividend/maturity Money, offer `null`, and
|
||||
unchanged account access/error behavior where touched.
|
||||
3. Regenerate or sync OpenAPI/frontend contract artifacts through the
|
||||
established contract workflow and verify runtime OpenAPI.
|
||||
4. Update frontend generated consumers, fixtures, event page and account
|
||||
overview event block to render nullable Money directly.
|
||||
5. Update frontend tests for known event amount and no-amount states.
|
||||
6. Update docs glossary and affected feature/system documentation to as-built
|
||||
terminology after implementation verification.
|
||||
7. Run affected local gates plus the workspace change dispatcher before
|
||||
archive.
|
||||
|
||||
Rollback is a normal code/docs/spec revert: no database migration, persisted
|
||||
state migration, cache invalidation or dependency rollback is introduced.
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
# align-domain-model-and-money-contract — Implementation Summary
|
||||
|
||||
## Semantic Verification
|
||||
|
||||
Date: 2026-08-25
|
||||
|
||||
Checked areas:
|
||||
|
||||
- Future Cash Event contract: backend `UpcomingEventDto` now exposes nullable
|
||||
`projectedAmount` as canonical Money `{ currency, value }`; old event-level
|
||||
`amount` and `currency` fields were removed from the runtime OpenAPI schema
|
||||
and generated frontend types.
|
||||
- Money semantics: coupon, dividend and maturity events return Money in the
|
||||
source currency when currency and amount are known; offer and source events
|
||||
without currency return `null`, not zero or an invented currency.
|
||||
- Account access semantics: Upcoming Events still resolves account-scoped data
|
||||
through the existing current T-Bank connection access boundary; inaccessible
|
||||
accounts still short-circuit without reading upstream portfolio/events.
|
||||
- Portfolio terminology: runtime Portfolio behavior was not changed; docs and
|
||||
specs now distinguish Portfolio Snapshot, Portfolio Position, Currency
|
||||
Instrument Position and Cash Balance without altering route names or storage.
|
||||
- Frontend contract consumption: event page, account overview recent events,
|
||||
hooks, fixtures and generated schema now consume `projectedAmount` directly
|
||||
and keep the existing «Сумма неизвестна» UI for `null`.
|
||||
- Docs as-built state: glossary and affected feature/system docs describe the
|
||||
delivered Money contract and current-connection account accessibility without
|
||||
copying Proposal, Design or Tasks as a parallel SDD artifact.
|
||||
- Non-goals: routes, supported account types, token lifecycle, T-Bank upstream
|
||||
sources, cache TTLs, sorting, durable storage and broad module structure were
|
||||
not changed.
|
||||
|
||||
Conclusion:
|
||||
|
||||
The implementation matches the approved Proposal, Delta Specs and Design. The
|
||||
only public API contract break is the intended Future Cash Event projected
|
||||
amount change from separate nullable amount/currency fields to nullable Money.
|
||||
@@ -0,0 +1,68 @@
|
||||
## Why
|
||||
|
||||
После нескольких итераций вокруг счетов, Portfolio, операций и будущих
|
||||
денежных событий в модели появились близкие, но не полностью согласованные
|
||||
формулировки. Одни документы говорят о принадлежности Brokerage Account User,
|
||||
другие — о доступности счёта через текущее подключение брокера. Денежные
|
||||
значения в части потоков описаны как единый Money, а в будущих событиях
|
||||
разделены на сумму и валюту. Валютная позиция инструмента и Cash Balance также
|
||||
легко смешиваются в чтении, хотя в поведении системы они имеют разные роли.
|
||||
|
||||
Эти расхождения уже затрагивают публичное понимание продукта: разработчик,
|
||||
агент или downstream consumer может по-разному интерпретировать доступность
|
||||
счёта, свежесть Portfolio, денежные прогнозы и связь Instrument с событиями.
|
||||
Исправить это стоит сейчас, пока область T-Bank-инвестиций ещё компактна и до
|
||||
следующих расширений по операциям, прогнозам и аналитике.
|
||||
|
||||
## What Changes
|
||||
|
||||
- Согласовать канонический язык вокруг Brokerage Account: система описывает
|
||||
счёт как доступный User через его текущее подключение брокера, не как
|
||||
долговременно принадлежащий User объект внутри WealthLedger.
|
||||
- Согласовать денежные значения во всех инвестиционных read-моделях вокруг
|
||||
единого Money-представления с явной валютой и отсутствующим значением как
|
||||
неизвестностью, а не нулём.
|
||||
- Уточнить различие между Currency Instrument Position и Cash Balance: первая
|
||||
является позицией по торгуемому инструменту, вторая — денежным остатком на
|
||||
счёте.
|
||||
- Сделать язык Portfolio Snapshot явным там, где речь идёт о результате
|
||||
наблюдения Portfolio на конкретный момент с признаком свежести.
|
||||
- Расширить доменный смысл Instrument так, чтобы он охватывал не только
|
||||
Portfolio Position, но и связанные события по инструменту.
|
||||
- Структурировать domain glossary так, чтобы доменные термины не смешивались с
|
||||
runtime/cache-механизмами, сохранив последние как технические ограничения
|
||||
текущего поведения.
|
||||
- **Non-goals:** не менять физическую модульную структуру приложения, не
|
||||
вводить новое долговременное хранение Portfolio, событий или счетов, не
|
||||
переименовывать массово существующие пользовательские маршруты и не менять
|
||||
брокерскую интеграцию сверх необходимого согласования observable contract.
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
_Нет._
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
- `brokerage-accounts`: требования к счетам должны использовать единый язык
|
||||
доступности через текущее подключение брокера и не создавать ложное
|
||||
ожидание долговременного владения счётом внутри WealthLedger.
|
||||
- `portfolio-view`: требования к Portfolio должны явно различать Portfolio
|
||||
Snapshot, Cash Balance и позиции по валютным инструментам, а также
|
||||
согласованно описывать Money и Instrument.
|
||||
- `brokerage-account-upcoming-events`: требования к будущим событиям должны
|
||||
использовать тот же язык доступности счёта, Money и Instrument, что и
|
||||
Portfolio.
|
||||
- `project-documentation`: glossary и durable documentation должны закрепить
|
||||
единый доменный язык и отделить его от runtime/cache терминов.
|
||||
|
||||
## Impact
|
||||
|
||||
Затрагиваются пользователи и разработчики, которые читают инвестиционные
|
||||
экраны, API contract, OpenSpec requirements и as-built документацию по
|
||||
T-Bank-счетам, Portfolio и будущим денежным событиям. Изменение является
|
||||
контрактным для финансовых read-моделей и потребует синхронного обновления
|
||||
SDD, backend contract, frontend consumer types, пользовательских состояний и
|
||||
документации. Долговременная модель хранения User, Session и T-Bank connection
|
||||
не расширяется.
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: User can retrieve upcoming events for an account
|
||||
Система MUST предоставлять аутентифицированному User read-only список известных будущих событий открытого поддерживаемого Brokerage Account, доступного через текущий T-Bank connection User, от момента запроса до 30 календарных дней после него включительно. Ответ MUST содержать только события coupon, dividend, maturity и offer, отсортированные по дате по возрастанию, и момент расчёта прогноза.
|
||||
|
||||
Список будущих событий является прогнозом по текущему наблюдаемому состоянию счёта и MUST NOT означать, что WealthLedger хранит долговременное право владения Brokerage Account независимо от T-Bank connection.
|
||||
|
||||
#### Scenario: Получение событий доступного счёта
|
||||
- **WHEN** аутентифицированный User запрашивает будущие события открытого поддерживаемого Brokerage Account, доступного через текущий T-Bank connection
|
||||
- **THEN** система возвращает известные события на следующие 30 календарных дней и момент, на который был выполнен расчёт
|
||||
|
||||
#### Scenario: Отсутствие событий в периоде
|
||||
- **WHEN** для счёта нет известных событий в ближайшие 30 календарных дней
|
||||
- **THEN** система возвращает успешный пустой список и момент расчёта
|
||||
|
||||
#### Scenario: Отклонение недоступного счёта
|
||||
- **WHEN** аутентифицированный User запрашивает события счёта, который не является открытым поддерживаемым счётом, доступным через его T-Bank connection
|
||||
- **THEN** система возвращает существующий ответ о недоступности счёта и не раскрывает события
|
||||
|
||||
### Requirement: Forecasts preserve event meaning and uncertainty
|
||||
Система MUST передавать для каждого события его календарную дату, тип, доступные идентификаторы и название Instrument, а также прогнозную сумму до налогов как Money, если она известна. Прогнозная сумма MUST рассчитываться по текущему количеству бумаг на момент запроса. Если денежные условия события неизвестны или неприменимы, прогнозная сумма MUST быть `null`, а не нулём и не парой разрозненных полей. Дивиденд MUST обозначать переданную дату как дату реестра, а offer MUST передаваться без вымышленной суммы.
|
||||
|
||||
Instrument в Future Cash Event MUST иметь тот же доменный смысл, что и в Portfolio: это финансовый инструмент, к которому относится событие, даже если событие не является Portfolio Position.
|
||||
|
||||
#### Scenario: Прогноз купона по текущей позиции
|
||||
- **WHEN** поддерживаемая облигация в текущей Portfolio Position имеет известный купон в пределах периода
|
||||
- **THEN** система возвращает событие coupon с прогнозной суммой до налогов как Money в исходной валюте, рассчитанной по текущему количеству облигаций
|
||||
|
||||
#### Scenario: Прогноз дивиденда
|
||||
- **WHEN** поддерживаемая акция в текущей Portfolio Position имеет объявленный дивиденд с датой фиксации реестра в пределах периода
|
||||
- **THEN** система возвращает событие dividend с датой, обозначенной как дата реестра (`record_date`), и прогнозной суммой до налогов как Money, равной `dividend_net` на одну бумагу, умноженной на текущее количество акций
|
||||
|
||||
#### Scenario: Отменённый дивиденд не попадает в прогноз
|
||||
- **WHEN** запись дивиденда T-Bank имеет тип выплаты `Cancelled`
|
||||
- **THEN** система не добавляет такое событие в результат
|
||||
|
||||
#### Scenario: Отображение оферты без суммы
|
||||
- **WHEN** для поддерживаемой облигации известна оферта в пределах периода, но её денежные условия недоступны
|
||||
- **THEN** система возвращает событие offer с прогнозной суммой `null`
|
||||
|
||||
#### Scenario: Неподдерживаемое частичное погашение
|
||||
- **WHEN** источник не предоставляет расписание будущих частичных погашений или амортизации облигации
|
||||
- **THEN** система НЕ создаёт синтетическое событие частичного погашения
|
||||
|
||||
### Requirement: Account page presents an upcoming events list
|
||||
Страница Brokerage Account MUST предоставлять действие перехода на отдельную страницу «Будущие события». Эта страница MUST показывать все полученные события компактным списком без фильтров, пагинации и суммарного итога, с датой, типом, инструментом и прогнозной суммой в исходной валюте, если она известна. Страница MUST объяснять, что прогноз основан на текущей позиции, указан до налогов и может быть неполным из-за доступности данных источника.
|
||||
|
||||
#### Scenario: Просмотр ближайших событий
|
||||
- **WHEN** User открывает «Будущие события» из обзора Brokerage Account
|
||||
- **THEN** он видит отсортированный список событий ближайших 30 дней, где offer обозначен без суммы
|
||||
|
||||
#### Scenario: Пустое состояние страницы
|
||||
- **WHEN** запрос будущих событий успешно возвращает пустой список
|
||||
- **THEN** страница сообщает, что на ближайшие 30 дней событий не найдено, и объясняет ограничения покрытия источника
|
||||
|
||||
#### Scenario: Ошибка страницы
|
||||
- **WHEN** запрос будущих событий завершается ошибкой
|
||||
- **THEN** страница не показывает частичный или устаревший список и предоставляет действие повторить запрос
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Список активных брокерских счетов
|
||||
Система SHALL предоставлять авторизованному User `GET /api/v1/brokerage-accounts`, возвращающий массив открытых поддерживаемых инвестиционных счетов T-Bank, доступных через текущий T-Bank connection User, в порядке, полученном от T-Bank. Каждый элемент MUST содержать стабильный `id`, непустое `name`, нормализованный `type` (`BROKERAGE`, `INDIVIDUAL_INVESTMENT`, `INVEST_BOX` или `MONEY_MARKET_FUND`) и nullable `openedOn` в формате `YYYY-MM-DD`. Система MUST включать только счета T-Bank типов «брокерский счёт», «ИИС», «Инвесткопилка» и «фонд денежного рынка»; банковские и неизвестные типы MUST NOT возвращаться. Система MUST удалить внешние пробелы из имени и при пустом результате использовать непустое название соответствующего типа.
|
||||
|
||||
Список Brokerage Account является результатом наблюдения текущего брокерского подключения и MUST NOT создавать ожидание, что WealthLedger хранит долговременное право владения счётом независимо от T-Bank connection.
|
||||
|
||||
#### Scenario: Поддерживаемые счета возвращаются в upstream-порядке
|
||||
- **WHEN** User с действующим T-Bank connection запрашивает список, а T-Bank возвращает открытые брокерский счёт, ИИС и неподдержанный дебетовый счёт
|
||||
- **THEN** система отвечает `200` массивом брокерского счёта и ИИС в исходном порядке T-Bank без дебетового счёта
|
||||
|
||||
#### Scenario: Пустой список поддерживаемых счетов
|
||||
- **WHEN** User с действующим T-Bank connection не имеет открытых поддерживаемых инвестиционных счетов
|
||||
- **THEN** система отвечает `200` пустым массивом
|
||||
|
||||
#### Scenario: Пустое название получает fallback
|
||||
- **WHEN** T-Bank возвращает поддерживаемый открытый счёт с пустым либо состоящим из пробелов именем
|
||||
- **THEN** система отвечает этим счётом с непустым названием его нормализованного типа
|
||||
|
||||
#### Scenario: Дата открытия отсутствует
|
||||
- **WHEN** T-Bank не возвращает дату открытия поддерживаемого открытого счёта
|
||||
- **THEN** система отвечает этим счётом с `openedOn: null`
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Получение Portfolio выбранного Brokerage Account
|
||||
Система SHALL предоставлять авторизованному User `GET /api/v1/brokerage-accounts/:accountId/portfolio`. Она MUST возвращать нормализованный полный Portfolio Snapshot только для открытого поддерживаемого Brokerage Account, доступного через текущий T-Bank connection User. Ответ MUST содержать `lastSuccessfulAt` в RFC 3339 UTC без миллисекунд, boolean `isStale`, `totalsByCurrency`, `positions` и `cashBalances`.
|
||||
|
||||
Portfolio Snapshot является результатом наблюдения Portfolio на конкретный момент. Он MUST NOT означать, что WealthLedger хранит Portfolio как долговременный агрегат или подтверждает доступность Brokerage Account вне текущего T-Bank connection.
|
||||
|
||||
#### Scenario: Получение текущего Portfolio
|
||||
- **WHEN** User запрашивает Portfolio доступного Brokerage Account, а обязательные upstream-данные успешно получены
|
||||
- **THEN** система отвечает `200` полным нормализованным Portfolio Snapshot с `isStale: false` и временем успешного получения финансового снимка
|
||||
|
||||
#### Scenario: Недоступный Brokerage Account
|
||||
- **WHEN** `accountId` не принадлежит User, закрыт, неподдерживаем или больше не доступен через текущий T-Bank connection
|
||||
- **THEN** система отвечает `404` без Portfolio Snapshot
|
||||
|
||||
### Requirement: Нормализованный состав Portfolio
|
||||
Система MUST возвращать только обычные Portfolio Position с ненулевым количеством и доступные Cash Balance с ненулевой суммой. Cash Balance MUST быть отдельным от Portfolio Position элементом и агрегироваться до одного значения на валюту. Portfolio Position с типом `CURRENCY` MUST означать позицию по торгуемому валютному Instrument и MUST NOT смешиваться с Cash Balance. Порядок Portfolio Position и Cash Balance MUST соответствовать upstream-порядку; система MUST NOT сортировать или объединять Portfolio Position с одинаковым FIGI. Виртуальные позиции и заблокированные денежные остатки MUST NOT входить в Portfolio.
|
||||
|
||||
Каждая Portfolio Position MUST содержать нормализованный тип `STOCK`, `BOND`, `FUND`, `CURRENCY`, `FUTURE`, `OPTION`, `STRUCTURED_NOTE`, `DIGITAL_FINANCIAL_ASSET` либо `OTHER`, строковое `quantity`, nullable `currentValue`, nullable `averagePurchasePrice`, nullable `absoluteReturn`, nullable `isin` и nullable `logoName` — имя файла логотипа бренда инструмента. Имя файла логотипа MUST заполняться из metadata инструмента, когда upstream предоставляет бренд с непустым именем файла логотипа; во всех остальных случаях оно MUST быть `null`. Логотип является presentation metadata: он не влияет на количество, стоимость, агрегаты, успешность или свежесть Portfolio Snapshot. Система MUST искать metadata по instrument UID при его наличии, затем по FIGI. Отображаемый идентификатор MUST предпочитать name, затем ticker, а при недоступности metadata использовать FIGI, не скрывая финансовые значения.
|
||||
|
||||
#### Scenario: Позиция с неизвестным upstream-типом
|
||||
- **WHEN** upstream возвращает обычную ненулевую позицию с нераспознанным типом инструмента
|
||||
- **THEN** система возвращает её в исходном порядке с типом `OTHER`
|
||||
|
||||
#### Scenario: Позиция валютного инструмента не является денежным остатком
|
||||
- **WHEN** upstream возвращает ненулевую Portfolio Position валютного инструмента и ненулевой Cash Balance той же валюты
|
||||
- **THEN** система возвращает Portfolio Position с типом `CURRENCY` отдельно от Cash Balance и не объединяет их между собой
|
||||
|
||||
#### Scenario: Позиция без metadata инструмента
|
||||
- **WHEN** lookup metadata по FIGI не дал ticker или name либо временно недоступен
|
||||
- **THEN** система возвращает позицию с FIGI как fallback-идентификатором и всеми доступными финансовыми значениями
|
||||
|
||||
#### Scenario: Имя инструмента доступно
|
||||
- **WHEN** metadata инструмента по FIGI содержит непустое name
|
||||
- **THEN** система возвращает name как отображаемый идентификатор позиции вместо FIGI или ticker
|
||||
|
||||
#### Scenario: FIGI не разрешается, но доступен instrument UID
|
||||
- **WHEN** FIGI позиции не даёт metadata, а её instrument UID возвращает непустое name
|
||||
- **THEN** система возвращает name, полученное по instrument UID
|
||||
|
||||
#### Scenario: ISIN инструмента доступен
|
||||
- **WHEN** metadata инструмента позиции содержит непустой `isin`
|
||||
- **THEN** система возвращает его в поле `isin` позиции
|
||||
|
||||
#### Scenario: ISIN инструмента отсутствует
|
||||
- **WHEN** у инструмента позиции нет ISIN либо metadata недоступна
|
||||
- **THEN** система возвращает позицию с `isin: null`, не скрывая финансовые значения
|
||||
|
||||
#### Scenario: Имя файла логотипа бренда доступно
|
||||
- **WHEN** metadata инструмента позиции содержит бренд с непустым именем файла логотипа
|
||||
- **THEN** система возвращает его в поле `logoName` позиции
|
||||
|
||||
#### Scenario: Имя файла логотипа бренда отсутствует
|
||||
- **WHEN** у инструмента позиции нет бренда, имя файла логотипа пустое либо metadata недоступна
|
||||
- **THEN** система возвращает позицию с `logoName: null`, не скрывая финансовые значения
|
||||
|
||||
#### Scenario: Денежные остатки в одной валюте
|
||||
- **WHEN** upstream возвращает несколько ненулевых доступных денежных остатков одной валюты
|
||||
- **THEN** система возвращает один Cash Balance с их точной суммой
|
||||
|
||||
#### Scenario: Штатный пустой Portfolio
|
||||
- **WHEN** у доступного Brokerage Account нет ненулевых обычных позиций и доступных денежных остатков
|
||||
- **THEN** система отвечает `200` с пустыми `positions`, `cashBalances` и `totalsByCurrency`
|
||||
|
||||
### Requirement: Точные значения и валютные агрегаты
|
||||
Все количества и финансовые значения в Portfolio Snapshot MUST передаваться десятичными строками без экспоненциальной записи и без незначащих завершающих нулей. Денежное значение MUST быть единым Money-значением с ISO-кодом валюты и десятичной строкой значения. Система MUST корректно нормализовать отрицательные целую и дробную части upstream-значений. Отсутствующее значение MUST передаваться как `null`, а не как ноль.
|
||||
|
||||
`currentValue` Portfolio Position MUST быть точным произведением количества на текущую цену при наличии валидных множителей и валюты цены. `absoluteReturn` MUST быть точной величиной `(currentPrice - averagePurchasePrice) * quantity` только когда обе цены имеют одинаковую известную валюту. Позиция без вычислимой текущей стоимости MUST оставаться в ответе, но MUST NOT входить в агрегаты. `totalsByCurrency` MUST содержать точную сумму currentValue позиций и Cash Balance в каждой валюте без валютной конвертации.
|
||||
|
||||
#### Scenario: Мультивалютный Portfolio
|
||||
- **WHEN** Portfolio содержит финансовые значения в RUB и USD
|
||||
- **THEN** система возвращает отдельные агрегаты RUB и USD без общего сконвертированного итога
|
||||
|
||||
#### Scenario: Позиция без вычислимой стоимости
|
||||
- **WHEN** у ненулевой позиции отсутствует либо невалидна цена, количество или валюта цены
|
||||
- **THEN** система возвращает позицию с `currentValue: null` и не включает её в `totalsByCurrency`
|
||||
|
||||
#### Scenario: Отрицательная дробная часть upstream-значения
|
||||
- **WHEN** upstream возвращает сумму с `units: 0` и отрицательной дробной частью `nano: -500000000`
|
||||
- **THEN** система передаёт её как строку `"-0.5"`
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Устойчивая документация имеет доступные фактические точки входа
|
||||
Репозиторий `docs` SHALL содержать indexes архитектуры, разработки и фич,
|
||||
которые ведут разработчика или AI-агента к фактической устойчивой документации
|
||||
без обращения к архивированному change или корневому delivery-plan файлу.
|
||||
Архитектурная документация MUST содержать system overview поставленных
|
||||
возможностей и локальных границ, канонический domain glossary, а также
|
||||
Mermaid-диаграммы потоков auth/session и T-Bank → клиент. Feature documentation
|
||||
MUST предоставлять index поставленных фич и MUST NOT описывать поставленные
|
||||
Brokerage Accounts, Portfolio, Account Ledger или Future Cash Events как
|
||||
отложенные.
|
||||
|
||||
Канонический domain glossary MUST закреплять различие между Brokerage Account,
|
||||
доступным через текущее T-Bank connection, Portfolio Snapshot, Portfolio
|
||||
Position, Currency Instrument Position, Cash Balance, Money, Instrument, Ledger
|
||||
Entry и Future Cash Event. Runtime/cache-термины MUST быть отделены от
|
||||
доменных терминов так, чтобы документация не представляла технический механизм
|
||||
как бизнес-сущность.
|
||||
|
||||
#### Scenario: Новый участник находит поставленную систему
|
||||
- **WHEN** новый участник открывает устойчивую документацию
|
||||
- **THEN** он может перейти из index к system overview, glossary, документам
|
||||
поставленных фич и соответствующему development workflow
|
||||
|
||||
#### Scenario: Участник различает близкие доменные понятия
|
||||
- **WHEN** участнику нужно описать cached portfolio data, валютную позицию,
|
||||
денежный остаток или account operation
|
||||
- **THEN** канонический glossary различает runtime/cache-механизмы, Portfolio
|
||||
Snapshot, Currency Instrument Position, Cash Balance, Future Cash Event и
|
||||
Ledger Entry
|
||||
@@ -0,0 +1,35 @@
|
||||
## 1. Backend contract и money mapping
|
||||
|
||||
- [x] 1.1 Обновить `backend/src/tbank/upcoming-events.dto.ts`: заменить отдельные nullable `currency` и `amount` у Future Cash Event на одно nullable Money-поле прогнозной суммы, переиспользуя существующий Money DTO shape `{ currency, value }`; verification: `npm test -- tbank/upcoming-events.service.spec.ts --runInBand` в `backend` видит новый contract shape в expected events.
|
||||
- [x] 1.2 Обновить `backend/src/tbank/upcoming-events.service.ts`: coupon, dividend и maturity должны возвращать прогнозную сумму как Money в исходной валюте, offer и неизвестные денежные условия — `null`, без изменения периода, сортировки, `calculatedAt`, event types и source failure behavior; verification: `npm test -- tbank/upcoming-events.service.spec.ts --runInBand` в `backend`.
|
||||
- [x] 1.3 Расширить backend tests в `backend/src/tbank/upcoming-events.service.spec.ts` для нового Money contract: coupon/dividend/maturity содержат `{ currency, value }`, offer содержит `null`, отменённый dividend по-прежнему исключается, partial result при source failure не возвращается; verification: `npm test -- tbank/upcoming-events.service.spec.ts --runInBand` в `backend`.
|
||||
- [x] 1.4 Проверить, что account-scoped access semantics не изменились для Upcoming Events: недоступный, закрытый, неподдерживаемый или отсутствующий в текущем T-Bank connection Brokerage Account продолжает давать существующий недоступный-account response без раскрытия событий; verification: focused existing/new test в `backend/src/tbank/upcoming-events.service.spec.ts` и `npm test -- tbank/upcoming-events.service.spec.ts --runInBand` в `backend`.
|
||||
- [x] 1.5 Запустить backend contract и quality checks после DTO/service правок; verification: `npm run lint && npm run typecheck:app && npm run typecheck:test && npm test -- --runInBand` в `backend`.
|
||||
|
||||
## 2. Runtime OpenAPI и frontend generated contract
|
||||
|
||||
- [x] 2.1 Запустить штатную синхронизацию frontend OpenAPI artifacts после backend contract change; verification: `npm run openapi:sync` в `frontend` обновляет только `frontend/src/shared/api/spec/openapi.json` и `frontend/src/shared/api/generated/schema.d.ts` без ручной правки generated файла.
|
||||
- [x] 2.2 Проверить generated contract локально после sync; verification: `npm run openapi:check && npm run typecheck` в `frontend`.
|
||||
- [x] 2.3 Выполнить обязательный runtime contract gate для обеих сторон API; verification: `bash specs/scripts/verify-runtime-openapi.sh` из workspace root проходит после backend runtime schema и frontend committed snapshot sync.
|
||||
|
||||
## 3. Frontend consumers и пользовательское отображение событий
|
||||
|
||||
- [x] 3.1 Обновить event fixtures и tests в `frontend/src/entities/brokerage-event/api/useBrokerageEvents.test.tsx`, `frontend/src/pages/account-events/ui/AccountEventsPage.test.tsx`, `frontend/src/pages/account/ui/AccountPage.vm.test.tsx` и related MSW fixtures так, чтобы Future Cash Event использовал nullable Money-поле вместо `amount`/`currency`; verification: `npm run test:run -- brokerage-event AccountEventsPage AccountPage.vm` в `frontend`.
|
||||
- [x] 3.2 Обновить full events page в `frontend/src/pages/account-events/ui/AccountEventsPage.tsx`: known projected amount рендерится через существующий `formatMoney`, `null` сохраняет текущую copy без суммы, дата/type/instrument/error/empty states не меняются; verification: `npm run test:run -- AccountEventsPage` в `frontend`.
|
||||
- [x] 3.3 Обновить account overview recent events presentation в `frontend/src/pages/account/ui/components/RecentEvents.view.tsx` и view-model mapping в `frontend/src/pages/account/ui/AccountPage.vm.ts`, если generated type требует mapping change: known projected amount рендерится через `formatMoney`, `null` остаётся без суммы, limit/sorting берутся из существующего response без нового client-side поведения; verification: `npm run test:run -- AccountPage.vm AccountPage` в `frontend`.
|
||||
- [x] 3.4 Проверить, что frontend больше не обращается к removed `amount`/`currency` fields Future Cash Event вне generated snapshot diff; verification: `rg -n "\\b(amount|currency)\\b" frontend/src/entities/brokerage-event frontend/src/pages/account-events frontend/src/pages/account/ui -g "*.ts" -g "*.tsx"` reviewed manually for no legacy event-money access, затем `npm run typecheck` в `frontend`.
|
||||
- [x] 3.5 Запустить frontend canonical gate после consumer и generated правок; verification: `npm run verify` в `frontend`.
|
||||
|
||||
## 4. Docs и as-built domain language
|
||||
|
||||
- [x] 4.1 Обновить `docs/architecture/domain-glossary.md`: закрепить Brokerage Account как доступный через текущее T-Bank connection, Money как единый `{ currency, value }`, Portfolio Snapshot, Currency Instrument Position, Cash Balance, Instrument для Portfolio и Future Cash Event, а runtime/cache terms отделить от доменных терминов; verification: scoped markdown review плюс `npm run verify` в `docs`.
|
||||
- [x] 4.2 Обновить affected feature/system docs (`docs/features/brokerage-accounts.md`, `docs/features/portfolio-view.md`, `docs/features/brokerage-account-upcoming-events.md`, `docs/architecture/system-overview.md` при необходимости) до фактического поведения: Upcoming Events показывают nullable Money прогнозной суммы, Brokerage Account описан как current-connection доступность, Portfolio Snapshot и Cash Balance/валютная позиция различаются; verification: scoped link/review check и `npm run verify` в `docs`.
|
||||
- [x] 4.3 Проверить, что docs не стали параллельным SDD artifact и не описывают planned behavior до реализации; verification: review affected docs на отсутствие пересказа Proposal/Design/Tasks и `npm run verify` в `docs`.
|
||||
|
||||
## 5. Specs, semantic verification и archive readiness
|
||||
|
||||
- [x] 5.1 Проверить OpenSpec change structurally после всех planning artifacts и перед implementation handoff; verification: `openspec validate align-domain-model-and-money-contract --type change --strict --store wealth-ledger-specs` из `specs`.
|
||||
- [x] 5.2 После реализации выполнить semantic verification против Proposal, Delta Specs и Design: подтвердить, что changed public contract реализован только для Future Cash Event projected amount, account access остаётся current-connection based, Portfolio Snapshot/Cash Balance/Currency Instrument Position semantics не сломаны, routes/storage/cache/upstream policies не изменены; verification: зафиксировать checked areas и conclusion в implementation summary перед archive.
|
||||
- [x] 5.3 Выполнить affected repository gates под Node 22: backend `npm run verify`, frontend `npm run verify`, docs `npm run verify`, specs strict validation; verification: все commands завершаются успешно без auto-fix как completion evidence candidates.
|
||||
- [x] 5.4 Выполнить workspace dispatcher как pre-archive gate; verification: `bash specs/scripts/verify-workspace-change.sh align-domain-model-and-money-contract` из workspace root.
|
||||
- [x] 5.5 Провести independent read-only review generated-contract diff, backend money mapping, frontend rendering, docs terminology и OpenSpec deltas; verification: отсутствие Critical/Important findings до archive.
|
||||
Reference in New Issue
Block a user