Files
specs/openspec/changes/persist-tbank-data/specs/tbank-data-sync/spec.md
T
ksv741 2f5814abdd
Conventional commits / commits (pull_request) Successful in 12s
Specs security / security (pull_request) Successful in 7s
Specs verify / verify (pull_request) Successful in 11s
Specs security / security (push) Successful in 7s
Specs verify / verify (push) Successful in 9s
spec: complete persist-tbank-data implementation
Record the approved proposal, delta specifications, design and 27 verified
implementation tasks. Keep the change active; archiving and main-spec synchronization
are deferred.
2026-09-26 21:29:24 +03:00

314 lines
33 KiB
Markdown

## Purpose
Определяет постоянное хранение данных T-Bank, локальное чтение и синхронизацию
по запросу с проверяемыми полнотой, свежестью и изоляцией подключения.
## ADDED Requirements
### Requirement: Все экраны читают сохранённые данные без ожидания T-Bank
Система SHALL постоянно сохранять уже загруженные счета, последний полный
портфель, исполненные операции, данные инструментов, текущие котировки,
дневные свечи и календари выплат. Доходы, личные позиции и прогнозные выплаты
SHALL рассчитываться по сохранённым источникам. Все соответствующие экраны,
включая режим «Все счета», поиск, сведения о компании и зависимости котировок,
MUST читать доступные данные без ожидания ответа T-Bank. Перезапуск backend
MUST NOT требовать повторной загрузки сохранённых полных интервалов.
#### Scenario: Брокер медленно отвечает при заполненном хранилище
- **WHEN** User открывает любой экран с сохранёнными данными текущего подключения, а T-Bank задерживает ответ
- **THEN** обычный серверный ответ содержит сохранённые данные и их состояние, не ожидая T-Bank, включая проверку доступных счетов
#### Scenario: Перезапуск сохраняет историю
- **WHEN** backend перезапущен и User запрашивает ранее полностью загруженный прошлый период
- **THEN** система возвращает его из постоянного хранилища без повторного получения операций или завершённых свечей этого периода
#### Scenario: Данные ещё не загружены
- **WHEN** User впервые запрашивает отсутствующие данные
- **THEN** ответ различает отсутствие загруженного результата и подтверждённый пустой результат, сообщает о запрошенной загрузке и не ожидает её завершения
### Requirement: Обновление запускается только запросом с учётом свежести
Система MUST запускать синхронизацию только запросом экрана либо ручным
обновлением. Обычный запрос SHALL планировать только отсутствующие зависимости,
пропуски и данные с истекшим сроком свежести: портфель и текущие цены —
60 секунд; операции текущего месяца и доступные счета — 300 секунд;
справочник инструментов, включая сведения о компании, и календари выплат —
24 часа; незавершённая дневная свеча — 60 секунд. Свежесть MUST отсчитываться
от последнего успешного получения соответствующего источника, а не чтения
ответа или расчёта производного представления. Полнота и свежесть MUST
оставаться независимыми признаками. Периодические запуски MUST NOT выполняться.
Для текущего изменяемого периода полнота SHALL относиться к состоянию на
последний успешный `asOf`: рост `to=now` внутри TTL не является новым
пропуском, требующим RPC. Настоящие пропуски до `asOf` и незавершённый хвост
при переходе месяца MUST NOT маскироваться этим правилом.
#### Scenario: Данные ещё свежие
- **WHEN** экран повторно запрошен до истечения сроков всех его полных зависимостей
- **THEN** запрос не создаёт новых обращений к T-Bank
#### Scenario: Текущий период запрошен через секунду
- **WHEN** текущий месяц полностью загружен на `asOf` и повторный запрос через секунду имеет более поздний `to=now`
- **THEN** система возвращает сохранённый результат с исходным `asOf` без нового RPC до истечения пяти минут, не утверждая наблюдение новых операций после `asOf`
#### Scenario: Истекла только свежесть портфеля
- **WHEN** портфель получен 60 секунд назад, а полные счета и metadata ещё свежие
- **THEN** обычный запрос инициирует обновление портфеля и только иных необходимых устаревших зависимостей, сохраняя прежний ответ доступным
#### Scenario: Пользователь не обращается к системе
- **WHEN** запрошенная работа завершилась и сроки свежести истекают без новых запросов
- **THEN** никакая синхронизация не запускается, в том числе при запуске backend
### Requirement: Ручное обновление ограничено текущим экраном
Действие «Обновить» SHALL обходить давность данных и запрашивать обновление
экрана с его зависимостями: текущего счёта либо доступных счетов в режиме
«Все счета», применённого периода и сравнений доходов, либо инструмента
с показанными свечами, выплатами и личной историей. Сервер MUST определять
разрешённые зависимости сам, сохраняя проверку входа и доступа. Обновление
MUST NOT перечитывать всю накопленную историю других периодов и инструментов.
Подтверждение приёма работы MUST NOT означать успешное обновление данных.
#### Scenario: Обновление старого месяца
- **WHEN** User нажимает «Обновить» на журнале за полностью загруженный прошлый месяц
- **THEN** перечитывается показанный период счёта и необходимые зависимости без принудительного перечитывания остальных полных месяцев
#### Scenario: Обновление доходов
- **WHEN** User обновляет аналитику за выбранный период
- **THEN** обновление охватывает выбранный и требуемые периоды сравнения, а результаты разных периодов не подменяют друг друга
#### Scenario: Обновление инструмента
- **WHEN** User обновляет инструмент с выбранным диапазоном графика и показанной личной историей
- **THEN** обновляются зависимости этого инструмента, диапазон свечей и показанный период личной истории разрешённых счетов без обновления всего каталога
### Requirement: Первая загрузка приоритизирует экран и продолжает историю
Система SHALL сначала загружать данные нужного экрану периода, включая
периоды сравнения доходов, затем всю доступную историю запрошенного счёта.
Для режима «Все счета» это применяется к каждому доступному счёту.
В рамках уже запрошенной работы SHALL заранее загружаться доступная рыночная
история бумаг текущего портфеля; для остальных инструментов — при первом
открытии. Новый запрос нужного экрану пропуска MUST получать приоритет перед
ещё не начатой догрузкой старой истории. Начатая работа MAY завершиться
после ухода со страницы. Недоказанная нижняя граница доступной истории
MUST NOT выдаваться за подтверждение её полной загрузки.
#### Scenario: Сначала сравнения доходов
- **WHEN** история счёта отсутствует и User открывает доходы за месяц
- **THEN** выбранный месяц и доступные обязательные периоды сравнения загружаются перед остальной историей; состояние старой истории не задерживает уже полный результат экрана
#### Scenario: Уход со страницы
- **WHEN** User уходит после начала догрузки истории
- **THEN** запрошенная работа может закончиться и сохранить результат, но не создаёт расписание последующих обновлений
#### Scenario: Инструмент отсутствует в портфеле
- **WHEN** User впервые открывает такой инструмент
- **THEN** система запрашивает и сохраняет его историю, не предзагружая весь каталог
### Requirement: Полнота подтверждается успешным покрытием интервалов
Система MUST считать историю счёта полной за период только после успешного
получения всех доступных исполненных операций всего этого интервала на момент
сверки. Полный период MAY быть пустым. Фильтр категорий, инструмента, размер
страницы, отсутствие записей, максимальная дата операции и наличие курсора
MUST NOT служить доказательством полноты. Производное представление «Все
счета» MUST учитывать покрытие каждого входящего счёта. Полные прошлые месяцы
SHALL сохраняться без автоматической повторной сверки; пропуски SHALL
догружаться при запросе. Смена месяца MUST NOT автоматически закрывать
незагруженный хвост прошлого месяца. Существующая семантика входных дат
журнала и доходов MUST сохраняться без пропусков и двойного учёта на границах.
#### Scenario: Пустой полный интервал
- **WHEN** все страницы интервала успешно получены и операций нет
- **THEN** период обозначается полным и пустым; последующее обычное чтение прошлого месяца не обращается к брокеру за этим интервалом
#### Scenario: Сбой промежуточной страницы
- **WHEN** одна из нескольких страниц интервала недоступна
- **THEN** интервал остаётся неполным, сохранённый результат не удаляется, частичные записи не выдаются за полный период
#### Scenario: Граница месяца
- **WHEN** последний успешный запрос прошлого месяца закончился до его конца
- **THEN** первый подходящий запрос нового месяца догружает недостающий хвост; смена даты сама не подтверждает полноту прошлого месяца
#### Scenario: Неполон один из счетов
- **WHEN** все счета, кроме одного, полностью покрывают период общего дохода
- **THEN** общий период не считается полным, а отсутствующий вклад счёта не заменяется нулём
### Requirement: Сверка операций сохраняет идентичность и прежний результат
Система SHALL использовать стабильный ID брокерской операции в границах
пользователя, подключения и счёта как принятое допущение проекта, а не гарантию
API. Повторное получение ID MUST обновлять поля той же операции без дубля
и двойного финансового учёта. Ручная сверка MUST согласовывать состав и поля
исполненных операций показанного интервала: отсутствующие в полном новом
ответе записи могут быть удалены только вместе с успешным завершением всех
страниц этого интервала. Частичный либо ошибочный новый ответ MUST NOT
изменять ранее опубликованный полный финансовый результат и время его
успешной сверки. Автоматическое обнаружение изменений полной старой истории
и смены ID не требуется.
#### Scenario: Поля операции исправлены
- **WHEN** успешная повторная сверка получает прежний ID с другой суммой или статусом, меняющим вхождение в исполненную историю
- **THEN** итоговый набор отражает новый полный ответ без дублирования ID, а доходы пересчитываются из согласованного набора
#### Scenario: Операция исчезла при ручной сверке
- **WHEN** все страницы перечитанного интервала успешно завершились и прежняя операция отсутствует
- **THEN** операция исключается из опубликованного набора этого интервала и производных финансовых результатов
#### Scenario: Перезагрузка полного периода оборвалась
- **WHEN** после нескольких страниц ручной сверки возникает ошибка
- **THEN** прежний полный набор, его суммы, полнота и время успешной сверки остаются доступными вместе с ошибкой обновления
#### Scenario: Сбой во втором месяце перечитываемого квартала
- **WHEN** ручная сверка полного квартала получила первый месяц, но оборвалась на странице второго
- **THEN** весь прежний результат показанного квартала, включая режим «Все счета», остаётся опубликованным; успешный новый первый месяц не смешивается с прежними остальными месяцами
### Requirement: Прогресс переживает перезапуск и одновременные запросы
Система MUST сохранять завершённые интервалы, незавершённую работу и её
прогресс. После перезапуска следующий подходящий запрос экрана или ручное
обновление SHALL возобновлять запрошенную работу, повторяя незавершённый
интервал безопасно и без повторной загрузки полных старых интервалов.
Одновременные запросы одной границы данных SHALL объединять одинаковую
работу, а пересекающиеся интервалы SHALL использовать общую загрузку
пересечения. Ошибка MUST оставлять возможность следующего явного запроса,
не создавая бесконечный цикл повторов. Поздняя публикация устаревшей попытки
MUST NOT перезаписывать результат новой попытки.
#### Scenario: Двадцать одновременных запросов
- **WHEN** двадцать запросов требуют один отсутствующий интервал одного счёта
- **THEN** выполняется один проход страниц этого интервала с разрешёнными транспортными повторами, а все запросы видят общее состояние работы
#### Scenario: Перезапуск между страницами
- **WHEN** процесс остановлен после сохранения прогресса, но до полного завершения интервала
- **THEN** до следующего запроса работа не возобновляется; после запроса интервал перечитывается без дублей и без ложного признака полноты
#### Scenario: Ошибка завершила попытку
- **WHEN** запрошенная синхронизация исчерпала допустимые транспортные повторы
- **THEN** она показывает безопасную ошибку и прекращает попытки до следующего запроса; наблюдение её состояния не запускает её заново
### Requirement: Общие данные инструментов отделены от личных
Система SHALL совместно использовать неперсональные metadata, котировки,
свечи и календари одного инструмента между пользователями. Общие записи
MUST NOT содержать аккаунты, личные позиции, остатки, операции, оценки выплат
пользователя, token либо персональные ошибки. Личные данные и вычисления
MUST оставаться ограничены пользователем, доступным счётом и текущим
подключением. Успешный общий результат MAY переиспользоваться между счетами
без повторного получения того же источника.
#### Scenario: Два пользователя открывают инструмент
- **WHEN** оба имеют право пользоваться интеграцией и общие данные инструмента уже загружены
- **THEN** они используют одни общие данные, но видят только собственные позиции, операции и оценки выплат
#### Scenario: Ошибка token одного пользователя
- **WHEN** источник отклоняет token одного пользователя при загрузке общего инструмента
- **THEN** личная ошибка не становится состоянием инструмента для другого пользователя и не удаляет ранее сохранённые общие данные
### Requirement: Локальная проверка доступа сохраняет границу подключения
Каждое чтение и действие синхронизации MUST проверять сессию, текущее
подключение и локально подтверждённую принадлежность счёта этому подключению.
Отсутствие ранее подтверждённого списка SHALL означать загрузку доступа,
а не доступ к произвольному accountId или доказанный пустой список.
Устаревший подтверждённый список MAY использоваться до фоновой сверки;
успешная сверка MUST отзывать доступ к исчезнувшим, закрытым или неподдержанным
счетам. Известный отказ token MUST сохранять существующую ошибку
`INVALID_TBANK_TOKEN`, отсутствие подключения — `TBANK_NOT_CONNECTED`,
недоступный счёт — `404`. Хранение не вводит архив без подключения.
Успешная замена или удаление подключения MUST блокировать выдачу прежних
личных данных и публикацию любых поздних результатов прежней работы.
Неуспешная замена MUST сохранять действующую границу.
#### Scenario: Подтверждённый счёт проверяется локально
- **WHEN** User читает заполненные данные доступного счёта текущего подключения
- **THEN** проверка принадлежности не обращается синхронно к T-Bank и не использует список другого пользователя или подключения
#### Scenario: Прямой переход до загрузки счетов
- **WHEN** список текущего подключения ещё неизвестен и User запрашивает конкретный accountId
- **THEN** система сообщает о загрузке проверки доступа, не выдаёт личных данных этого ID и загружает доступные счета
#### Scenario: Доступ к счёту отозван
- **WHEN** успешная фоновая сверка больше не подтверждает доступ к ранее видимому счёту
- **THEN** последующие запросы этого счёта возвращают `404`, а клиент прекращает показывать его как доступный
#### Scenario: Поздний результат прежнего подключения
- **WHEN** старый запрос заканчивается после успешной замены или удаления подключения
- **THEN** его данные, прогресс, ошибки и признаки свежести не публикуются и не становятся результатом нового подключения
### Requirement: Завершённость свечей подтверждается источником
Система SHALL сохранять дневные свечи по инструменту и времени источника.
Завершённые свечи MUST NOT перечитываться обычным запросом при полном
покрытии диапазона. Незавершённая свеча SHALL обновляться при запросе после
60 секунд до явного подтверждения завершённости источником, даже если её
календарный день прошёл. Успешное пустое окно MAY подтверждать отсутствие
свечей только в завершённом диапазоне и MUST NOT создавать синтетические
свечи. Открытое окно текущего дня, в том числе пока пустое, SHALL оставаться
изменяемым с минутным сроком свежести. Ручное обновление SHALL
перечитывать показанный диапазон, сохраняя прежние данные при ошибке.
#### Scenario: Наступил новый день
- **WHEN** сохранённая свеча предыдущего дня не подтверждена как завершённая источником
- **THEN** новый день не делает её окончательной, и подходящий запрос продолжает обновлять её с минутным сроком свежести
#### Scenario: Выходной без свечей
- **WHEN** источник успешно подтвердил пустое окно без торговых свечей
- **THEN** окно сохраняется как проверенное без выдуманных цен и повторной обычной загрузки завершённого диапазона
#### Scenario: До начала торгов свеча отсутствует
- **WHEN** запрос текущего дня вернул пустое окно, а после начала торгов User снова открывает график по истечении минуты
- **THEN** система запрашивает изменяемое окно и сохраняет появившуюся свечу; утренний ответ не закрепляет окончательное отсутствие свечи
### Requirement: Интерфейс различает свежесть полноту и состояние работы
На всех экранах T-Bank клиент MUST показывать время последнего успешного
получения, свежесть, загрузку и безопасную ошибку обновления применимых
данных. Полнота MUST относиться к показанному периоду и его зависимостям;
общий прогресс истории счёта MUST NOT подменять полноту экрана. Отсутствующие
данные SHALL иметь состояние первичной загрузки; доступные данные SHALL
оставаться видимыми во время обновления. Неполные totals/comparisons MUST
NOT показываться как окончательные или как ноль; неполный пустой журнал
MUST NOT сообщать, что операций нет. Наблюдение запрошенной работы MUST
прекращаться после завершения либо ухода и MUST NOT запускать синхронизацию.
Известные ошибки доступа MUST NOT маскироваться сохранённым результатом.
#### Scenario: Недавний период готов раньше всей истории
- **WHEN** показанный период и его зависимости полны, но старая история ещё загружается
- **THEN** пользователь видит готовый результат периода и отдельное состояние догрузки истории
#### Scenario: Полнота изменилась без новых записей
- **WHEN** источник подтвердил пустой период экрана, а остальная история ещё загружается
- **THEN** наблюдающий клиент получает изменение полноты и показывает подтверждённый пустой результат, не ожидая конца всей истории
#### Scenario: Ответы наблюдения завершились в обратном порядке
- **WHEN** поздний ответ чтения содержит прежние данные или доступ, а клиент уже принял более новое состояние этого ресурса либо отзыв доступа
- **THEN** прежний ответ не откатывает показанные данные и не восстанавливает недоступный личный результат
#### Scenario: Источник временно недоступен
- **WHEN** обновление сохранённого полного результата завершается временной ошибкой
- **THEN** прежние данные и время сохраняются, ошибка видима и доступно ручное повторное обновление; ошибка не обновляет время успешного получения и не считается успешной сверкой
#### Scenario: Неполное сравнение дохода
- **WHEN** выбранный период готов, а необходимый период сравнения ещё не загружен
- **THEN** сравнение обозначается как незавершённое без нулевой базы или окончательной разницы
### Requirement: Быстродействие и сокращение запросов проверяются измерениями
Для обычных чтений заполненной БД система SHALL иметь целевой бюджет
серверного ответа до 500 мс в зафиксированном профиле нагрузки. Выполнение
цели MUST подтверждаться измерениями, а не предполагаться из наличия БД.
Свежие полные зависимости MUST давать ноль новых RPC T-Bank; двадцать
одновременных запросов одного устаревшего набора MUST создавать не более
одной логической загрузки каждой зависимости. Полные старые операции и
завершённые свечи MUST давать ноль повторных RPC для своих интервалов при
обычном чтении, включая чтение после перезапуска.
#### Scenario: Измеряется заполненное хранилище
- **WHEN** проверяется согласованный набор обычных запросов при задержанном или недоступном T-Bank
- **THEN** фиксируются распределение серверных задержек, число RPC и параметры нагрузки; цель 500 мс не объявляется достигнутой без этих результатов
#### Scenario: Повторные открытия не перечитывают историю
- **WHEN** один и тот же полный прошлый период читается повторно без ручного обновления
- **THEN** сохранённые данные выдаются без RPC этого интервала, независимо от числа страниц клиентской выдачи