FinGuide / «Финансовый капитал» — контракт бэкенд ↔ фронтенд¶
Источники анализа:
- Сайт Figma
https://smooth-try-70453479.figma.site/; пакет Figma Make разобран статически. - Excel-файл
Модель_P---56630d2a-6465-4036-bd42-9117c7dc9bd6.xlsx— расчётная модель FinGuide; подробный разбор:docs/model-analytics.md.
В прототипе все данные живут в localStorage; для боевого продукта источником истины должен быть бэкенд.
Экраны из макета¶
/Дашборд: доходы/расходы за год, чистый баланс, норма сбережений, цели, пенсионный капитал, прогноз и рекомендации./foundationОбщие данные: профиль, возраст, валюта, стартовый капитал, доходность/инфляция./incomeCRUD источников дохода./expensesCRUD категорий расходов./goalsфинансовые цели + приоритет waterfall-распределения./goal-trackingфактические взносы к целям./pensionпенсионный расчёт и стратегииpreserve/spend./summaryсводный отчёт./analyticsграфики, оценка финансового здоровья, план/факт, тренды, капитал./calendarежемесячный трекер накоплений./budget50/30/20 и конверты бюджета./scenarios/compareсценарии и сравнение./settings,/profile,/faq.
Базовые правила API¶
- Базовый URL внутри сервиса:
/api/v1. - Публичный URL реального бэкенда на текущем сервере:
http://66.42.121.18/finguide-api/api/v1. GET /api/v1возвращает JSON-индекс сервиса со ссылками на Swagger/OpenAPI и основные endpoints.- Swagger UI реального бэкенда:
http://66.42.121.18/finguide-api/swagger-ui.html. - Legacy mock Swagger остаётся только для переходного сравнения:
http://66.42.121.18/finguide-mock/.
Текущая real-реализация на H2 уже покрывает чтение плана/дашборда/health/cashflow/scenarios и CRUD для IncomeSource, ExpenseItem, Goal, включая goals/reorder. Остальные группы методов из карты ниже пока ведутся отдельными задачами.
- Авторизация: Authorization: Bearer <JWT> с access token из Keycloak realm finguide. Бэкенд валидирует JWT как OAuth2 Resource Server и не владеет password-based /auth/register/login/refresh/logout endpoints. В demo/H2 режиме (FINGUIDE_DEMO_MODE=true) /api/v1/** временно открыт для интеграции фронтенда с real backend без Keycloak.
- Все даты: ISO-8601 (YYYY-MM-DD, date-time UTC).
- Деньги: число в валюте записи + currency; агрегаты возвращаются в базовой валюте плана.
- Ответы: { "data": ... }; ошибки: { "error": { "code", "message", "details", "requestId" } }.
- Все запросы на запись возвращают актуальный ресурс; фронтенд может делать оптимистичное обновление, но затем обязан принять ответ бэкенда.
- Расчёты (dashboard, analytics, pension, budget spent) делает бэкенд, чтобы фронтенд не дублировал финансовую логику.
- Денежные входные суммы для расходов и целей передаются положительными числами; бэкенд сам нормализует знак исходящих платежей. В рассчитанных ответах расходы и цели тоже положительные, а netSavings = income - expenses - goalExpenses.
- Поля *Pct в API — процентные пункты (6 означает 6%). Внутри расчётов бэкенд переводит их в десятичную ставку (0.06).
Общие конвенции API¶
Пагинация¶
Все методы API со списками, которые могут вырасти неограниченно (contributions, notifications, monthly-tracker, export jobs), используют курсорную пагинацию:
limitпо умолчанию50, максимум200.cursor— непрозрачная строка, выдаваемая сервером; клиент не интерпретирует её.- Ответ:
Короткие справочные коллекции (incomes, expenses, goals, envelopes, пользовательские scenarios) возвращаются целиком без пагинации — они ограничены по бизнес-смыслу, например до 10 сценариев.
Идемпотентность запросов на запись¶
Все небезопасные POST-запросы, повторное выполнение которых создаёт дубликаты, поддерживают заголовок:
Применимо к:
POST /plans/{planId}/contributions;POST /plans/{planId}/incomes,POST /plans/{planId}/expenses,POST /plans/{planId}/goals;POST /import,POST /export;POST /scenarios,POST /scenarios/compare.
Бэкенд хранит (user_id, idempotency_key) минимум 24 часа. Повтор с тем же ключом и тем же телом возвращает ранее сохранённый ответ; повтор с другим телом — 409 CONFLICT.
PATCH/PUT/DELETE идемпотентны по семантике HTTP и не требуют заголовка Idempotency-Key.
Оптимистическая конкуренция¶
Сущности, редактируемые из нескольких вкладок/устройств (Goal, IncomeSource, ExpenseItem, PensionSettings, BudgetSettings, UserProfile), включают поле version: int в теле ответа и заголовок ETag.
Клиент при PATCH/PUT присылает:
Если версия в БД новее — бэкенд отвечает 412 PRECONDITION_FAILED с актуальным состоянием в error.details.current. Фронтенд обязан показать конфликт пользователю и не перезаписывать изменения молча.
Contribution не требует версионирования — это журнал только на добавление.
Коды ошибок¶
Стандартный формат ошибки:
{
"error": {
"code": "PLAN_NOT_FOUND",
"message": "Plan not found or access denied",
"details": { ... },
"requestId": "01HF..."
}
}
Перечень code расширяется, но существующие коды не переименовываются:
| code | HTTP | Когда |
|---|---|---|
VALIDATION_FAILED |
400 | Невалидное тело запроса; details.fields[] содержит ошибки по полям |
UNAUTHENTICATED |
401 | Нет JWT или JWT невалиден |
FORBIDDEN |
403 | JWT валиден, но нет доступа к ресурсу |
PLAN_NOT_FOUND |
404 | План не найден или скрыт по правам |
RESOURCE_NOT_FOUND |
404 | Общий 404 для goal/income/expense/contribution |
CONFLICT |
409 | Бизнес-конфликт, например несовпадение идемпотентного запроса или дубликат |
PRECONDITION_FAILED |
412 | If-Match не совпал с текущей версией |
RATE_LIMITED |
429 | Превышен лимит; details.retryAfterSec |
INTERNAL |
500 | Непредвиденная ошибка; всегда логируется с requestId |
DEPENDENCY_UNAVAILABLE |
503 | Keycloak / БД / внешний сервис недоступны |
Фронтенд маппит code на UX: уведомление, диалог конфликта, перенаправление на экран входа. На message завязываться нельзя — текст может локализоваться.
Главные сущности¶
Профиль пользователя (UserProfile)¶
id, name, email, phone, avatarUrl, age, gender, initialBalance, createdAt, updatedAt.
Доходы и расходы (IncomeSource / ExpenseItem)¶
id, name, amount, currency, frequency: monthly|yearly|one_time, growthType: manual|inflation|none|schedule, growthPct, growthSchedule[], startDate, endDate, опционально startYear, endYear.
Для POST id и timestamps генерирует сервер; PATCH принимает частичное тело и меняет только переданные поля. В текущей H2-реализации null в nullable-полях (endDate, growthLabel, icon, indexLabel) трактуется как «оставить текущее значение», поэтому очистка таких полей будет оформлена отдельной merge-patch/replace семантикой позже. Валидация: суммы неотрицательные, growthPct от 0 до 100, endDate >= startDate, currency — ISO-4217 alpha-3.
Для расходов дополнительно: budgetClass: needs|wants|savings, growthLabel.
growthSchedule[] нужен для Excel-модели: по каждой строке дохода/расхода могут быть разные ставки роста по годам, а не один постоянный процент.
Цель (Goal)¶
id, name, icon, currentCost, savedAmount, currency, targetYear, type: one_time|recurring, growthType, growthPct, growthSchedule[], priority. targetYear должен быть не меньше 2024.
Логика waterfall-распределения: ближайшая или приоритетная цель получает свободные накопления первой; порядок сохраняется через /goals/reorder.
Для Excel-модели Goal также должен поддерживать плановые расходы на цели: plannedAmount, frequency, startDate/endDate или startYear/endYear. Это покрывает лист Цели: ежемесячные и ежегодные расходы на цели как отдельный денежный поток.
Взнос (Contribution)¶
id, goalId, amount, currency, date, note.
Бэкенд либо хранит Goal.savedAmount денормализованно, либо пересчитывает его из записей взносов и возвращает в Goal.
Пенсионные настройки (PensionSettings)¶
currentAge, retirementAge, monthlyExpenses, desiredMonthlyExpensesCurrentPrices, currency, expectedReturnPct, inflationPct, withdrawalStrategy: preserve_capital|spend_down_30y, statePensionEnabled, statePensionMonthly.
Excel-модель требует два пенсионных расчёта:
preserve_capital: тратить только реальную доходность от капитала;spend_down: тратить желаемый уровень расходов и считать возраст исчерпания капитала.
Предположения модели (ModelAssumptions)¶
startYear, projectionEndYear или horizonYears, birthYear, monthsPerYear, currency, initialCapital, investmentReturnPct, inflationSchedule[], sourceModel.
Это слой, который переносит лист Вводные в бэкенд и делает расчёты воспроизводимыми без Excel.
Настройки бюджета (BudgetSettings)¶
method: 503020|envelope, envelopes[], classifications{expenseId: needs|wants|savings}.
Конверт бюджета: id, name, limit, icon, color, рассчитанные spent, remaining, pct, isOver.
Сценарий (Scenario)¶
id, name, emoji, description, isBase, snapshot или adjustments.
Стандартные сценарии из макета: базовый, оптимистичный, пессимистичный. Пользовательские сценарии — до 10 штук.
Карта методов API¶
Авторизация и профиль¶
Keycloak владеет входом, регистрацией, refresh/logout, восстановлением пароля, MFA и пользовательскими сессиями. Эти endpoints доступны под публичным route /auth/realms/finguide/protocol/openid-connect/... и не входят в FinGuide API contract. Frontend использует Authorization Code + PKCE и передаёт access token в API.
GET /me— реализовано в real backend; возвращает локальный бизнес-профиль, связанный сJWT.sub, с синхронизациейemail/ФИО из JWT.PATCH /me,PUT/DELETE /me/avatar— отдельные задачи профиля.- Смена пароля — вне зоны ответственности backend; пароль меняется через Keycloak account/password reset screens.
План и CRUD¶
GET/PUT /plans/current(GETреализован;PUTотдельная задача). Для anonymous demo возвращает seeded plan22222222-2222-4222-8222-222222222222; для authenticated пользователя первыйGETтранзакционно и идемпотентно создаёт его собственный current plan, клонируя persisted demo seed, и дальше возвращает только пользовательский план.GET/POST /plans/{planId}/incomes,GET/PATCH/DELETE /plans/{planId}/incomes/{id}— реализовано в real backend.GET/POST /plans/{planId}/expenses,GET/PATCH/DELETE /plans/{planId}/expenses/{id}— реализовано в real backend.GET/POST /plans/{planId}/goals,GET/PATCH/DELETE /plans/{planId}/goals/{id}— реализовано в real backend.POST /plans/{planId}/goals/reorder— реализовано в real backend; тело{ "goalIds": ["..."] }должно содержать все текущие id целей ровно по одному разу.GET/POST /plans/{planId}/contributions,GET/PATCH/DELETE /plans/{planId}/contributions/{id}GET/PATCH /plans/{planId}/pensionGET/PATCH /plans/{planId}/budget,POST /plans/{planId}/budget/envelopes/autogenerate
Аналитика и производные данные¶
GET /plans/{planId}/dashboardGET /plans/{planId}/analytics/projection?years=30GET/PATCH /plans/{planId}/analytics/assumptionsGET /plans/{planId}/analytics/balance/currentGET /plans/{planId}/analytics/cashflow?startYear=2024&endYear=2076GET /plans/{planId}/analytics/healthGET/POST /plans/{planId}/calendar/monthly-trackerGET /plans/{planId}/pension/projection
analytics/cashflow — главный метод API в стиле Excel-модели. Он возвращает годовые строки: возраст, номер периода, доходы, расходы, расходы на цели, чистые сбережения, капитал на начало/конец года.
Сценарии¶
GET/POST /scenariosGET/PATCH/DELETE /scenarios/{scenarioId}POST /scenarios/compare
Уведомления, импорт и экспорт¶
GET /notifications?filter=all|unreadPOST /notifications/readPOST /importPOST /export,GET /export/{jobId}
Интеграция фронтенда¶
- Если включён OIDC: пройти
/login→ Keycloak Authorization Code + PKCE →/auth/callback, сохранить access token и подставлятьAuthorization. Затем:GET /me+GET /plans/current+GET /plans/{id}/dashboard. Frontend должен разделять cache для anonymous demo и authenticated user session и показывать neutral loader до завершения восстановления токена/current plan, чтобы не мигал seeded demo/default профиль. - CRUD-экраны используют локальное оптимистичное состояние, но инвалидируют
dashboard,analytics,budgetпосле записи. - Дашборд и графики не считают деньги сами — только отображают расчётные методы API.
- Для миграции из прототипа можно один раз прочитать ключи localStorage
finguide-data,finguide-user-profile,finguide-budgets,finguide-scenariosи отправитьPUT /plans/current/POST /import. - Импорт/экспорт должен поддерживать JSON для полного плана,
excel_modelдля исходной модели и CSV/XLSX/PDF для отчётов.