spec: unify income payment cards and drop period comparisons (WealthLedger/main#33)
16 KiB
Engineering Design
Context and Constraints
Аналитика дохода использует выбранный период, отдельные previous/year диапазоны
и публикует их через DTO и metadata; план синхронизации также запрашивает эти
диапазоны. Frontend строит блоки сравнений из ответа и показывает исполненные
операции через LedgerEntryRow. Полный журнал, недавние записи и личная история
бумаги используют этот же ряд; прогноз использует BrokerageEventCard;
календарные карточки акции и облигации реализованы внутри соответствующих
страниц. Все уже используют EntryCard либо существующие Mantine primitives,
но это не один предметный компонент выплаты.
Граница реализации — утверждённые Delta Specs и System Analysis, включая целевой контракт. Доступ, деньги, полнота периода, порядок записей и формы остальных 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
семантически, а не только с 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.