spec: refine performance rules from implementation decisions (WealthLedger/main#54)
This commit is contained in:
@@ -65,17 +65,21 @@ Issue: WealthLedger/main#54
|
|||||||
приближение, точная десятичная степень для неё бессмысленна. Поэтому
|
приближение, точная десятичная степень для неё бессмысленна. Поэтому
|
||||||
решатель работает на float64 и выдаёт результат до 6 знаков (точности
|
решатель работает на float64 и выдаёт результат до 6 знаков (точности
|
||||||
хватает с запасом).
|
хватает с запасом).
|
||||||
- Алгоритм: Ньютон от r₀ = 0,1, переход на бисекцию по `[−0.9999, 100]`
|
- При обычном наборе потоков с одной сменой знака по датам Descartes'
|
||||||
при выходе за интервал или отсутствии сходимости за 50 шагов. Не больше
|
rule доказывает единственность корня: уточнить его бисекцией в
|
||||||
200 итераций, критерий — |NPV| ≤ 0,01 ₽.
|
`[−0.9999, 100]` до |NPV| ≤ 0,01 ₽. Нет корня → `NO_SOLUTION`.
|
||||||
- Перед поиском проверяется смена знака NPV на концах интервала; если её
|
При нескольких сменах знака нет доказательства единственности — пока
|
||||||
нет, результат `NO_SOLUTION`.
|
консервативно вернуть `AMBIGUOUS_RETURN`, даже если фактически корень
|
||||||
- *Отклонено:* npm-пакет XIRR (новая зависимость ради ~40 строк); чистая
|
единственный. Полное выделение всех корней добавлять только при
|
||||||
бисекция (медленнее, но остаётся запасным путём).
|
подтверждённой потребности.
|
||||||
4. **Переводы в `all`.** Деньги и бумаги сопоставляются в domain по правилам
|
- *Отклонено:* Ньютон от одного старта и проверка только знаков на концах:
|
||||||
System Analysis. Каждая операция участвует не более чем в одной паре:
|
они не выявляют несколько решений. Новая зависимость не нужна.
|
||||||
сортировка по `occurredAt`, жадное сопоставление с ближайшей подходящей
|
4. **Переводы в `all`.** Деньги и бумаги сопоставляются в domain только
|
||||||
операцией.
|
для выявления кандидатов (сумма/время либо инструмент/количество).
|
||||||
|
Кандидат без независимого признака связи не исключается из потоков:
|
||||||
|
затронутые счета исключаются из результата `all` с причиной
|
||||||
|
`UNCONFIRMED_TRANSFER`. Существующий T-Bank contract не даёт такого
|
||||||
|
признака, поэтому автоматически подтверждённых пар сейчас нет.
|
||||||
5. **История индексов: `MarketIndexHistoryService`** в
|
5. **История индексов: `MarketIndexHistoryService`** в
|
||||||
`src/modules/investments/market-index/`. В `MoexClient` добавляется метод
|
`src/modules/investments/market-index/`. В `MoexClient` добавляется метод
|
||||||
`indexHistory(secid, from, till)` с пагинацией по `history.cursor`. Если
|
`indexHistory(secid, from, till)` с пагинацией по `history.cursor`. Если
|
||||||
@@ -197,8 +201,8 @@ model MarketIndexSyncState {
|
|||||||
LibreOffice `XIRR` или Python `scipy.optimize`, и записываются как
|
LibreOffice `XIRR` или Python `scipy.optimize`, и записываются как
|
||||||
константы с допуском 1e-6;
|
константы с допуском 1e-6;
|
||||||
- примеры из спецификации: 10 % на 730 дней, 110 000 ₽ по индексу;
|
- примеры из спецификации: 10 % на 730 дней, 110 000 ₽ по индексу;
|
||||||
- `basis = period`, `NO_SOLUTION`, поток в выходной;
|
- `basis = period`, `NO_SOLUTION`, `AMBIGUOUS_RETURN` (несколько корней), поток в выходной;
|
||||||
- парность переводов, порядок причин;
|
- кандидаты на переводы без подтверждённой связи, порядок причин;
|
||||||
- тест клиента MOEX: пагинация, невалидная страница, таймаут — на
|
- тест клиента MOEX: пагинация, невалидная страница, таймаут — на
|
||||||
подменённом fetch, как в `moex.client.spec.ts`;
|
подменённом fetch, как в `moex.client.spec.ts`;
|
||||||
- e2e на SQLite: маршрут, доступ, `all`, сбой MOEX с сохранённой и без
|
- e2e на SQLite: маршрут, доступ, `all`, сбой MOEX с сохранённой и без
|
||||||
@@ -248,10 +252,10 @@ backend — маршруты и drop таблиц индексов.
|
|||||||
|
|
||||||
## Technical Risks
|
## Technical Risks
|
||||||
|
|
||||||
- **Парность переводов** может не совпасть с реальными данными T-Bank.
|
- **Неподтверждённые переводы** делают результат `all` неполным. Меры:
|
||||||
Меры: проверка на данных пользователя до закрытия change, правило
|
показать исключённые счета и причину; проверить на реальных данных,
|
||||||
изолировано в domain.
|
появится ли надёжный признак связи.
|
||||||
- **Нестабильность XIRR** при крупных выводах (несколько корней). Мера:
|
- **Неоднозначность XIRR** при крупных выводах (несколько корней). Мера:
|
||||||
проверка смены знака и `NO_SOLUTION` вместо сомнительного корня.
|
поиск возможных корней и `AMBIGUOUS_RETURN`, а не произвольный корень.
|
||||||
- **Формат ISS** может измениться. Мера: строгий разбор, сбой вместо
|
- **Формат ISS** может измениться. Мера: строгий разбор, сбой вместо
|
||||||
частичных данных, сохранённая история остаётся.
|
частичных данных, сохранённая история остаётся.
|
||||||
|
|||||||
@@ -22,17 +22,19 @@ Issue: WealthLedger/main#54
|
|||||||
| Результат счёта: рублёвый счёт с известным началом | API: ответ 200 со всеми полями; значения совпадают с эталоном (ниже) |
|
| Результат счёта: рублёвый счёт с известным началом | API: ответ 200 со всеми полями; значения совпадают с эталоном (ниже) |
|
||||||
| Результат счёта: счёт недоступен через подключение | API: чужой `accountId` → 404, в теле нет финансовых полей |
|
| Результат счёта: счёт недоступен через подключение | API: чужой `accountId` → 404, в теле нет финансовых полей |
|
||||||
| Внешние потоки: пополнения, вывод, дивиденды | Эталонный счёт A: `netInvested = 130000`, `profit = 10000` |
|
| Внешние потоки: пополнения, вывод, дивиденды | Эталонный счёт A: `netInvested = 130000`, `profit = 10000` |
|
||||||
| Внешние потоки: перевод между счетами в `all` | Счета B и C с парой вывод/пополнение: `all.netInvested` не меняется от перевода |
|
| Внешние потоки: кандидат на перевод между счетами в `all` | Счета B и C с равными суммами в пределах 5 минут исключаются с `UNCONFIRMED_TRANSFER`, а не объявляются внутренней парой |
|
||||||
| Внешние потоки: перевод на счёт вне результата | Счёт B исключён (USD), его пополнение от C → вывод у C в `all` |
|
| Внешние потоки: перевод на счёт вне результата | Счёт B исключён (USD), его пополнение от C → вывод у C в `all` |
|
||||||
| Доходность: период больше года | Счёт D: 100 000 → 121 000 за 730 дн. → `return.value = 0.1`, `basis = annual` |
|
| Доходность: период больше года | Счёт D: 100 000 → 121 000 за 730 дн. → `return.value = 0.1`, `basis = annual` |
|
||||||
| Доходность: меньше года | Счёт E, 200 дн.: `basis = period`; в UI нет слова «годовых» ни в одном значении |
|
| Доходность: меньше года | Счёт E, 200 дн.: `basis = period`; в UI нет слова «годовых» ни в одном значении |
|
||||||
| Доходность: нет решения | Счёт F (текущая стоимость 0 после пополнений, без выводов): `NO_SOLUTION`, `profit` доступен, `outperformance = null` |
|
| Доходность: нет решения | Счёт F (текущая стоимость 0 после пополнений, без выводов): `NO_SOLUTION`, `profit` доступен, `outperformance = null` |
|
||||||
|
| Доходность: несколько решений | Набор потоков с двумя корнями XIRR: `AMBIGUOUS_RETURN`, прибыль доступна, `outperformance = null` |
|
||||||
| Индекс: пополнение и рост индекса | Подменённая история (1 000 → 1 100) или ручной расчёт по реальным закрытиям: `benchmark.value = 110000`, `outperformance.money = 5000` |
|
| Индекс: пополнение и рост индекса | Подменённая история (1 000 → 1 100) или ручной расчёт по реальным закрытиям: `benchmark.value = 110000`, `outperformance.money = 5000` |
|
||||||
| Индекс: поток в выходной | Пополнение в субботу → используется закрытие пятницы (проверяется по `series` в день потока) |
|
| Индекс: поток в выходной | Пополнение в субботу → используется закрытие пятницы (проверяется по `series` в день потока) |
|
||||||
| Индекс: отставание | Счёт со стоимостью ниже теневой: отрицательные `percentagePoints` и `money`, в UI «Отставание от рынка» |
|
| Индекс: отставание | Счёт со стоимостью ниже теневой: отрицательные `percentagePoints` и `money`, в UI «Отставание от рынка» |
|
||||||
| Справочный индекс недоступен | IMOEX нет, MCFTRR есть: `reference.unavailableReason` задан, `benchmark` и `outperformance` есть, в UI нет пунктирной линии |
|
| Справочный индекс недоступен | IMOEX нет, MCFTRR есть: `reference.unavailableReason` задан, `benchmark` и `outperformance` есть, в UI нет пунктирной линии |
|
||||||
| Недоступно: неизвестная дата открытия | Счёт с `historyBoundary = unknown`: `data: null`, `HISTORY_START_UNKNOWN`; в UI «Доходность недоступна», нет нулей |
|
| Недоступно: неизвестная дата открытия | Счёт с `historyBoundary = unknown`: `data: null`, `HISTORY_START_UNKNOWN`; в UI «Доходность недоступна», нет нулей |
|
||||||
| Недоступно: USD в `all` | 3 счёта, один с USD: `completeness = incomplete`, `excludedAccounts` содержит счёт и `NON_RUB_ACTIVITY`; в UI предупреждение |
|
| Недоступно: USD в `all` | 3 счёта, один с USD: `completeness = incomplete`, `excludedAccounts` содержит счёт и `NON_RUB_ACTIVITY`; в UI предупреждение |
|
||||||
|
| Недоступно: даты оценок в `all` различаются | Последние оценки A и B в разные московские дни → `data: null`, `ACCOUNT_VALUATION_DATES_DIFFER`; стоимость не складывается |
|
||||||
| Недоступно: индекс недоступен | MOEX заблокирован и истории нет: `profit` есть, `benchmark.unavailableReason = INDEX_HISTORY_UNAVAILABLE`, `series = []` |
|
| Недоступно: индекс недоступен | MOEX заблокирован и истории нет: `profit` есть, `benchmark.unavailableReason = INDEX_HISTORY_UNAVAILABLE`, `series = []` |
|
||||||
| Недоступно: остальные причины | Перевод бумаг без пары → `UNVALUED_SECURITIES_TRANSFER`; позиция без цены → `PORTFOLIO_VALUATION_INCOMPLETE`; незавершённая синхронизация → `OPERATIONS_HISTORY_INCOMPLETE` |
|
| Недоступно: остальные причины | Перевод бумаг без пары → `UNVALUED_SECURITIES_TRANSFER`; позиция без цены → `PORTFOLIO_VALUATION_INCOMPLETE`; незавершённая синхронизация → `OPERATIONS_HISTORY_INCOMPLETE` |
|
||||||
| Вкладка: обгоняю, меньше года, неполный, недоступный, методика | UI по соответствующим данным; формулировки и тоны как в UI/UX Package; «Как считали» содержит все пункты спецификации |
|
| Вкладка: обгоняю, меньше года, неполный, недоступный, методика | UI по соответствующим данным; формулировки и тоны как в UI/UX Package; «Как считали» содержит все пункты спецификации |
|
||||||
@@ -130,7 +132,8 @@ Issue: WealthLedger/main#54
|
|||||||
|
|
||||||
## Gaps / Blockers
|
## Gaps / Blockers
|
||||||
|
|
||||||
- **Парность переводов** не подтверждена на реальных данных. Routing: System
|
- **Подтверждение переводов** отсутствует в текущем контракте T-Bank.
|
||||||
Analysis (уточнить правило) по итогам ручной проверки до закрытия change.
|
Routing: System Analysis при появлении нового признака связи; до этого
|
||||||
|
кандидаты исключаются с явной причиной, не выдавая сомнительный процент.
|
||||||
- **Sandbox T-Bank** может не воспроизвести все наборы. Routing: Test Data —
|
- **Sandbox T-Bank** может не воспроизвести все наборы. Routing: Test Data —
|
||||||
наполнение одноразовой SQLite-копии; не блокирует.
|
наполнение одноразовой SQLite-копии; не блокирует.
|
||||||
|
|||||||
+20
-4
@@ -4,7 +4,7 @@
|
|||||||
## ADDED Requirements
|
## ADDED Requirements
|
||||||
|
|
||||||
### Requirement: Результат инвестирования выбранного счёта
|
### Requirement: Результат инвестирования выбранного счёта
|
||||||
Система SHALL предоставлять аутентифицированному User результат инвестирования за всё время для открытого поддерживаемого Brokerage Account, доступного через текущий T-Bank connection User, и для режима «Все счета» (`all`). Период результата MUST начинаться с известной даты начала истории операций и заканчиваться датой последней полной оценки Portfolio. Даты MUST определяться календарными днями по Europe/Moscow. Результат MUST рассчитываться только в RUB без валютной конвертации.
|
Система SHALL предоставлять аутентифицированному User результат инвестирования за всё время для открытого поддерживаемого Brokerage Account, доступного через текущий T-Bank connection User, и для режима «Все счета» (`all`). История операций MUST быть полной с известной даты начала истории счёта; период результата MUST начинаться с даты первого внешнего потока и заканчиваться датой последней полной оценки Portfolio. Даты MUST определяться календарными днями по Europe/Moscow. Результат MUST рассчитываться только в RUB без валютной конвертации.
|
||||||
|
|
||||||
#### Scenario: Рублёвый счёт с известным началом истории
|
#### Scenario: Рублёвый счёт с известным началом истории
|
||||||
- **WHEN** User открывает результат рублёвого счёта с известным началом истории, полной историей операций и полной текущей оценкой Portfolio
|
- **WHEN** User открывает результат рублёвого счёта с известным началом истории, полной историей операций и полной текущей оценкой Portfolio
|
||||||
@@ -21,14 +21,18 @@
|
|||||||
- **WHEN** на счёт внесено 100 000 RUB и 50 000 RUB, выведено 20 000 RUB, получены дивиденды 3 000 RUB, а текущая стоимость Portfolio 140 000 RUB
|
- **WHEN** на счёт внесено 100 000 RUB и 50 000 RUB, выведено 20 000 RUB, получены дивиденды 3 000 RUB, а текущая стоимость Portfolio 140 000 RUB
|
||||||
- **THEN** чистые внешние вложения равны 130 000 RUB, прибыль равна 10 000 RUB, а дивиденды не учитываются как отдельный поток
|
- **THEN** чистые внешние вложения равны 130 000 RUB, прибыль равна 10 000 RUB, а дивиденды не учитываются как отдельный поток
|
||||||
|
|
||||||
#### Scenario: Перевод между счетами в режиме «Все счета»
|
#### Scenario: Подтверждённый перевод между счетами в режиме «Все счета»
|
||||||
- **WHEN** в режиме `all` 50 000 RUB переведены со счёта A на счёт B, и оба счёта входят в результат
|
- **WHEN** в режиме `all` источник однозначно подтверждает перевод 50 000 RUB со счёта A на счёт B, и оба счёта входят в результат
|
||||||
- **THEN** перевод не меняет чистые внешние вложения и доходность режима `all`
|
- **THEN** перевод не меняет чистые внешние вложения и доходность режима `all`
|
||||||
|
|
||||||
#### Scenario: Перевод на счёт вне результата
|
#### Scenario: Перевод на счёт вне результата
|
||||||
- **WHEN** в режиме `all` деньги переведены на счёт, не входящий в результат
|
- **WHEN** в режиме `all` деньги переведены на счёт, не входящий в результат
|
||||||
- **THEN** перевод учитывается как вывод в дату исполнения
|
- **THEN** перевод учитывается как вывод в дату исполнения
|
||||||
|
|
||||||
|
#### Scenario: Совпадение операций не доказывает перевод
|
||||||
|
- **WHEN** в режиме `all` вывод на одном счёте и пополнение другого совпадают по сумме и времени, но источник не подтверждает связь между ними
|
||||||
|
- **THEN** система не объявляет их внутренним переводом, исключает затронутые счета из рассчитанной части результата и показывает причину неполноты; если не осталось счетов для расчёта, результат недоступен
|
||||||
|
|
||||||
### Requirement: Доходность с учётом сумм и дат потоков
|
### Requirement: Доходность с учётом сумм и дат потоков
|
||||||
Доходность MUST быть годовой ставкой r, при которой сумма всех внешних потоков, дисконтированных по фактическому числу дней от начала периода по формуле `(1 + r)^(дни / 365)`, и текущей стоимости равна нулю (XIRR). Пополнения MUST входить в расчёт как вложение User, выводы и текущая стоимость — как возврат. Если продолжительность периода меньше 365 дней, система MUST возвращать и показывать доходность за период `(1 + r)^(дни / 365) − 1` с явным числом дней и MUST NOT показывать годовое значение. Если единственное решение не найдено, доходность MUST быть недоступной с причиной «расчёт не сходится», а не нулём.
|
Доходность MUST быть годовой ставкой r, при которой сумма всех внешних потоков, дисконтированных по фактическому числу дней от начала периода по формуле `(1 + r)^(дни / 365)`, и текущей стоимости равна нулю (XIRR). Пополнения MUST входить в расчёт как вложение User, выводы и текущая стоимость — как возврат. Если продолжительность периода меньше 365 дней, система MUST возвращать и показывать доходность за период `(1 + r)^(дни / 365) − 1` с явным числом дней и MUST NOT показывать годовое значение. Если единственное решение не найдено, доходность MUST быть недоступной с причиной «расчёт не сходится», а не нулём.
|
||||||
|
|
||||||
@@ -40,10 +44,18 @@
|
|||||||
- **WHEN** период результата составляет 200 дней
|
- **WHEN** период результата составляет 200 дней
|
||||||
- **THEN** доходность показывается как процент за 200 дней, и ни одно значение результата не подписано как «годовых»
|
- **THEN** доходность показывается как процент за 200 дней, и ни одно значение результата не подписано как «годовых»
|
||||||
|
|
||||||
|
#### Scenario: Первое пополнение позже открытия счёта
|
||||||
|
- **WHEN** счёт открыт 300 дней назад, 100 000 RUB внесены 100 дней назад, а текущая стоимость 110 000 RUB
|
||||||
|
- **THEN** период равен 100 дням, а доходность показывается как +10,0 % за 100 дней
|
||||||
|
|
||||||
#### Scenario: Нет решения
|
#### Scenario: Нет решения
|
||||||
- **WHEN** набор потоков и текущая стоимость не дают ни одной ставки, обнуляющей сумму
|
- **WHEN** набор потоков и текущая стоимость не дают ни одной ставки, обнуляющей сумму
|
||||||
- **THEN** доходность и опережение недоступны с причиной «расчёт не сходится», а прибыль остаётся доступной
|
- **THEN** доходность и опережение недоступны с причиной «расчёт не сходится», а прибыль остаётся доступной
|
||||||
|
|
||||||
|
#### Scenario: Несколько ставок
|
||||||
|
- **WHEN** набор потоков допускает более одной ставки доходности
|
||||||
|
- **THEN** доходность и опережение недоступны с причиной «расчёт неоднозначен», а прибыль остаётся доступной
|
||||||
|
|
||||||
### Requirement: Те же деньги в индексе Мосбиржи
|
### Requirement: Те же деньги в индексе Мосбиржи
|
||||||
Система SHALL рассчитывать альтернативный результат: каждый внешний поток в ту же дату применяется к индексу Мосбиржи с учётом дивидендов после налогов (MCFTRR) по значению закрытия этого дня. Если в дату потока не было торгов, MUST использоваться последнее предшествующее закрытие. Стоимость альтернативы MUST равняться остатку условных паёв индекса, умноженному на последнее закрытие не позже конца периода. Доходность альтернативы MUST считаться той же методикой и за тот же период, что и доходность User. Опережение MUST равняться разнице доходностей User и альтернативы в процентных пунктах и разнице текущей стоимости и стоимости альтернативы в RUB. Отрицательное опережение MUST называться отставанием.
|
Система SHALL рассчитывать альтернативный результат: каждый внешний поток в ту же дату применяется к индексу Мосбиржи с учётом дивидендов после налогов (MCFTRR) по значению закрытия этого дня. Если в дату потока не было торгов, MUST использоваться последнее предшествующее закрытие. Стоимость альтернативы MUST равняться остатку условных паёв индекса, умноженному на последнее закрытие не позже конца периода. Доходность альтернативы MUST считаться той же методикой и за тот же период, что и доходность User. Опережение MUST равняться разнице доходностей User и альтернативы в процентных пунктах и разнице текущей стоимости и стоимости альтернативы в RUB. Отрицательное опережение MUST называться отставанием.
|
||||||
|
|
||||||
@@ -70,7 +82,7 @@
|
|||||||
Система MUST NOT возвращать числовой результат, выдаваемый за доходность, если его нельзя рассчитать честно. Для одного счёта результат MUST быть недоступным с машиночитаемой причиной, если:
|
Система MUST NOT возвращать числовой результат, выдаваемый за доходность, если его нельзя рассчитать честно. Для одного счёта результат MUST быть недоступным с машиночитаемой причиной, если:
|
||||||
- начало истории операций неизвестно;
|
- начало истории операций неизвестно;
|
||||||
- есть поток или позиция не в RUB;
|
- есть поток или позиция не в RUB;
|
||||||
- есть ввод или вывод ценных бумаг без денежной оценки;
|
- есть ввод или вывод ценных бумаг без денежной оценки, а в режиме `all` — неподтверждённый кандидат на перевод между своими счетами;
|
||||||
- текущая оценка Portfolio неполная;
|
- текущая оценка Portfolio неполная;
|
||||||
- история операций не получена полностью.
|
- история операций не получена полностью.
|
||||||
|
|
||||||
@@ -84,6 +96,10 @@
|
|||||||
- **WHEN** в режиме `all` один из трёх счетов содержит пополнение в USD
|
- **WHEN** в режиме `all` один из трёх счетов содержит пополнение в USD
|
||||||
- **THEN** результат рассчитывается по двум оставшимся счетам и помечается как неполный с указанием исключённого счёта и причины «операции не в рублях»
|
- **THEN** результат рассчитывается по двум оставшимся счетам и помечается как неполный с указанием исключённого счёта и причины «операции не в рублях»
|
||||||
|
|
||||||
|
#### Scenario: Счета оценены в разные московские дни
|
||||||
|
- **WHEN** в режиме `all` последние полные оценки включённых счетов относятся к разным московским календарным дням
|
||||||
|
- **THEN** сводный результат недоступен с причиной «оценки счетов на разные даты»; последние стоимости не складываются как одновременные
|
||||||
|
|
||||||
#### Scenario: Индекс недоступен
|
#### Scenario: Индекс недоступен
|
||||||
- **WHEN** история индекса с дивидендами не получена за период результата
|
- **WHEN** история индекса с дивидендами не получена за период результата
|
||||||
- **THEN** прибыль показывается, а результат тех же денег в индексе и опережение недоступны с причиной «история индекса недоступна»
|
- **THEN** прибыль показывается, а результат тех же денег в индексе и опережение недоступны с причиной «история индекса недоступна»
|
||||||
|
|||||||
+37
-20
@@ -58,7 +58,7 @@ Issue: WealthLedger/main#54
|
|||||||
сбой индекса превращается в `INDEX_HISTORY_UNAVAILABLE` внутри
|
сбой индекса превращается в `INDEX_HISTORY_UNAVAILABLE` внутри
|
||||||
`benchmark`/`reference`.
|
`benchmark`/`reference`.
|
||||||
- Числа: деньги — `MoneyDto` (RUB, десятичная строка, округление до копеек
|
- Числа: деньги — `MoneyDto` (RUB, десятичная строка, округление до копеек
|
||||||
half-even); доли (`Rate`) — десятичная строка до 6 знаков, `0.151` = 15,1 %;
|
half away from zero, как общий decimal helper); доли (`Rate`) — десятичная строка до 6 знаков, `0.151` = 15,1 %;
|
||||||
точки `series` — десятичные RUB до копеек.
|
точки `series` — десятичные RUB до копеек.
|
||||||
- Идемпотентность: GET без побочных эффектов, кроме догрузки истории индекса.
|
- Идемпотентность: GET без побочных эффектов, кроме догрузки истории индекса.
|
||||||
Её повтор безопасен (upsert по индексу и торговому дню).
|
Её повтор безопасен (upsert по индексу и торговому дню).
|
||||||
@@ -93,19 +93,22 @@ Issue: WealthLedger/main#54
|
|||||||
|
|
||||||
### Переводы между счетами в `all`
|
### Переводы между счетами в `all`
|
||||||
|
|
||||||
T-Bank не передаёт в операции счёт-контрагента. Поэтому перевод
|
T-Bank не передаёт в операции счёт-контрагента. Совпадение операций —
|
||||||
определяется парой операций.
|
лишь кандидат на перевод, а не подтверждение. Не объявлять кандидата
|
||||||
|
внутренним потоком без независимого признака связи.
|
||||||
|
|
||||||
- **Деньги.** Вывод на счёте A и пополнение на счёте B (оба входят в
|
- **Деньги.** Вывод на счёте A и пополнение на счёте B равной суммы RUB
|
||||||
результат) с равной суммой RUB и `occurredAt` в пределах 5 минут.
|
в пределах 5 минут — только кандидат. Пока нет подтверждения связи,
|
||||||
Пара исключается из потоков. Без пары операция остаётся внешним потоком.
|
оба счёта исключаются из расчётной части `all` с причиной
|
||||||
|
`UNCONFIRMED_TRANSFER`. Без кандидата операция остаётся внешним потоком.
|
||||||
- **Бумаги.** `OUTPUT_SECURITIES` / `TRANS_*` на счёте A и
|
- **Бумаги.** `OUTPUT_SECURITIES` / `TRANS_*` на счёте A и
|
||||||
`INPUT_SECURITIES` / `TRANS_*` на счёте B с тем же инструментом и
|
`INPUT_SECURITIES` / `TRANS_*` на счёте B с тем же инструментом и
|
||||||
количеством в тот же день по Москве. Пара внутренняя. Без пары оба счёта
|
количеством в тот же день — только кандидат; оба счёта исключаются.
|
||||||
получают `UNVALUED_SECURITIES_TRANSFER` и исключаются.
|
Для одиночного ввода или вывода применяется `UNVALUED_SECURITIES_TRANSFER`.
|
||||||
|
|
||||||
Правило парности нужно подтвердить на реальных данных при первом запуске
|
В текущем контракте T-Bank нет признака, подтверждающего пару. Полностью
|
||||||
(см. Assumptions).
|
корректное включение счетов с переводами в `all` отложено до появления
|
||||||
|
подтверждённой связи; не вычислять процент на предположении.
|
||||||
|
|
||||||
### Причины недоступности
|
### Причины недоступности
|
||||||
|
|
||||||
@@ -118,6 +121,8 @@ T-Bank не передаёт в операции счёт-контрагента
|
|||||||
нет вычислимой стоимости (то же правило, что у `portfolio-value-history`).
|
нет вычислимой стоимости (то же правило, что у `portfolio-value-history`).
|
||||||
4. `NON_RUB_ACTIVITY`.
|
4. `NON_RUB_ACTIVITY`.
|
||||||
5. `UNVALUED_SECURITIES_TRANSFER`.
|
5. `UNVALUED_SECURITIES_TRANSFER`.
|
||||||
|
6. `UNCONFIRMED_TRANSFER` (только `all`, когда есть кандидат на внутренний
|
||||||
|
перевод без доказанной связи).
|
||||||
|
|
||||||
Как причины попадают в ответ:
|
Как причины попадают в ответ:
|
||||||
|
|
||||||
@@ -126,15 +131,22 @@ T-Bank не передаёт в операции счёт-контрагента
|
|||||||
- В `all` исключённые счета перечисляются в `excludedAccounts`, а
|
- В `all` исключённые счета перечисляются в `excludedAccounts`, а
|
||||||
`completeness = incomplete`. Если не осталось ни одного счёта, ответ
|
`completeness = incomplete`. Если не осталось ни одного счёта, ответ
|
||||||
`data: null`, а `meta.unavailable` содержит все счета.
|
`data: null`, а `meta.unavailable` содержит все счета.
|
||||||
- `NO_SOLUTION` бывает только в `return` (пользователя или индекса) и не
|
- `NO_SOLUTION` и `AMBIGUOUS_RETURN` бывают только в `return`
|
||||||
|
(пользователя или индекса) и не
|
||||||
исключает счёт.
|
исключает счёт.
|
||||||
|
- Если даты последних полных оценок оставшихся счетов `all` в Москве
|
||||||
|
различаются, сводка целиком `data: null` с причиной
|
||||||
|
`ACCOUNT_VALUATION_DATES_DIFFER` в `meta.unavailable` для каждого
|
||||||
|
затронутого счёта: суммы разных дат не складываются.
|
||||||
- `INDEX_HISTORY_UNAVAILABLE` бывает только в `benchmark` / `reference`.
|
- `INDEX_HISTORY_UNAVAILABLE` бывает только в `benchmark` / `reference`.
|
||||||
|
|
||||||
### Расчёт
|
### Расчёт
|
||||||
|
|
||||||
- **Период.** `from` — `historyFrom` (в `all` — минимальный по счетам
|
- **Период.** `from` — дата первого внешнего потока по Москве (решение
|
||||||
результата). `to` — дата по Москве самого раннего `fetchedAt` среди
|
Change Owner: не дата открытия счёта). Полнота истории по-прежнему
|
||||||
счетов результата. `days = to − from` в календарных днях.
|
проверяется от `historyFrom`. `to` — дата по Москве `fetchedAt`; для `all` она общая
|
||||||
|
только когда все включённые счета оценены в один московский день.
|
||||||
|
`days = to − from` в календарных днях.
|
||||||
`basis = annual` при `days ≥ 365`, иначе `period`.
|
`basis = annual` при `days ≥ 365`, иначе `period`.
|
||||||
- **Агрегаты.** `netInvested = Σ потоков`,
|
- **Агрегаты.** `netInvested = Σ потоков`,
|
||||||
`currentValue = Σ totalsByCurrency[RUB]`,
|
`currentValue = Σ totalsByCurrency[RUB]`,
|
||||||
@@ -143,8 +155,10 @@ T-Bank не передаёт в операции счёт-контрагента
|
|||||||
- **XIRR.** Найти r из `Σ CFᵢ / (1 + r)^((dᵢ − d₀) / 365) = 0`, где
|
- **XIRR.** Найти r из `Σ CFᵢ / (1 + r)^((dᵢ − d₀) / 365) = 0`, где
|
||||||
пополнение входит как `−`, вывод — как `+`, а `currentValue` — как `+` в
|
пополнение входит как `−`, вывод — как `+`, а `currentValue` — как `+` в
|
||||||
дату `to`. d₀ — дата первого потока. Корень ищется на `(−0.9999, 100]`.
|
дату `to`. d₀ — дата первого потока. Корень ищется на `(−0.9999, 100]`.
|
||||||
- Нет смены знака или корень не найден с точностью |NPV| ≤ 0,01 ₽ →
|
- Нет корня или корень не найден с точностью |NPV| ≤ 0,01 ₽ →
|
||||||
`NO_SOLUTION`.
|
`NO_SOLUTION`. Если решений более одного — `AMBIGUOUS_RETURN`,
|
||||||
|
произвольный корень не выбирается. Смена знака на концах диапазона
|
||||||
|
не доказывает единственность решения.
|
||||||
- Нет потоков → `NO_SOLUTION`.
|
- Нет потоков → `NO_SOLUTION`.
|
||||||
- Для `basis = period` значение равно `(1 + r)^(days / 365) − 1`.
|
- Для `basis = period` значение равно `(1 + r)^(days / 365) − 1`.
|
||||||
- **Теневой портфель.** Для каждого потока берётся закрытие индекса на дату
|
- **Теневой портфель.** Для каждого потока берётся закрытие индекса на дату
|
||||||
@@ -168,12 +182,15 @@ T-Bank не передаёт в операции счёт-контрагента
|
|||||||
|
|
||||||
- Хранится (индекс, торговый день по Москве) → точное CLOSE, upsert.
|
- Хранится (индекс, торговый день по Москве) → точное CLOSE, upsert.
|
||||||
Нерабочие дни не хранятся.
|
Нерабочие дни не хранятся.
|
||||||
- **Свежесть.** Догрузка нужна, если последняя сохранённая дата меньше
|
- **Свежесть.** Расширение диапазона назад загружается сразу; хвост до
|
||||||
`to`, а последняя попытка была больше часа назад. На каждый индекс
|
`to`, где закрытия ещё нет, перепроверяется не чаще раза в час после
|
||||||
|
успешной проверки; повтор после неудачной попытки — не чаще раза в час.
|
||||||
|
На каждый индекс
|
||||||
одновременно идёт не больше одной догрузки, повторные чтения ждут её
|
одновременно идёт не больше одной догрузки, повторные чтения ждут её
|
||||||
результат. Первая загрузка начинается с `from − 10 дней`, чтобы было
|
результат. Первая загрузка начинается с `from − 10 дней`, чтобы было
|
||||||
закрытие для потока в выходной.
|
закрытие для потока в выходной.
|
||||||
- **Достаточное покрытие.** Есть закрытие `≤` даты первого потока и
|
- **Достаточное покрытие.** Есть закрытие `≤` даты первого потока,
|
||||||
|
подтверждённый диапазон доходит до даты последнего потока и
|
||||||
последнее закрытие `≥ to − 10` календарных дней. 10 дней покрывают
|
последнее закрытие `≥ to − 10` календарных дней. 10 дней покрывают
|
||||||
новогодние каникулы биржи. Иначе `INDEX_HISTORY_UNAVAILABLE`.
|
новогодние каникулы биржи. Иначе `INDEX_HISTORY_UNAVAILABLE`.
|
||||||
- **Лицензия.** ISS бесплатно отдаёт данные для ознакомления. Приложение
|
- **Лицензия.** ISS бесплатно отдаёт данные для ознакомления. Приложение
|
||||||
@@ -186,7 +203,7 @@ T-Bank не передаёт в операции счёт-контрагента
|
|||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| Нет сессии / нет подключения / счёт не найден или недоступен | Доступ | 401 / 503 `TBANK_NOT_CONNECTED` / 404 | Существующие сценарии счёта |
|
| Нет сессии / нет подключения / счёт не найден или недоступен | Доступ | 401 / 503 `TBANK_NOT_CONNECTED` / 404 | Существующие сценарии счёта |
|
||||||
| Причина недоступности счёта | Бизнес | 200, `data: null` или `excludedAccounts` | Исчезает после синхронизации или изменения данных |
|
| Причина недоступности счёта | Бизнес | 200, `data: null` или `excludedAccounts` | Исчезает после синхронизации или изменения данных |
|
||||||
| XIRR не сходится | Бизнес | 200, `return.unavailableReason = NO_SOLUTION` | Нет |
|
| XIRR не сходится или неоднозначен | Бизнес | 200, `return.unavailableReason = NO_SOLUTION` либо `AMBIGUOUS_RETURN` | Нет |
|
||||||
| MOEX недоступен, сохранённой истории хватает | Технический | 200, полный результат | Догрузка при следующем чтении (не чаще раза в час) |
|
| MOEX недоступен, сохранённой истории хватает | Технический | 200, полный результат | Догрузка при следующем чтении (не чаще раза в час) |
|
||||||
| MOEX недоступен, истории не хватает | Технический | 200, `benchmark.unavailableReason = INDEX_HISTORY_UNAVAILABLE`, `series: []` | То же |
|
| MOEX недоступен, истории не хватает | Технический | 200, `benchmark.unavailableReason = INDEX_HISTORY_UNAVAILABLE`, `series: []` | То же |
|
||||||
| Сбой хранилища или непредвиденная ошибка | Технический | 500 Problem Details | Повтор из UI; другие вкладки не затронуты |
|
| Сбой хранилища или непредвиденная ошибка | Технический | 500 Problem Details | Повтор из UI; другие вкладки не затронуты |
|
||||||
|
|||||||
+4
-1
@@ -50,7 +50,7 @@ components:
|
|||||||
description: Точное десятичное значение без экспоненты и незначащих нулей
|
description: Точное десятичное значение без экспоненты и незначащих нулей
|
||||||
Rate:
|
Rate:
|
||||||
allOf: [{ $ref: '#/components/schemas/Decimal' }]
|
allOf: [{ $ref: '#/components/schemas/Decimal' }]
|
||||||
description: Доля, не проценты. 0.151 = 15,1 %. Округление до 6 знаков, half-even.
|
description: Доля, не проценты. 0.151 = 15,1 %. Округление до 6 знаков, half away from zero.
|
||||||
Money:
|
Money:
|
||||||
$ref: 'openapi.json#/components/schemas/MoneyDto'
|
$ref: 'openapi.json#/components/schemas/MoneyDto'
|
||||||
UnavailableReason:
|
UnavailableReason:
|
||||||
@@ -60,8 +60,11 @@ components:
|
|||||||
- OPERATIONS_HISTORY_INCOMPLETE
|
- OPERATIONS_HISTORY_INCOMPLETE
|
||||||
- NON_RUB_ACTIVITY
|
- NON_RUB_ACTIVITY
|
||||||
- UNVALUED_SECURITIES_TRANSFER
|
- UNVALUED_SECURITIES_TRANSFER
|
||||||
|
- UNCONFIRMED_TRANSFER
|
||||||
|
- ACCOUNT_VALUATION_DATES_DIFFER
|
||||||
- PORTFOLIO_VALUATION_INCOMPLETE
|
- PORTFOLIO_VALUATION_INCOMPLETE
|
||||||
- NO_SOLUTION
|
- NO_SOLUTION
|
||||||
|
- AMBIGUOUS_RETURN
|
||||||
- INDEX_HISTORY_UNAVAILABLE
|
- INDEX_HISTORY_UNAVAILABLE
|
||||||
description: |
|
description: |
|
||||||
Новые значения MAY добавляться; клиент MUST показывать неизвестное
|
Новые значения MAY добавляться; клиент MUST показывать неизвестное
|
||||||
|
|||||||
+1
-1
@@ -13,7 +13,7 @@ sequenceDiagram
|
|||||||
UI-->>User: Существующие состояния ошибки счёта
|
UI-->>User: Существующие состояния ошибки счёта
|
||||||
else Доступ подтверждён
|
else Доступ подтверждён
|
||||||
API->>Store: Граница истории, покрытие операций, последняя полная оценка Portfolio
|
API->>Store: Граница истории, покрытие операций, последняя полная оценка Portfolio
|
||||||
Note over API: По каждому счёту: причины недоступности<br/>(HISTORY_START_UNKNOWN, OPERATIONS_HISTORY_INCOMPLETE,<br/>NON_RUB_ACTIVITY, UNVALUED_SECURITIES_TRANSFER,<br/>PORTFOLIO_VALUATION_INCOMPLETE)
|
Note over API: По каждому счёту: причины недоступности<br/>(HISTORY_START_UNKNOWN, OPERATIONS_HISTORY_INCOMPLETE,<br/>NON_RUB_ACTIVITY, UNVALUED_SECURITIES_TRANSFER,<br/>PORTFOLIO_VALUATION_INCOMPLETE, UNCONFIRMED_TRANSFER)<br/>all: ACCOUNT_VALUATION_DATES_DIFFER<br/>XIRR: NO_SOLUTION / AMBIGUOUS_RETURN
|
||||||
alt Не осталось счетов для расчёта
|
alt Не осталось счетов для расчёта
|
||||||
API-->>UI: 200 {data: null, meta.unavailable: [...]}
|
API-->>UI: 200 {data: null, meta.unavailable: [...]}
|
||||||
UI-->>User: «Доходность недоступна» + причина
|
UI-->>User: «Доходность недоступна» + причина
|
||||||
|
|||||||
@@ -5,17 +5,17 @@ Issue: WealthLedger/main#54. Ветки в каждом repo:
|
|||||||
|
|
||||||
## 1. Backend: история индексов
|
## 1. Backend: история индексов
|
||||||
|
|
||||||
- [ ] 1.1 [backend] Хранилище дневных закрытий индексов и состояния догрузки (аддитивная миграция)
|
- [x] 1.1 [backend] Хранилище дневных закрытий индексов и состояния догрузки (аддитивная миграция)
|
||||||
- **Covers:** `instruments/market-index-history` — `Дневная история индексов Мосбиржи`
|
- **Covers:** `instruments/market-index-history` — `Дневная история индексов Мосбиржи`
|
||||||
- **Source:** design «Data / Storage Changes»
|
- **Source:** design «Data / Storage Changes»
|
||||||
- **Verification:** `npx prisma migrate dev` на одноразовой копии БД; `npm run verify`
|
- **Verification:** `npx prisma migrate dev` на одноразовой копии БД; `npm run verify`
|
||||||
- **Depends on:** none
|
- **Depends on:** none
|
||||||
- [ ] 1.2 [backend] `MoexClient.indexHistory(secid, from, till)`: пагинация `history.cursor`, строгий разбор, сбой без частичных строк
|
- [x] 1.2 [backend] `MoexClient.indexHistory(secid, from, till)`: пагинация `history.cursor`, строгий разбор, сбой без частичных строк
|
||||||
- **Covers:** `instruments/market-index-history` — `Дневная история индексов Мосбиржи`
|
- **Covers:** `instruments/market-index-history` — `Дневная история индексов Мосбиржи`
|
||||||
- **Source:** system-analysis «Contracts» (MOEX ISS), [index-refresh.mmd](system-analysis/sequences/index-refresh.mmd); design backend 5
|
- **Source:** system-analysis «Contracts» (MOEX ISS), [index-refresh.mmd](system-analysis/sequences/index-refresh.mmd); design backend 5
|
||||||
- **Verification:** `moex.client.spec.ts`: несколько страниц, невалидная страница, таймаут, не-2xx
|
- **Verification:** `moex.client.spec.ts`: несколько страниц, невалидная страница, таймаут, не-2xx
|
||||||
- **Depends on:** none
|
- **Depends on:** none
|
||||||
- [ ] 1.3 [backend] `MarketIndexHistoryService`: покрытие периода, догрузка не чаще раза в час, single-flight, upsert, лог `integration.moex.index_history.*`, сбой → недостаточно покрытия
|
- [x] 1.3 [backend] `MarketIndexHistoryService`: покрытие периода, догрузка не чаще раза в час, single-flight, upsert, лог `integration.moex.index_history.*`, сбой → недостаточно покрытия
|
||||||
- **Covers:** `instruments/market-index-history` — `Дневная история индексов Мосбиржи`, `Свежесть и недоступность истории индексов`
|
- **Covers:** `instruments/market-index-history` — `Дневная история индексов Мосбиржи`, `Свежесть и недоступность истории индексов`
|
||||||
- **Source:** system-analysis «Индексы MOEX», «Error and Recovery Semantics»; design backend 5, «Cross-cutting Implementation»
|
- **Source:** system-analysis «Индексы MOEX», «Error and Recovery Semantics»; design backend 5, «Cross-cutting Implementation»
|
||||||
- **Verification:** unit-тесты сервиса (покрытие ±10 дн., час, параллельные вызовы, сбой с историей и без); `npm run verify`
|
- **Verification:** unit-тесты сервиса (покрытие ±10 дн., час, параллельные вызовы, сбой с историей и без); `npm run verify`
|
||||||
@@ -23,27 +23,27 @@ Issue: WealthLedger/main#54. Ветки в каждом repo:
|
|||||||
|
|
||||||
## 2. Backend: расчёт результата
|
## 2. Backend: расчёт результата
|
||||||
|
|
||||||
- [ ] 2.1 [backend] Вынести `isCompleteValuation(snapshot)` в portfolio domain и использовать его при записи истории оценки
|
- [x] 2.1 [backend] Вынести `isCompleteValuation(snapshot)` в portfolio domain и использовать его при записи истории оценки
|
||||||
- **Covers:** `portfolio/investment-performance` — `Недоступный и неполный результат`
|
- **Covers:** `portfolio/investment-performance` — `Недоступный и неполный результат`
|
||||||
- **Source:** design backend 2; qa-analysis «Regression Impact»
|
- **Source:** design backend 2; qa-analysis «Regression Impact»
|
||||||
- **Verification:** существующие тесты `portfolio-value-history` и новые unit-тесты функции проходят; `npm run verify`
|
- **Verification:** существующие тесты `portfolio-value-history` и новые unit-тесты функции проходят; `npm run verify`
|
||||||
- **Depends on:** none
|
- **Depends on:** none
|
||||||
- [ ] 2.2 [backend] Domain: маппинг типов операций → роли, внешние потоки по дате Europe/Moscow, отсечка по `fetchedAt`, парность переводов в `all`, причины недоступности в порядке приоритета
|
- [x] 2.2 [backend] Domain: маппинг типов операций → роли, внешние потоки по дате Europe/Moscow, отсечка по `fetchedAt`, кандидаты на переводы в `all` без автоматического подтверждения, причины недоступности в порядке приоритета
|
||||||
- **Covers:** `portfolio/investment-performance` — `Внешние денежные потоки`, `Недоступный и неполный результат`
|
- **Covers:** `portfolio/investment-performance` — `Внешние денежные потоки`, `Недоступный и неполный результат`
|
||||||
- **Source:** system-analysis «Маппинг операций T-Bank», «Переводы между счетами в all», «Причины недоступности»; design backend 4, 7
|
- **Source:** system-analysis «Маппинг операций T-Bank», «Переводы между счетами в all», «Причины недоступности»; design backend 4, 7
|
||||||
- **Verification:** unit-тесты: счёт A (130 000 / 10 000), перевод внутри `all`, перевод на исключённый счёт, бумаги без пары, USD, порядок причин
|
- **Verification:** unit-тесты: счёт A (130 000 / 10 000), `UNCONFIRMED_TRANSFER` при совпадении операций, перевод на исключённый счёт, бумаги без пары, USD, порядок причин
|
||||||
- **Depends on:** none
|
- **Depends on:** none
|
||||||
- [ ] 2.3 [backend] Domain: XIRR (Ньютон + бисекция, проверка знака), basis annual/period, теневой портфель MCFTRR/IMOEX, опережение, series
|
- [x] 2.3 [backend] Domain: XIRR (Ньютон + бисекция, проверка знака), basis annual/period, теневой портфель MCFTRR/IMOEX, опережение, series
|
||||||
- **Covers:** `portfolio/investment-performance` — `Доходность с учётом сумм и дат потоков`, `Те же деньги в индексе Мосбиржи`, `Справочный индекс без дивидендов`, `График результата`
|
- **Covers:** `portfolio/investment-performance` — `Доходность с учётом сумм и дат потоков`, `Те же деньги в индексе Мосбиржи`, `Справочный индекс без дивидендов`, `График результата`
|
||||||
- **Source:** system-analysis «Расчёт»; design backend 3
|
- **Source:** system-analysis «Расчёт»; design backend 3
|
||||||
- **Verification:** unit-тесты с эталонными константами (LibreOffice/scipy, допуск 1e-6): 10 % за 730 дн., 110 000 ₽ по индексу, поток в выходной, `NO_SOLUTION`, period basis, отставание
|
- **Verification:** unit-тесты с эталонными константами (LibreOffice/scipy, допуск 1e-6): 10 % за 730 дн., 110 000 ₽ по индексу, поток в выходной, `NO_SOLUTION`, `AMBIGUOUS_RETURN`, period basis, отставание
|
||||||
- **Depends on:** none
|
- **Depends on:** none
|
||||||
- [ ] 2.4 [backend] Модуль `performance`: маршруты `GET /brokerage-accounts/{accountId|all}/performance`, доступ, сборка ответа, DTO, `UnavailableReason`, лог `performance.calculated`
|
- [x] 2.4 [backend] Модуль `performance`: маршруты `GET /brokerage-accounts/{accountId|all}/performance`, доступ, сборка ответа, DTO, `UnavailableReason`, лог `performance.calculated`
|
||||||
- **Covers:** `portfolio/investment-performance` — `Результат инвестирования выбранного счёта`, `Недоступный и неполный результат`; `instruments/market-index-history` — `Свежесть и недоступность истории индексов`
|
- **Covers:** `portfolio/investment-performance` — `Результат инвестирования выбранного счёта`, `Недоступный и неполный результат`; `instruments/market-index-history` — `Свежесть и недоступность истории индексов`
|
||||||
- **Source:** contract [investment-performance.openapi.yaml](system-analysis/contracts/investment-performance.openapi.yaml), [performance-read.mmd](system-analysis/sequences/performance-read.mmd); design backend 1, 6
|
- **Source:** contract [investment-performance.openapi.yaml](system-analysis/contracts/investment-performance.openapi.yaml), [performance-read.mmd](system-analysis/sequences/performance-read.mmd); design backend 1, 6
|
||||||
- **Verification:** e2e на SQLite (`npm run test:sqlite`): 200 ready, `data: null`, `all` incomplete, 401/404/503, MOEX недоступен с историей и без; `npm run verify`
|
- **Verification:** e2e на SQLite (`npm run test:sqlite`): 200 ready, `data: null`, `all` incomplete, 401/404/503, MOEX недоступен с историей и без; `npm run verify`
|
||||||
- **Depends on:** 1.3, 2.1, 2.2, 2.3
|
- **Depends on:** 1.3, 2.1, 2.2, 2.3
|
||||||
- [ ] 2.5 [backend] Обновить generated OpenAPI и сверить его с целевым контрактом
|
- [x] 2.5 [backend] Обновить generated OpenAPI и сверить его с целевым контрактом
|
||||||
- **Covers:** `portfolio/investment-performance` — `Результат инвестирования выбранного счёта`
|
- **Covers:** `portfolio/investment-performance` — `Результат инвестирования выбранного счёта`
|
||||||
- **Source:** design «Contract Implementation»
|
- **Source:** design «Contract Implementation»
|
||||||
- **Verification:** `npm run openapi:generate`; `npm run openapi:check`; построчная сверка маршрутов, required, nullable, enum и форматов с `investment-performance.openapi.yaml`, результат — в описании PR
|
- **Verification:** `npm run openapi:generate`; `npm run openapi:check`; построчная сверка маршрутов, required, nullable, enum и форматов с `investment-performance.openapi.yaml`, результат — в описании PR
|
||||||
@@ -51,22 +51,22 @@ Issue: WealthLedger/main#54. Ветки в каждом repo:
|
|||||||
|
|
||||||
## 3. Frontend
|
## 3. Frontend
|
||||||
|
|
||||||
- [ ] 3.1 [frontend] Обновить OpenAPI snapshot и типы, entity `performance` с `usePerformance(accountId)`
|
- [x] 3.1 [frontend] Обновить OpenAPI snapshot и типы, entity `performance` с `usePerformance(accountId)`
|
||||||
- **Covers:** `portfolio/investment-performance` — `Результат инвестирования выбранного счёта`
|
- **Covers:** `portfolio/investment-performance` — `Результат инвестирования выбранного счёта`
|
||||||
- **Source:** design frontend 1, «Contract Implementation»
|
- **Source:** design frontend 1, «Contract Implementation»
|
||||||
- **Verification:** `npm run openapi:generate`; `npm run openapi:check`; `npm run typecheck`
|
- **Verification:** `npm run openapi:generate`; `npm run openapi:check`; `npm run typecheck`
|
||||||
- **Depends on:** 2.5
|
- **Depends on:** 2.5
|
||||||
- [ ] 3.2 [frontend] Форматтеры `formatSignedPercent`, `formatPercentagePoints` (знак U+2212, 1 знак)
|
- [x] 3.2 [frontend] Форматтеры `formatSignedPercent`, `formatPercentagePoints` (знак U+2212, 1 знак)
|
||||||
- **Covers:** `portfolio/investment-performance` — `Вкладка «Результат» на странице счёта`
|
- **Covers:** `portfolio/investment-performance` — `Вкладка «Результат» на странице счёта`
|
||||||
- **Source:** design frontend 4
|
- **Source:** design frontend 4
|
||||||
- **Verification:** unit-тесты: положительное, отрицательное, ноль, округление
|
- **Verification:** unit-тесты: положительное, отрицательное, ноль, округление
|
||||||
- **Depends on:** none
|
- **Depends on:** none
|
||||||
- [ ] 3.3 [frontend] Вкладка «Результат»: container, VM, view, KPI, alerts, недоступно, skeleton, ошибка секции, «Как считали», hint, строки `portfolio.performance.*`
|
- [x] 3.3 [frontend] Вкладка «Результат»: container, VM, view, KPI, alerts, недоступно, skeleton, ошибка секции, «Как считали», hint, строки `portfolio.performance.*`
|
||||||
- **Covers:** `portfolio/investment-performance` — `Вкладка «Результат» на странице счёта`, `Недоступный и неполный результат`
|
- **Covers:** `portfolio/investment-performance` — `Вкладка «Результат» на странице счёта`, `Недоступный и неполный результат`
|
||||||
- **Source:** design frontend 2, 3, 6, 7, «UI/UX Package»; prototype вариант A
|
- **Source:** design frontend 2, 3, 6, 7, «UI/UX Package»; prototype вариант A
|
||||||
- **Verification:** тесты VM и view (RTL + MSW): обгоняю, отстаю, меньше года, неполный, недоступно, неизвестная причина, загрузка, ошибка только во вкладке; `npm run verify`
|
- **Verification:** тесты VM и view (RTL + MSW): обгоняю, отстаю, меньше года, неполный, недоступно, неизвестная причина, загрузка, ошибка только во вкладке; `npm run verify`
|
||||||
- **Depends on:** 3.1, 3.2
|
- **Depends on:** 3.1, 3.2
|
||||||
- [ ] 3.4 [frontend] График результата: LineChart с тремя сериями и точкой текущей стоимости, tooltip, текстовая альтернатива, пояснение об отсутствии прошлой стоимости, состояние без series
|
- [x] 3.4 [frontend] График результата: LineChart с тремя сериями и точкой текущей стоимости, tooltip, текстовая альтернатива, пояснение об отсутствии прошлой стоимости, состояние без series
|
||||||
- **Covers:** `portfolio/investment-performance` — `График результата`, `Справочный индекс без дивидендов`
|
- **Covers:** `portfolio/investment-performance` — `График результата`, `Справочный индекс без дивидендов`
|
||||||
- **Source:** design frontend 5, «UI/UX Package»
|
- **Source:** design frontend 5, «UI/UX Package»
|
||||||
- **Verification:** тесты view: серии, `aria-label` с итогами, отсутствие референсной линии, пустой series; browser-тест на 390px без горизонтальной прокрутки; `npm run verify`
|
- **Verification:** тесты view: серии, `aria-label` с итогами, отсутствие референсной линии, пустой series; browser-тест на 390px без горизонтальной прокрутки; `npm run verify`
|
||||||
@@ -74,22 +74,22 @@ Issue: WealthLedger/main#54. Ветки в каждом repo:
|
|||||||
|
|
||||||
## 4. Docs
|
## 4. Docs
|
||||||
|
|
||||||
- [ ] 4.1 [docs] ADR 023 и строка в `adr/README.md`
|
- [x] 4.1 [docs] ADR 023 и строка в `adr/README.md`
|
||||||
- **Covers:** `N/A` — документация решения
|
- **Covers:** `N/A` — документация решения
|
||||||
- **Source:** docs-impact: `docs/adr/023-investment-performance-vs-index.md`, `docs/adr/README.md`
|
- **Source:** docs-impact: `docs/adr/023-investment-performance-vs-index.md`, `docs/adr/README.md`
|
||||||
- **Verification:** `npm run verify`
|
- **Verification:** `npm run verify`
|
||||||
- **Depends on:** none
|
- **Depends on:** none
|
||||||
- [ ] 4.2 [docs] Страница `domains/portfolio/investment-performance.md` и обновления `all-accounts.md`, `data-reference.md`, `portfolio/overview.md`
|
- [x] 4.2 [docs] Страница `domains/portfolio/investment-performance.md` и обновления `all-accounts.md`, `data-reference.md`, `portfolio/overview.md`
|
||||||
- **Covers:** `N/A` — документация текущего поведения
|
- **Covers:** `N/A` — документация текущего поведения
|
||||||
- **Source:** docs-impact: соответствующие строки таблицы
|
- **Source:** docs-impact: соответствующие строки таблицы
|
||||||
- **Verification:** `npm run verify`; сверка с реализованным поведением 2.4 и 3.3
|
- **Verification:** `npm run verify`; сверка с реализованным поведением 2.4 и 3.3
|
||||||
- **Depends on:** 2.4, 3.3, 4.1
|
- **Depends on:** 2.4, 3.3, 4.1
|
||||||
- [ ] 4.3 [docs] Глоссарий, `overview.md`, `income/overview.md`: новые термины и снятие конфликтующих запретов
|
- [x] 4.3 [docs] Глоссарий, `overview.md`, `income/overview.md`: новые термины и снятие конфликтующих запретов
|
||||||
- **Covers:** `N/A` — терминология
|
- **Covers:** `N/A` — терминология
|
||||||
- **Source:** docs-impact: `docs/glossary.md`, `docs/overview.md`, `docs/domains/income/overview.md`
|
- **Source:** docs-impact: `docs/glossary.md`, `docs/overview.md`, `docs/domains/income/overview.md`
|
||||||
- **Verification:** `npm run verify`; `grep -rn "доходност" .` — только новое значение или оговорки «не является»
|
- **Verification:** `npm run verify`; `grep -rn "доходност" .` — только новое значение или оговорки «не является»
|
||||||
- **Depends on:** 4.1
|
- **Depends on:** 4.1
|
||||||
- [ ] 4.4 [docs] `integrations/moex.md` (раздел «Индексы»), `architecture/context.md`, `architecture/http-api.md`
|
- [x] 4.4 [docs] `integrations/moex.md` (раздел «Индексы»), `architecture/context.md`, `architecture/http-api.md`
|
||||||
- **Covers:** `N/A` — документация интеграции и API
|
- **Covers:** `N/A` — документация интеграции и API
|
||||||
- **Source:** docs-impact: соответствующие строки таблицы
|
- **Source:** docs-impact: соответствующие строки таблицы
|
||||||
- **Verification:** `npm run verify`; сверка с generated OpenAPI из 2.5
|
- **Verification:** `npm run verify`; сверка с generated OpenAPI из 2.5
|
||||||
@@ -97,7 +97,7 @@ Issue: WealthLedger/main#54. Ветки в каждом repo:
|
|||||||
|
|
||||||
## 5. Завершение
|
## 5. Завершение
|
||||||
|
|
||||||
- [ ] 5.1 [specs] Commit planning artifacts и прототипа; PR в `develop`
|
- [x] 5.1 [specs] Commit planning artifacts и прототипа; PR в `develop`
|
||||||
- **Covers:** `N/A` — поставка planning artifacts
|
- **Covers:** `N/A` — поставка planning artifacts
|
||||||
- **Source:** engineering workflow
|
- **Source:** engineering workflow
|
||||||
- **Verification:** `npm run validate`; `git diff --check`
|
- **Verification:** `npm run validate`; `git diff --check`
|
||||||
|
|||||||
Reference in New Issue
Block a user