5 Commits
Author SHA1 Message Date
ksv741 fab9e5b30d Merge branch 'develop' into codex/reconcile-hourly-investment-sync
Docs security / security (push) Successful in 7s
Docs verify / verify (push) Successful in 16s
Conventional commits / commits (pull_request) Successful in 13s
Docs security / security (pull_request) Successful in 8s
Docs verify / verify (pull_request) Successful in 13s
2026-10-09 20:59:45 +03:00
ksv741 a45d3e77b9 docs: document hourly investment reads
Docs security / security (push) Successful in 8s
Docs verify / verify (push) Successful in 13s
Conventional commits / commits (pull_request) Successful in 12s
Docs security / security (pull_request) Successful in 8s
Docs verify / verify (pull_request) Successful in 14s
2026-10-09 19:56:40 +03:00
ksv741 3a272681c5 docs: clarify card summary totals 2026-10-09 16:04:56 +03:00
codex-bot 0f8bbee10a Merge pull request 'docs: document hourly sync history' (#31) from release/0.1.3 into develop
Docs security / security (push) Successful in 9s
Docs verify / verify (push) Successful in 14s
Merge hourly sync history documentation
2026-10-09 09:19:44 +03:00
ksv741 e0bceea936 docs: document hourly sync history
Docs security / security (push) Successful in 7s
Docs verify / verify (push) Successful in 13s
Conventional commits / commits (pull_request) Successful in 11s
Docs security / security (pull_request) Successful in 7s
Docs verify / verify (pull_request) Successful in 13s
2026-10-09 09:05:08 +03:00
12 changed files with 239 additions and 113 deletions
@@ -1,6 +1,8 @@
# 017. Локальное чтение данных T-Bank и фоновая синхронизация # 017. Локальное чтение данных T-Bank и фоновая синхронизация
**Status:** Accepted — архитектурное направление согласовано. **Status:** Accepted — историческое архитектурное направление. Решения ниже о
request-driven запуске, ручном обновлении и отсутствии периодического обхода
заменены [ADR 019](019-hourly-reads-and-online-instrument-reference.md).
Prefetch policy ниже заменена [ADR 018](018-minimal-runtime-coordination.md): Prefetch policy ниже заменена [ADR 018](018-minimal-runtime-coordination.md):
свечи загружаются по запрошенному диапазону, без предварительной загрузки свечи загружаются по запрошенному диапазону, без предварительной загрузки
@@ -0,0 +1,37 @@
# 019. Часовой обход и онлайн-справочник инструментов
**Status:** Accepted. Заменяет request-driven, manual-refresh и
no-periodic-walk части [ADR 017](017-local-tbank-reads-and-background-sync.md)
и дополняет [ADR 018](018-minimal-runtime-coordination.md).
## Decision
- Личные T-Bank данные текущего подключения обновляются владельцами источников
через durable hourly walk; его результат ограничен этой connection boundary.
Обычный GET и retry читают только сохранённый результат и не запускают
personal work.
- Только первое открытие неподтверждённого исторического интервала operations
и первого неоткрытого диапазона candles может получить соответственно этот
интервал или диапазон. Это не manual sync; повторное чтение не расширяет и не
запускает такую работу заново.
- Portfolio, operations, forecasts и события сохраняют собственные complete
результаты и время последнего успешного чтения. Результат ресурса
(freshness, completeness, `lastSuccessfulAt`, availability) независим от
resource-relevant последней часовой попытки: поздняя partial/failed попытка
не заменяет сохранённый complete result. При его отсутствии `data: null`.
- Detail, company, catalog search и ticker lookup получают ответ онлайн через
существующие integration clients. Их ошибки не заменяют сохранённые
финансовые данные или подписи snapshot.
- Старые справочные строки не являются источником актуальности и удаляются
только отдельной совместимой миграцией после проверки FK и свечей.
- Клиент повторяет открытые инвестиционные GET не чаще одного раза в 30 минут;
retry повторяет чтение и не создаёт screen sync work. Наблюдение последней
попытки, включая Accounts `sync`, не является командой.
## Consequences
Карточки инструментов зависят от доступности публичного upstream в момент
чтения. Финансовые экраны сохраняют последний успешный snapshot, а статус
последнего hourly walk показывается отдельно. OpenAPI backend остаётся
единственным исполняемым источником HTTP-контракта; frontend использует
опубликованный snapshot.
@@ -0,0 +1,42 @@
# 020. Evidence for hourly sync read boundaries
**Status:** Accepted. Evidence for [ADR 019](019-hourly-reads-and-online-instrument-reference.md).
## Measurements
The backend persisted-read budget was measured with `npm run test:performance`
using a synthetic SQLite dataset: three accounts, 100,000 operations per
account, 200 instruments, and 5,000 candles per instrument. The run used Node
24.21.0 on Apple M1 Pro with 32 GiB RAM and a failing synthetic upstream after
population. It measured 100 reads per route family, excluding transport
limiter cost.
The generated evidence is written by the test to its temporary
`persistent-budget.json` file. This is a repeatable measurement, not a
production SLA: it does not include network latency, real T-Bank quotas, or
multi-process SQLite contention.
The successful run produced a 617,263,104-byte SQLite database and measured
zero upstream calls during the persisted-read phase. Backend p50/p95 response
times were approximately 1.3/2.3 ms for account and portfolio reads,
3.9/4.5 ms for account operations, 6.7/7.2 ms for aggregate operations,
40.0/41.5 ms for account income analytics, and 128.1/134.2 ms for aggregate
income analytics. These figures are local synthetic measurements only.
## Durability
The migration durability check passes against a separately built repository at
the pre-history migration prefix. It verifies preservation of users, sessions,
T-Bank connection data, bank-card data, operations, categories, archive state,
and replay behavior across migration and restart.
The executed command was `node test/bank-cards-durability.cjs` with a separately
built pre-history repository. It completed with `PASS durability`.
## Limits
The configured T-Bank start limiter is `TBANK_RATE_LIMIT_PER_SECOND`, default
`5` per backend instance. The hourly scheduler therefore remains a single
durable reservation owner; the measurement does not justify increasing this
limit or changing the hourly cadence. Any production tuning requires current
T-Bank documentation and a new measurement with the deployed topology.
+2
View File
@@ -23,3 +23,5 @@
- [016 — Версионируемый комплект развёртывания](016-versioned-deployment-bundle.md) - [016 — Версионируемый комплект развёртывания](016-versioned-deployment-bundle.md)
- [017 — Локальное чтение данных T-Bank и фоновая синхронизация](017-local-tbank-reads-and-background-sync.md) - [017 — Локальное чтение данных T-Bank и фоновая синхронизация](017-local-tbank-reads-and-background-sync.md)
- [018 — Минимальная runtime-координация](018-minimal-runtime-coordination.md) - [018 — Минимальная runtime-координация](018-minimal-runtime-coordination.md)
- [019 — Часовой обход и онлайн-справочник инструментов](019-hourly-reads-and-online-instrument-reference.md)
- [020 — Evidence for hourly sync read boundaries](020-hourly-sync-verification-evidence.md)
+48 -35
View File
@@ -1,51 +1,64 @@
# Свежесть данных и повторное использование # Свежесть данных и повторное использование
WealthLedger читает инвестиционные данные из SQLite. Обычный GET возвращает WealthLedger читает личные инвестиционные данные из SQLite. Обычный GET и его
сохранённый результат и запрашивает фоновые зависимости, если они отсутствуют HTTP retry возвращают сохранённый результат и не запускают личную работу с
или устарели. HTTP-ответ не ждёт T-Bank. Модель принята в T-Bank. Нормальным владельцем обновления служит часовой обход текущего
[ADR 017](../../adr/017-local-tbank-reads-and-background-sync.md). подключения: после успешного подключения он начинает первую попытку, затем
начинает следующую не ранее чем через 60 минут от начала предыдущей. После
простоя выполняется не более одна актуальная попытка, а перекрывающийся срок
пропускается, не ставится в очередь. Общие справочные данные инструментов
запрашиваются онлайн, когда это требуется карточке; финансовый HTTP-ответ не
ждёт T-Bank. Модель принята в
[ADR 019](../../adr/019-hourly-reads-and-online-instrument-reference.md).
## Клиентский и серверный снимки ## Клиентский и серверный снимки
[Client Cached Snapshot](../../glossary.md#client-cached-snapshot) — успешный [Client Cached Snapshot](../../glossary.md#client-cached-snapshot) — успешный
ответ в TanStack Query. Он сохраняет показанное содержимое при временной ошибке ответ в TanStack Query. Он сохраняет показанное содержимое при временной ошибке
запроса в той же границе доступа. SQLite сохраняет источники независимо от HTTP-перечитывания в той же границе доступа. Это не серверный fallback и не
браузера: membership, последний полный portfolio, исполненные операции, состояние часового обхода. SQLite независимо от браузера хранит membership,
общие сведения об инструментах, котировки, свечи и календари. последний полный portfolio, исполненные операции, прогнозы, общие сведения об
инструментах, котировки, свечи и календари.
[Полнота периода](../../glossary.md#полнота-истории-счёта) и свежесть различаются. [Полнота периода](../../glossary.md#полнота-истории-счёта) и свежесть различаются.
Успешно проверенный пустой интервал полон; последняя страница журнала сама Успешно проверенный пустой результат полон и доступен; последняя страница
по себе полноту не доказывает. Ошибка сверки сохраняет прежние данные и время. журнала сама по себе полноту не доказывает. Результат каждого личного ресурса
Старый полный портфель не удаляется по истечении TTL. `isStale` показывает в `{ data, meta }` описывают пять независимых значений: freshness,
возраст portfolio source, а не сам факт неудачного обновления. completeness, `lastSuccessfulAt`, `lastAttempt` и availability. Первые,
полнота и `lastSuccessfulAt` относятся только к возвращённому подтверждённому
результату. `lastAttempt` описывает относящуюся к ресурсу часовую попытку:
`none`, `running`, `succeeded`, `partial` или `failed`.
| Источник | Когда обычный запрос требует обновления | Поздняя неуспешная или частичная попытка сохраняет прежний полный результат и
его исходное время. Если полного результата нет, `data` равно `null`: состояние
availability объясняет первоначальную загрузку, временную недоступность либо
недоступность подключения. Пустой массив не означает отсутствие результата.
Старый полный portfolio не удаляется из-за возраста. `Portfolio.isStale`
сохраняет смысл возраста его snapshot, а не обозначает неуспех обхода или
клиентскую ошибку перечитывания.
| Источник | Владелец нормального обновления |
| --- | --- | | --- | --- |
| Portfolio, котировки, изменяемый хвост дневных свечей | Через 60 секунд | | Membership, portfolio, изменяемый хвост операций и события | Часовой обход текущего подключения |
| Membership и изменяемый хвост операций | Через 5 минут | | Котировки и изменяемый хвост дневных свечей | Установленная политика источника; не ordinary personal GET |
| Metadata, поиск каталога, company и календари | Через сутки | | Metadata, поиск каталога и company | Онлайн при чтении |
| Полные прошлые интервалы операций и завершённые свечи | При явном обновлении показанного периода | | Полные прошлые интервалы операций и завершённые свечи | Не обновляются часовым обходом или обычным GET |
TTL не создаёт таймер импорта. Запрошенная работа может закончиться после Два исключения разрешают только первое получение отсутствующего результата:
ухода со страницы. После перезапуска нет автоматического обхода intent: первое открытие неподтверждённого исторического интервала операций получает
следующий подходящий запрос повторяет незавершённый интервал с первой страницы, только этот интервал и возвращает локальные строки с неполным покрытием; первое
переиспользуя полные интервалы. Cursor брокера не служит durable checkpoint. открытие диапазона свечей без подтверждённого локального результата получает
только этот диапазон. Ни одно исключение не обновляет membership, portfolio,
события, другой интервал или диапазон. Их HTTP retry также только перечитывает
локальное состояние. Подтверждённый полный интервал или диапазон остаётся
локальным.
Ручное «Обновить» перечитывает зависимости текущего экрана и применённый Часовой обход сохраняет успешные разделы независимо, а failed section не
период независимо от TTL. Повторная сверка полного периода публикуется одной заменяет прежний complete result. Отменой query lifecycle владеет TanStack
транзакцией после всех страниц, интервалов и счетов, включая `all`. Query. Открытый экран перечитывает ресурс не чаще одного раза в 30 минут;
Сбой сохраняет весь прежний результат. Первая загрузка может публиковать это локальный HTTP read. Совместимый параметр `sync=observe`, пока он есть в
полные малые интервалы и показывать частичный период без окончательных сумм. маршруте, также является только чтением и не представляет команду обходу.
[HTTP API](../http-api.md) описывает исполняемый контракт envelope.
Клиент наблюдает только известную активную работу открытого экрана.
Status GET и data GET с `sync=observe` не запускают работу. После изменения
состояния ответ попадает в ту же query identity; после ухода или завершения
наблюдение прекращается. Поздний ответ не заменяет уже принятый новый.
[HTTP API](../http-api.md) объясняет envelope и экранный refresh.
Отменой query lifecycle владеет TanStack Query. Status не пересобирает
финансовые ответы и не передаёт hash notifications. Свечи нового диапазона
загружаются при его открытии; предзагрузка всей истории портфеля не выполняется.
## Смена подключения ## Смена подключения
+21 -20
View File
@@ -2,7 +2,8 @@
## Источник контракта ## Источник контракта
Backend генерирует OpenAPI из собственного публичного API. Frontend использует Backend генерирует OpenAPI из собственного публичного API. Этот сгенерированный
OpenAPI — исполняемый источник HTTP-контракта. Frontend использует
предоставленный OpenAPI snapshot как актуальный вход и локально генерирует предоставленный OpenAPI snapshot как актуальный вход и локально генерирует
типизированный клиент. Backend владеет контрактом, а подготовка и доставка типизированный клиент. Backend владеет контрактом, а подготовка и доставка
схемы находятся вне frontend. Это разделение принято в схемы находятся вне frontend. Это разделение принято в
@@ -22,27 +23,27 @@ environment policy, а не используется frontend в runtime.
## Инвестиционные данные и синхронизация ## Инвестиционные данные и синхронизация
T-Bank data GET возвращают envelope `{ data, meta }`. `data` может быть `null` Затронутые GET инвестиционных ресурсов возвращают сгенерированный contract
при missing или неполном финансовом агрегате; partial ledger/candles могут `{ data, meta }`. `data` nullable: `null` означает отсутствие complete result,
содержать подтверждённые строки. `meta` разделяет availability, freshness, а подтверждённый пустой массив является available complete result. `meta`
lastSuccessfulAt, sync, coverage и состояния зависимостей. `dataRevision` независимо описывает freshness, completeness, `lastSuccessfulAt`,
описывает набор данных; пустое завершённое покрытие явно представлено в coverage. resource-relevant `lastAttempt` и availability. `lastAttempt` имеет состояния
Публичной hash-версии состояния нет. Ни одно поле не раскрывает connection revision. `none`, `running`, `succeeded`, `partial` и `failed`; его неуспех не заменяет
сохранённый complete result или его время. `Portfolio.isStale` остаётся
специфичным для snapshot полем, а не заменой состояния попытки.
Обычный GET планирует missing/stale зависимости после проверки доступа. Только Accounts дополнительно возвращает top-level `sync`: `null`, если у
GET с `sync=observe` только читает. `POST /api/v1/tbank-sync` принимает текущего подключения ещё не было часовой попытки, иначе connection-wide
валидированный screen descriptor и возвращает `202` с handle и нормализованным наблюдаемый результат. Это наблюдение, не команда. Обычный GET и retry после
scope. `GET /api/v1/tbank-sync/:id` наблюдает разрешённую работу; чужой или проверки доступа читают локальное состояние и не начинают личную работу.
прежний handle возвращает `404`. Приём работы не означает её успех. Устаревший `sync=observe`, пока он сохранён для совместимости маршрута, также
только читает. Request-started screen-sync command/status не является текущей
AS-IS моделью.
Status возвращает state/errorCode без данных ресурсов и численных progress Безопасные ошибки завершённой часовой попытки передаются в `lastAttempt` и
counters. Клиент перечитывает показанные resources через `sync=observe` во время Accounts `sync`; HTTP transport/access/input errors сохраняют Problem Details.
работы и после завершения, сохраняя generated query identity. Sync states ресурса: Старые данные при временной source failure сохраняют исходное успешное время.
`idle`, `running`, `failed`; отдельного публичного `queued` нет. Ни одно поле не раскрывает connection revision. Детали поведения —
Source failure принятой работы передаётся безопасным `meta.sync.errorCode`;
HTTP transport/access/input errors сохраняют Problem Details. Старые данные
при transient failure сохраняют исходное успешное время. Детали поведения —
[свежесть и полнота](cross-cutting/data-freshness.md). [свежесть и полнота](cross-cutting/data-freshness.md).
Envelope несовместим с прежними bare-array ответами: backend/frontend Envelope несовместим с прежними bare-array ответами: backend/frontend
+2 -3
View File
@@ -89,9 +89,8 @@ JavaScript Number. Отдельный ввод ограничен signed64: на
следующие страницы. Баланс при выборе прошлого периода остаётся текущим. следующие страницы. Баланс при выборе прошлого периода остаётся текущим.
Сводка учитывает все подходящие записи, включая не показанные на странице. Сводка учитывает все подходящие записи, включая не показанные на странице.
Сводка одной или всех карт показывает одну итоговую сумму расходов с учётом Сводка одной или всех карт отдельно показывает расходы, возвращённую сумму и
возвратов и компактные строки категорий по убыванию итоговых расходов. Отдельные показатели «Возвращено» итог после возвратов, а также компактные строки категорий по убыванию итоговых расходов. Возврат уменьшает расходы месяца своего
и «После возвратов» в интерфейсе скрыты; расчёт и операции возврата сохранены. Возврат уменьшает расходы месяца своего
получения: августовская покупка 1 000 ₽ и сентябрьский возврат 600 ₽ дают получения: августовская покупка 1 000 ₽ и сентябрьский возврат 600 ₽ дают
августовский итог 1 000 ₽ и сентябрьский −600 ₽ при отсутствии других трат. августовский итог 1 000 ₽ и сентябрьский −600 ₽ при отсутствии других трат.
Начальный остаток, пополнения и переводы в эти показатели не входят. Начальный остаток, пополнения и переводы в эти показатели не входят.
+30 -23
View File
@@ -40,10 +40,10 @@
## Период и список ## Период и список
Без фильтра период начинается сегодня в UTC и заканчивается через 30 дней Без фильтра период начинается сегодня в UTC и заканчивается через 30 дней
после начала, обе даты включены. Поля «С» и «По» позволяют изменить период: после начала, обе даты включены. `from` и `to` принимают только строгие даты
«Применить» сохраняет выбранные календарные даты в URL и запрашивает результат, `YYYY-MM-DD`; без `from` используется текущая дата UTC, без `to` — `from + 30`
«Сбросить» возвращает период по умолчанию. Обзор счёта сохраняет прогноз календарных дней. Поля «С» и «По» сохраняют выбранные календарные даты в URL,
на текущую дату и следующие 30 дней. а «Сбросить» возвращает период по умолчанию.
События упорядочены по календарной дате. У каждой строки видны тип, События упорядочены по календарной дате. У каждой строки видны тип,
инструмент, дата и доступная сумма в исходной валюте. Список не имеет инструмент, дата и доступная сумма в исходной валюте. Список не имеет
@@ -52,10 +52,13 @@
отсутствия всех будущих выплат. Инструмент без поддерживаемых данных отсутствия всех будущих выплат. Инструмент без поддерживаемых данных
не получает искусственную строку «нет данных». не получает искусственную строку «нет данных».
API принимает необязательные `from` и `to` в формате `YYYY-MM-DD` для Сохранённый forecast сохраняет свой `calculatedAt` и исходное включительное
отдельного и общего счёта. Без `from` используется сегодня в UTC, окно. GET для отдельного счёта или `all` возвращает только пересечение этого
без `to` — 30 дней после начала. Неверный формат или несуществующая дата окна и запрошенного UTC-периода, не меняя ни `calculatedAt`, ни исходное окно.
дают `400`; обратный период — `400 INVALID_EVENTS_PERIOD`. Пустое пересечение или отсутствие событий в пересечении — успешный пустой
список с теми же metadata; это не отсутствие forecast. Неверный формат или
несуществующая дата дают `400`; обратный период — `400 INVALID_EVENTS_PERIOD`
без замены периода и без запуска личной работы.
Фильтрация на сервере предшествует сортировке и вычислению Фильтрация на сервере предшествует сортировке и вычислению
`incomeTotalsByCurrency`, включающего только купоны и дивиденды. `incomeTotalsByCurrency`, включающего только купоны и дивиденды.
Итоги остаются в ответе API; полный экран показывает список без общего итога. Итоги остаются в ответе API; полный экран показывает список без общего итога.
@@ -63,26 +66,30 @@ API принимает необязательные `from` и `to` в форма
## Получение и отказы ## Получение и отказы
Запрос требует сессии, подключения и локально подтверждённого membership. Запрос требует сессии, подключения и локально подтверждённого membership.
Прогноз строится из личного сохранённого portfolio и общих календарей: T-Bank для Он читает сохранённый forecast и не запускает личную синхронизацию. Часовой
дивидендов, MOEX для bond events. Отдельного синхронного получения portfolio обход строит forecast из полного личного portfolio и общих календарей: T-Bank
нет. Личные оценки сохраняются отдельно от общих календарей. для дивидендов, MOEX для bond events. Личные оценки сохраняются отдельно от
общих календарей.
Последний полный forecast привязан к scope, периоду и membership. Последний полный forecast привязан к scope, исходному окну и membership. Если
Если новый portfolio требует ещё недоступного календаря, прежний полный часовой обход не может получить полный новый forecast, прежний полный прогноз,
прогноз и его source times остаются, в том числе после рестарта. Новый его `calculatedAt` и исходное окно остаются читаемыми, в том числе после
полный результат заменяет его атомарно. Без fallback показывается рестарта; `meta.lastAttempt` отдельно сообщает об ошибке обновления. Без
missing/partial, а не подтверждённое отсутствие выплат. Другой период или полного forecast `data: null` и metadata обозначает initial loading либо
изменившийся membership не получают старый прогноз под новым названием. временную недоступность, а не подтверждённое отсутствие выплат. В режиме `all`
Source failure сообщается в metadata и не изменяет успешное время. результат complete только при полном вкладе каждого включённого счёта. Другой
scope или изменившийся membership не получают старый forecast под новым
названием.
Успешный ответ MOEX без запрошенного блока означает отсутствие поддерживаемых Успешный ответ MOEX без запрошенного блока означает отсутствие поддерживаемых
событий этого источника; это отличается от transport failure и повреждённого событий этого источника; это отличается от transport failure и повреждённого
формата ответа. формата ответа.
При первом открытии показан каркас заголовка и строк, без интерактивных При первом открытии без complete forecast показан каркас заголовка и строк,
заглушек. Пустой ответ сохраняет возможность изменить период. без интерактивных заглушек. Пустой успешный ответ сохраняет возможность
При обычном фоновом обновлении уже полученные данные остаются на экране, изменить период. При failed HTTP reread уже полученный Client Cached Snapshot
ошибка получает предупреждение и повторную попытку. Без выбранного счёта остаётся на экране; это отличается от server-side failed hourly attempt.
запрос не выполняется. Смена сессии и подключения следует Повторная попытка перечитывает только HTTP-ответ. Без выбранного счёта запрос
не выполняется. Смена сессии и подключения следует
[общим правилам финансовых данных](../../architecture/cross-cutting/data-freshness.md). [общим правилам финансовых данных](../../architecture/cross-cutting/data-freshness.md).
Внешние границы объясняют интеграции [T-Bank](../../integrations/tbank.md) Внешние границы объясняют интеграции [T-Bank](../../integrations/tbank.md)
+6
View File
@@ -143,3 +143,9 @@ BondBy переиспользуется зависимостями выпуск
и [проверками](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/src/modules/investments/instruments/bonds/application/bond.spec.ts). и [проверками](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/src/modules/investments/instruments/bonds/application/bond.spec.ts).
Границы источников описаны в [разделе интеграций](../../integrations/README.md), Границы источников описаны в [разделе интеграций](../../integrations/README.md),
сходное разделение рыночных и личных данных — в [акциях](stocks.md). сходное разделение рыночных и личных данных — в [акциях](stocks.md).
## Online reference
Bond detail, search, and ticker lookup are read from the upstream integration
on each request. Saved quotes, candles, events, ratings, and portfolio labels
retain their own freshness and failure boundaries.
+7
View File
@@ -184,3 +184,10 @@ scope. Частичные строки видны с предупреждени
[рыночным данным](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/src/modules/investments/instruments/stocks/application/stock-market.service.ts), [рыночным данным](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/src/modules/investments/instruments/stocks/application/stock-market.service.ts),
[выплатам](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/src/modules/investments/instruments/stocks/application/stock-income.service.ts) и [проверкам](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/src/modules/investments/instruments/stocks/application/stock.spec.ts). [выплатам](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/src/modules/investments/instruments/stocks/application/stock-income.service.ts) и [проверкам](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/src/modules/investments/instruments/stocks/application/stock.spec.ts).
Сопоставление с облигациями — на [странице облигаций](bonds.md). Сопоставление с облигациями — на [странице облигаций](bonds.md).
## Online reference
Stock detail, company data, search, and ticker lookup are read from the
upstream integration on each request. Saved positions, quotes, candles, and
events remain independent local resources and are not replaced by a reference
read failure.
+32 -31
View File
@@ -1,46 +1,47 @@
# Загрузка портфеля # Загрузка портфеля
Экран `/accounts/:accountId` читает последний полный snapshot из SQLite Экран `/accounts/:accountId` читает последний полный snapshot из SQLite после
после проверки сессии, подключения и локального membership. Если список проверки сессии, подключения и локального membership. Если список счетов ещё
счетов ещё неизвестен, сервер сообщает о загрузке доступа без выдачи личных неизвестен, сервер сообщает о загрузке доступа без выдачи личных данных
данных неизвестного ID. Известный недоступный счёт возвращает `404`. неизвестного ID. Известный недоступный счёт возвращает `404`.
## Основной обмен ## Основной обмен
Обычный GET возвращает локальный envelope. Missing/stale зависимости Обычный GET и retry возвращают read-only локальный `{ data, meta }` envelope:
запрашиваются в фоне; чтение не ждёт T-Bank. Для нового полного snapshot они не ждут и не вызывают T-Bank и не предлагают ручную синхронизацию. Часовой
нужны оба финансовых ответа `GetPortfolio` и `GetPositions`. Суммы и позиции обход текущего подключения обновляет Portfolio независимо от экрана. Для нового
публикуются вместе; metadata дополняет представление независимо, с доступным полного snapshot ему нужны оба финансовых ответа `GetPortfolio` и
UID/FIGI вместо имени до её загрузки. `GetPositions`; суммы и позиции публикуются вместе. Metadata инструмента
дополняет представление независимо, с доступным UID/FIGI вместо имени до её
онлайн-загрузки.
Свежесть portfolio — 60 секунд. Истечение срока не удаляет snapshot и не Открытый экран выполняет локальное перечитывание не чаще одного раза в
создаёт таймер. «Обновить» на обзоре перечитывает зависимости текущего экрана: 30 минут; retry также повторяет только HTTP GET. `data: null` означает, что
membership, portfolio, показанные операции, доход и будущие события. полного snapshot ещё нет, а не нулевой портфель. Пустой подтверждённый portfolio
Секции могут быть готовы независимо. Подробнее — остаётся available complete result. Секции могут быть готовы независимо.
[свежесть](../../../architecture/cross-cutting/data-freshness.md). Подробнее — [свежесть](../../../architecture/cross-cutting/data-freshness.md).
## Границы доступа ## Границы доступа
Публикация проверяет connection revision и attempt в той же транзакции. Публикация проверяет connection revision и attempt в той же транзакции. Замена
Замена или удаление подключения не допускает позднюю запись прежней работы. или удаление подключения не допускает позднюю запись прежней работы. Frontend
Frontend очищает недоступный personal view и отклоняет запоздалые ответы. очищает недоступный personal view и отклоняет запоздалые ответы. В режиме
В режиме [«Все счета»](../all-accounts.md#полнота-свежесть-и-доступ) состав [«Все счета»](../all-accounts.md#полнота-свежесть-и-доступ) состав accounts и
accounts и snapshots читается согласованно, но их source times могут различаться. snapshots читается согласованно, но их source times могут различаться.
## Ошибки и результат для пользователя ## Ошибки и результат для пользователя
Нет подключения — `409 TBANK_NOT_CONNECTED`, известный отказ токена — Нет подключения — `409 TBANK_NOT_CONNECTED`, известный отказ токена —
`400 INVALID_TBANK_TOKEN`, недоступный счёт — `404`. Они не маскируются `400 INVALID_TBANK_TOKEN`, недоступный счёт — `404`. Они не маскируются
запасным snapshot. Transient source failure оставляет прежний полный портфель запасным snapshot. Временная ошибка часового обхода оставляет прежний полный
и его исходное время с безопасным предупреждением. При отсутствии snapshot portfolio и его исходное `lastSuccessfulAt`; `meta.lastAttempt` сообщает о
показывается missing/failed, без нулевых итогов. `503` означает ошибку failed или partial попытке отдельно. `Portfolio.isStale` сообщает возраст
обслуживания HTTP-запроса, а не принятой фоновой работы. snapshot, а Client Cached Snapshot и ошибка браузерного HTTP-перечитывания
имеют независимый смысл. При отсутствии полного snapshot `data: null` и
metadata обозначает initial loading либо временную недоступность, без нулевых
итогов. `503` означает ошибку обслуживания самого HTTP-запроса, а не
завершившуюся фоновую работу.
Observer запрашивает статус известной работы, затем `sync=observe` читает Денежные правила находятся в [справочнике](../data-reference.md), проверка
изменившийся ресурс без запуска синхронизации. При уходе и terminal outcome расхождения — в [диагностике](../how-to/investigate-discrepancy.md), RPC/лимиты
наблюдение прекращается. Повторное открытие может запросить недостающее; — в [интеграции T-Bank](../../../integrations/tbank.md).
после рестарта сохранённый snapshot доступен сразу.
Денежные правила находятся в [справочнике](../data-reference.md),
проверка расхождения — в [диагностике](../how-to/investigate-discrepancy.md),
RPC/лимиты — в [интеграции T-Bank](../../../integrations/tbank.md).
+9
View File
@@ -152,6 +152,15 @@ _Не использовать_: валютная позиция, заблоки
_Не использовать_: история портфеля, snapshot для графика. _Не использовать_: история портфеля, snapshot для графика.
## Точка истории стоимости портфеля
Полная оценка одного Brokerage Account в одной валюте на момент успешного
обновления Portfolio; последовательность таких точек описывает изменение
оценки во времени, включая неторговые дни. Не является Portfolio Snapshot
или инвестиционной доходностью; отсутствие цены позиции не приравнивается к нулю.
_Не использовать_: снимок состава портфеля, доходность.
## Client Cached Snapshot ## Client Cached Snapshot
Технический термин: последний успешно полученный frontend-ответ React Query, который остаётся Технический термин: последний успешно полученный frontend-ответ React Query, который остаётся