spec: unify income payment cards and drop period comparisons (WealthLedger/main#33) (#37)
Specs security / security (push) Successful in 9s
Specs verify / verify (push) Successful in 12s

spec: unify income payment cards and drop period comparisons (WealthLedger/main#33)
This commit was merged in pull request #37.
This commit is contained in:
2026-10-13 18:16:27 +03:00
parent fe295aa73a
commit 6c858a8858
14 changed files with 1014 additions and 0 deletions
@@ -0,0 +1,208 @@
# Engineering Design
## Context and Constraints
Аналитика дохода использует выбранный период, отдельные previous/year диапазоны
и публикует их через DTO и metadata; план синхронизации также запрашивает эти
диапазоны. Frontend строит блоки сравнений из ответа и показывает исполненные
операции через `LedgerEntryRow`. Полный журнал, недавние записи и личная история
бумаги используют этот же ряд; прогноз использует `BrokerageEventCard`;
календарные карточки акции и облигации реализованы внутри соответствующих
страниц. Все уже используют `EntryCard` либо существующие Mantine primitives,
но это не один предметный компонент выплаты.
Граница реализации — утверждённые Delta Specs и
[System Analysis](system-analysis/analysis.md), включая
[целевой контракт](system-analysis/contracts/income-analytics-response.schema.json).
Доступ, деньги, полнота периода, порядок записей и формы остальных API
сохраняются. Клиент получает публичный OpenAPI artifact явно.
## Technical Goals / Non-Goals
- Исключить сравнения из чтения, синхронизации, API и UI без изменения итогов
выбранного периода.
- Один компонент для купона/дивиденда в исполненной операции, прогнозе и
графике бумаги; режим определяется источником, а не знаком или датой.
- Не преобразовывать график в личный доход и не объединять чужие деньги,
прогноз и сумму на бумагу. Не удалять `buckets` или `instruments` из API.
## Implementation Impact Map
### main
N/A: нет изменений workspace, Compose, release orchestration или конфигурации.
### frontend
Affected: страница дохода и её display model, компонент записи операций,
компонент будущего события, календарные карточки акций/облигаций, локальная
копия публичного OpenAPI и генерируемые типы. Владельцы экранов сохраняют
свои списки, фильтры и локальные состояния.
### backend
Affected: расчёт и планирование income периода, чтение операций для
income-analytics, DTO ответа и план фонового получения выбранного интервала.
Источник исполненных операций и агрегация selected остаются владельцами денег.
### qa
N/A: самостоятельного репозитория QA нет; системные проверки описывает
`qa-analysis.md` без введения несуществующей инфраструктуры.
## Decisions
### main
Изменения не требуются: координация ссылками на Issue/Change, без workspace
автоматизации ради одного API перехода.
### frontend
Один предметный `PayoutCard` разместить на уровне `entities`, доступном
страницам и компонентам других slices через public export. Он использует
существующий `EntryCard` как визуальную основу; `EntryCard` не становится
владельцем финансовой семантики. Презентационные данные передаются явным
discriminated union по происхождению `executed | forecast | schedule`:
исполненная запись содержит signed payment и точный timestamp; прогноз —
nullable projected amount, dateKind и дату; график — nullable per-security
сумму, отдельную nullable оценку и доступные даты/параметры выпуска. Ветка
графика показывает дополнительные условия купона и дивиденда без размножения
карточек. У отсутствующих значений текстовый fallback, ноль форматируется
отдельно от неизвестного. Не вводить один универсальный произвольный набор
optional props, допускающий ложную комбинацию «исполнено + оценка».
Владельцы исходных данных адаптируют типизированный ответ непосредственно на
границе использования либо через узкий mapper сущности для повторных callers:
`LedgerEntryRow` передаёт точные купоны/дивиденды в `PayoutCard`, прочие
операции оставляет на прежнем пути; `BrokerageEventCard` делает то же для
coupon/dividend, сохраняя maturity/offer; страницы графиков заменяют только
содержимое календарной карточки. Доход продолжает вызывать `LedgerEntryRow`,
сохраняя `wrap`, HTML-список и occurrence key. Личная история инструмента
сохраняет quantity/price/account details там, где они доступны. Ключи списков
и порядок определяются caller, не самим компонентом.
Удалить comparison-only display model, компонент блока, подписи и стили,
относящиеся только к сравнениям. Не удалять предупреждение неполного
selected или сохранённого snapshot. Обновить текущие fixtures и регрессии
после передачи OpenAPI; генерация типов потребителя берёт только его
локальную копию artifact.
Альтернатива с одинаковым CSS для трёх существующих компонентов отвергнута:
не обеспечивает фактическое переиспользование. Альтернатива с общим DTO
backend отвергнута: источники и денежные смыслы различны, wire-контракт
остальных API не меняется.
### backend
Сократить план income-analytics до selected: сервис запрашивает один диапазон,
не конструирует comparison totals/availability; план экранной синхронизации
не включает предыдущий год и месяц. Сохранить независимый план current-month
summary и серверный выбор точных типов купона/дивиденда. В DTO удалить
`previous`, `yearComparison`, comparison DTO при отсутствии других callers;
`dependencies` сохранить в исходной форме с единственной ролью `selected`.
Текущий shared operations range reader использует индексированные имена
resource keys для трёх периодов; после одного диапазона оставить корректную
идентичность selected и убрать устаревший индексированный список. Сохраняем
status/coverage/freshness и публикацию подтверждённого периода. Дневные/
недельные/месячные buckets остаются, так как их удаление не согласовано.
Не вводить миграцию БД: убираются чтения и поля ответа, а не сохранённые
операции или подтверждённое покрытие. Никакой пересборки прошлого дохода.
## Cross-cutting Implementation
Существующая проверка пользователя/счёта и очистка личного query cache при
смене подключения остаются на местах. Изменение количества планируемых
диапазонов не добавляет work и не меняет поведение hourly attempt для других
экранов. Существующие request ID, metadata попытки и статусы дают достаточную
диагностику; новых событий или полей логирования не требуется. Пересчёта
денег, валютной конвертации и нового кэша нет.
## Data / Storage Changes
N/A: Prisma/SQLite schema и исторические записи не меняются; выбранное
покрытие продолжает подтверждаться до публикации окончательного результата.
## Contract Implementation
Backend обновляет DTO и исполняемый generated OpenAPI. Для обоих
income-analytics маршрутов проверить отсутствие `previous`/`yearComparison`
и наличие только `selected` в dependency role при сохранении nullable `data`,
остальных полей и Problem Details. Сверить обе схемы 200 с
[target JSON Schema](system-analysis/contracts/income-analytics-response.schema.json)
семантически, а не только с TypeScript shape; существующую проверку parity
runtime OpenAPI также сохранить. Передать созданный backend artifact frontend
явно; frontend обновляет snapshot и generated types штатной локальной
процедурой, без чтения соседнего checkout в сборке. Прежний клиент,
требующий удалённых полей, несовместим.
## Developer Verification Strategy
Backend: адресные проверки планирования только выбранного интервала, чтения
income summary/analytics, точных signed сумм и недоступного selected;
проверка HTTP контракта и OpenAPI parity. Frontend: наблюдаемое отсутствие
блоков сравнений в loaded/loading/error/empty; сохранение period URL,
порядка/повторов выплат и корректные режимы общего компонента на всех
потребителях. После локальных узких проверок выполнить обязательный verify
каждого изменённого implementation repo. Это developer-owned проверки;
системная матрица отдельно в `qa-analysis.md`.
## UI/UX Package
**Applicability and scope:** changed UI; пакет полностью inline, новых
изображений и assets не требуется. Один approval Design относится к этой
ревизии и приведённому ниже точному источнику переиспользования.
**Package revision:** `1`, change `unify-income-payment-cards`.
**Screens, states and interactions:** доход — фильтр, итоги, плоский список
исполненных выплат, без сравнений; прогноз на странице событий/превью —
пометка «Прогноз», неизвестная сумма отлична от нуля, дата реестра помечена;
графики акции/облигации — «Событие по графику», разные подписи per-share/
per-bond и личной оценки. В истории операций карточка сохраняет описание,
дату и доступную identity инструмента, signed Money и детали там, где были.
Loaded, empty, loading, error и обновление старого snapshot сохраняют текущие
локальные границы экранов. Оферта, погашение, сделка и налог не получают
режим выплаты. Компонент сам не переключает состояние и не инициирует запрос.
**Responsive behavior:** однотактный проход по карточкам без горизонтального
overflow при узкой ширине и длинных значениях; labels, значения и статусы
читаются текстом и клавиатурой, цвет остаётся дополнительным признаком.
Сохраняются доступные имена региона и фокус элементов фильтра.
**Reused source and differences:** immutable locator
`wealth-ledger-specs:openspec/changes/archive/2026-09-09-simplify-income-page/design.md`
(секция «Одна карточка соответствует одной выплате») — плоский income список,
одна карточка на запись, signed Money и сохранение порядка. Дополнить
только единым предметным presentation для прогнозов/графиков с явными
подписями происхождения; не переносить прогноз внутрь income страницы.
Визуальная основа реализации — существующий `EntryCard`; его геометрия
остаётся общим стилем, а не заменой `PayoutCard`. Пакет целиком inline.
## Risks / Trade-offs
- Разные даты и null-поля трёх источников провоцируют неверные labels при
слишком широком mapper: явные варианты данных и локальные проверки экранов
важнее экономии нескольких props.
- Для календаря бумаги нет логотипа/тикера в каждой записи: владелец страницы
передаёт известную identity, не создавая новый запрос.
- Меньше загружаемых диапазонов снижает объём работы, но отсутствие сравнения
может изменить порядок фонового покрытия старых периодов; цель сохранения
другой независимой истории остаётся прежней.
## Migration / Rollout / Rollback
Сначала обновить backend DTO/логику и получить опубликованный OpenAPI, затем
передать artifact frontend и обновить UI/типизацию; согласованно поставить
две стороны breaking ответа. Не выпускать старый frontend против нового
backend как поддерживаемую комбинацию. При rollback вернуть согласованную
предыдущую пару backend/frontend и прежний OpenAPI snapshot; сохранённые
операции и покрытие не требуют data rollback. Синхронизация canonical specs
и AS-IS docs выполняется по отдельным approved tasks после реализации.
## Solution Assessment
N/A: не создаётся новая инфраструктура, интеграция, storage, кэш или система
аутентификации. Существующие `EntryCard`, owner-slices и DTO сохраняются;
сопоставление альтернатив общего UI дано в решении frontend.