spec: unify income payment cards and drop period comparisons (WealthLedger/main#33) (#37)
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:
@@ -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.
|
||||
Reference in New Issue
Block a user