@marcos_feitoza/personal-finance-backend-trades-assets
v1.12.0
Published
Main API gateway and orchestration service for the Personal Finance backend.
Downloads
186
Readme
Backend Core - Personal Finance
Main API gateway and orchestration service for the Personal Finance backend.
Purpose
backend-core is the entry point used by frontend clients. It centralizes authentication, business routing, and internal service proxying.
For AI capabilities, it is the official boundary in front of ai-insights.
AI Integration (Updated)
backend-core proxies all AI endpoints and forwards:
Authorization: Bearer <token>X-Correlation-ID
AI proxy endpoints currently supported:
GET /api/ai/healthGET /api/ai/insightsPOST /api/ai/chatPOST /api/ai/feedbackPOST /api/ai/simulatePOST /api/ai/plan/monthlyGET /api/ai/observability/summaryGET /api/ai/usage/summaryPOST /api/ai/notifications/generateGET /api/ai/notificationsGET /api/ai/notifications/unread-countGET /api/ai/notifications/schedulePOST /api/ai/notifications/{notification_id}/readPOST /api/ai/notifications/read-allPOST /api/ai/advisor/report/monthly-deliver(proxy to advisor orchestrator)POST /api/ai/advisor/simulate(proxy to advisor orchestrator)POST /api/ai/advisor/feedback(proxy to advisor orchestrator)GET /api/ai/advisor/health(proxy to advisor orchestrator)POST /api/ai/advisor/chat(proxy to advisor orchestrator)POST /api/ai/advisor/report(proxy to advisor orchestrator)POST /api/ai/advisor/plan(proxy to advisor orchestrator)POST /api/ai/advisor/suggestions/click(tracks suggested prompt click analytics)GET /api/ai/advisor/observability/summary(proxy to advisor orchestrator)POST /api/ai/advisor/notifications/generate(proxy to advisor orchestrator)GET /api/ai/advisor/notifications(proxy to advisor orchestrator)
Market/Crypto proxy endpoints currently supported:
GET /api/core-market-data/stocksGET /api/core-market-data/searchGET /api/core-crypto/pricesGET /api/core-crypto/validate/{query}POST /api/core-crypto/log
Responsabilidades Principais
Autenticação de Usuários: Gerencia registro (
/register) e login (/token) de usuários.authentication (register/login/refresh/logout)
Orquestração de Transações: Lida com a criação, leitura e exclusão de transações financeiras, contribuições RRSP e dividendos.
orchestration for transactions/investments/metadata
Orquestração de Trades e Ativos: Utiliza a biblioteca
trades-assetspara gerenciar a compra e venda de ativos.integration with balance/market/ai services
consistent API boundary for frontend
Orquestração de Balanços: Expõe endpoints que se comunicam com o
balance-servicepara obter os saldos das contas.Gerenciamento de Investimentos: Processa as movimentações de dinheiro entre contas (ex: de CASH para TFSA).
Tecnologias
- Framework: FastAPI
- Banco de Dados: PostgreSQL (acessado via
personal-finance-backend-shared) - Dependências Locais:
personal-finance-backend-shared: Para acesso aos modelos,crude utilitários.personal-finance-backend-trades-assets: Para a lógica de trades e ativos.
- Deploy: Docker & Kubernetes (via Helm)
Important Environment Variables
JWT_SECRET_KEYBALANCE_SERVICE_URLMARKET_DATA_SERVICE_URLMARKET_DATA_CRYPTO_SERVICE_URLAI_INSIGHTS_SERVICE_URLAI_ADVISOR_ORCHESTRATOR_SERVICE_URLAI_ADVISOR_HTTP_TIMEOUT_SECONDSAI_ADVISOR_HTTP_RETRY_ATTEMPTSAI_ADVISOR_ROLLOUT_ENABLEDAI_ADVISOR_ROLLOUT_ALLOWED_PLANSAI_ADVISOR_ROLLOUT_PERCENTAI_ADVISOR_ROLLOUT_ALLOW_USER_IDSAI_ADVISOR_ROLLOUT_DENY_USER_IDSAI_ADVISOR_ROLLOUT_CHAT_FALLBACK_ENABLEDAI_ADVISOR_EXPOSE_ROUTING_METADATAAI_ADVISOR_LIMIT_FREE_DAILYAI_ADVISOR_LIMIT_FREE_MONTHLYAI_ADVISOR_LIMIT_PLUS_DAILYAI_ADVISOR_LIMIT_PLUS_MONTHLYAI_ADVISOR_LIMIT_PRO_DAILYAI_ADVISOR_LIMIT_PRO_MONTHLYAI_ADVISOR_BUDGET_FREE_MONTHLY_USDAI_ADVISOR_BUDGET_PLUS_MONTHLY_USDAI_ADVISOR_BUDGET_PRO_MONTHLY_USDAI_ADVISOR_COST_POLICY_ACTION
AI Advisor Rollout Variables (usabilidade)
As variáveis abaixo controlam o rollout de /api/ai/advisor/* no backend-core:
AI_ADVISOR_ROLLOUT_ENABLED:true: rollout ativofalse: desliga advisor para todos (exceto allowlist)
AI_ADVISOR_ROLLOUT_ALLOWED_PLANS:- lista CSV de planos elegíveis (
free,plus,pro) - exemplo:
plus,pro
- lista CSV de planos elegíveis (
AI_ADVISOR_ROLLOUT_PERCENT:- percentual de coorte por
user_id % 100 - exemplo:
10ativa ~10% dos usuários elegíveis
- percentual de coorte por
AI_ADVISOR_ROLLOUT_ALLOW_USER_IDS:- CSV de users sempre habilitados (bypass de plano/coorte)
- exemplo:
2,15,99
AI_ADVISOR_ROLLOUT_DENY_USER_IDS:- CSV de users sempre bloqueados (precedência mais alta)
- exemplo:
7,8
AI_ADVISOR_ROLLOUT_CHAT_FALLBACK_ENABLED:true: quando usuário não elegível no advisor chat, faz fallback para/api/ai/chatlegadofalse: retorna403 advisor_rollout_not_enabled
AI_ADVISOR_EXPOSE_ROUTING_METADATA:false(recomendado): não expõe metadados internos de roteamento/rollout no payload para usuário comumtrue: expõe metadados (rollout,advisor_routed_to) para todos os usuários autenticados
- Limites por plano (chat advisor):
AI_ADVISOR_LIMIT_FREE_DAILY/AI_ADVISOR_LIMIT_FREE_MONTHLYAI_ADVISOR_LIMIT_PLUS_DAILY/AI_ADVISOR_LIMIT_PLUS_MONTHLYAI_ADVISOR_LIMIT_PRO_DAILY/AI_ADVISOR_LIMIT_PRO_MONTHLY- valor
-1pode ser usado para desativar um limite específico
- Orçamento de custo por plano (USD/mês, chat advisor):
AI_ADVISOR_BUDGET_FREE_MONTHLY_USDAI_ADVISOR_BUDGET_PLUS_MONTHLY_USDAI_ADVISOR_BUDGET_PRO_MONTHLY_USD- valor
< 0desativa orçamento para o plano AI_ADVISOR_COST_POLICY_ACTION:block: retorna429 advisor_cost_budget_reachedao exceder orçamentowarn_only: não bloqueia (apenas registra evento de orçamento excedido)
Precedência do gate:
DENY_USER_IDSALLOW_USER_IDSAI_ADVISOR_ROLLOUT_ENABLEDAI_ADVISOR_ROLLOUT_ALLOWED_PLANSAI_ADVISOR_ROLLOUT_PERCENT(bucket)
Comportamento no response de /api/ai/advisor/chat:
- Por padrão (
AI_ADVISOR_EXPOSE_ROUTING_METADATA=false), payload para usuário comum não inclui metadados internos de rollout/routing. - Admin/support_admin ainda recebem metadados internos para troubleshooting.
- Com
AI_ADVISOR_EXPOSE_ROUTING_METADATA=true, qualquer usuário autenticado recebeadvisor_routed_toerollout.
Comportamento de limite:
- quando o usuário atinge limite diário ou mensal,
/api/ai/advisor/chatretorna429comcode=advisor_usage_limit_reached - o evento é registrado em
ai_usage_eventscomendpoint=ai.advisor.chateerror_type=advisor_usage_limit_reached
Comportamento de orçamento (USD):
- quando o custo mensal acumulado atinge o orçamento do plano:
- com
AI_ADVISOR_COST_POLICY_ACTION=block:/api/ai/advisor/chatretorna429comcode=advisor_cost_budget_reached - com
AI_ADVISOR_COST_POLICY_ACTION=warn_only: não bloqueia request, mas registra evento operacional
- com
- o evento é registrado em
ai_usage_eventscomerror_type=advisor_cost_budget_reached
Metering LLM (advisor):
- o proxy persiste em
ai_usage_eventsos campos de metering recebidos do orchestrator:providermodelprompt_tokenscompletion_tokenstotal_tokenscost_estimate
Admin observability (advisor):
/api/admin/ai-observability/summaryagora separa:total_errors(HTTP >= 400, inclui bloqueios de política)total_policy_blocks(advisor_usage_limit_reached,advisor_cost_budget_reached,advisor_rollout_not_enabled)total_technical_errors(5xx e falhas técnicas de proxy/upstream)technical_error_rate_percent(indicador principal para qualidade operacional)
- o payload também inclui agregação por
mode(by_mode) comp95_latency_ms.
prod-var.yaml
- name: AI_ADVISOR_LIMIT_FREE_DAILY
value: "20"
- name: AI_ADVISOR_LIMIT_FREE_MONTHLY
value: "300"
- name: AI_ADVISOR_LIMIT_PLUS_DAILY
value: "100"
- name: AI_ADVISOR_LIMIT_PLUS_MONTHLY
value: "3000"
- name: AI_ADVISOR_LIMIT_PRO_DAILY
value: "300"
- name: AI_ADVISOR_LIMIT_PRO_MONTHLY
value: "10000"
- name: AI_ADVISOR_BUDGET_FREE_MONTHLY_USD
value: "0.50"
- name: AI_ADVISOR_BUDGET_PLUS_MONTHLY_USD
value: "5.00"
- name: AI_ADVISOR_BUDGET_PRO_MONTHLY_USD
value: "20.00"
- name: AI_ADVISOR_COST_POLICY_ACTION
value: "block"Valores padrão (prod)
free: 20/dia, 300/mês plus: 100/dia, 3000/mês pro: 300/dia, 10000/mês
20/dia e 300/mês = quantidade de requests ao endpoint /api/ai/advisor/chat por usuário.
free: 20/dia Cada usuário free pode fazer até 20 perguntas por dia no AI Advisor chat.
free: 300/mês Cada usuário free pode fazer até 300 perguntas por mês no AI Advisor chat.
Quando bate qualquer um dos dois limites, a API retorna 429 advisor_usage_limit_reached.
-- Listar usuários com plano
SELECT id, email, role, plan, account_status, created_at
FROM users
ORDER BY id;
-- Ver 1 usuário específico
SELECT id, email, role, plan, account_status
FROM users
WHERE id = 2;
-- Alterar plano de um usuário (exemplo: user_id=2 para pro)
UPDATE users
SET plan = 'pro'
WHERE id = 2;
-- Conferir se alterou
SELECT id, email, plan
FROM users
WHERE id = 2;
Exemplos de configuração:
Full rollout:
AI_ADVISOR_ROLLOUT_ALLOWED_PLANS=free,plus,proAI_ADVISOR_ROLLOUT_PERCENT=100
Rollout seguro inicial:
AI_ADVISOR_ROLLOUT_ALLOWED_PLANS=plus,proAI_ADVISOR_ROLLOUT_PERCENT=10AI_ADVISOR_ROLLOUT_CHAT_FALLBACK_ENABLED=true
Local Run
pip install -r personal-finance-backend-core/requirements.txt
uvicorn personal-finance-backend-core.app.main:app --reload --app-dir .Deployment
- container build via
dockerbuild.shor monorepopodman-build.sh - Helm deployment managed by Argo CD
Notes
- Frontend should call AI endpoints only through
backend-core, not directly toai-insights. - This keeps auth, tracing, and future policy controls centralized.
- Frontend should call market/crypto through
backend-core(/api/core-market-data/*,/api/core-crypto/*) instead of direct service exposure. - AI Advisor scorecard oficial (metas/go-no-go/revisão semanal):
personal-finance-backend-ai-advisor-orchestrator/AI-Scorecard.md.
Autenticação (JWT)
Visão geral
O serviço backend-core implementa autenticação via JWT (access token) e protege a maioria dos endpoints com o header:
Authorization: Bearer <access_token>Endpoints
Login:
POST /api/auth/token- Envia credenciais no formato
application/x-www-form-urlencoded:username=<email>password=<senha>
- Retorna:
{"access_token": "...", "refresh_token": "...", "token_type": "bearer"}
- Envia credenciais no formato
Refresh:
POST /api/auth/refresh- Body:
{"refresh_token": "..."} - Retorna um novo par (rotaciona o refresh token):
{"access_token": "...", "refresh_token": "...", "token_type": "bearer"}
- Body:
Logout:
POST /api/auth/logout- Body:
{"refresh_token": "..."} - Revoga o refresh token (logout idempotente)
- Body:
Registro:
POST /api/auth/register
Claims atuais do token
No estado atual do projeto, o access token inclui:
sub: email do usuáriouser_id: id do usuáriorole: role do usuário (ex.:user,admin)plan: plano do usuário (ex.:free,plus,pro)iat: timestamp de emissãojti: id único do tokeniss: issuer (quandoJWT_ISSUERestiver configurada)aud: audience (quandoJWT_AUDIENCEestiver configurada)exp: expiração do token
Expiração
O access token expira em 30 minutos (ver ACCESS_TOKEN_EXPIRE_MINUTES em personal-finance-backend-shared/personal_finance_shared/auth_utils.py).
O refresh token expira por padrão em 30 dias (ver REFRESH_TOKEN_EXPIRE_DAYS em personal-finance-backend-shared/personal_finance_shared/auth_utils.py).
Variáveis de ambiente
Obrigatórias:
JWT_SECRET_KEY: segredo do JWT (HS256)
Reservadas para hardening futuro (Fase 0/1 do plano de JWT):
JWT_ISSUER: issuer (ex.:personal-finance-backend-core)JWT_AUDIENCE: audience (ex.:personal-finance-frontend)JWT_REFRESH_SECRET_KEY: reservado para implementação de refresh token
Obrigatória para refresh token (Fase 3):
JWT_REFRESH_SECRET_KEY: segredo usado para assinar/validar refresh tokens.
Opcional:
REFRESH_TOKEN_EXPIRE_DAYS: dias de expiração do refresh token (default 30).
Opcional (recomendado em produção):
JWT_STRICT_ISS_AUD: setrue, rejeita tokens semiss/aud(tokens legados)
Estrutura da API
Este serviço expõe e orquestra os seguintes grupos de endpoints:
/api/auth: Autenticação de usuários./api/transactions: Operações de crédito e débito./api/investments: Movimentações entre contas./api/accounts: Obtenção de balanços de contas./api/dividends: Gerenciamento de dividendos./api/rrsp-contributions: Gerenciamento de contribuições RRSP./api/trades: Operações de compra e venda de ativos (da bibliotecatrades-assets)./api/assets: Gerenciamento de ativos (da bibliotecatrades-assets)./api/ai: Proxy para o serviço de insights de IA (/health,/insights).
Planos e Controle de Acesso
O backend-core agora separa:
rolepara autorização administrativa (user/support_admin/admin)planpara monetização (free/plus/pro)
Comportamento atual:
- usuários novos são criados com
plan=free - endpoints de investimentos são bloqueados para
free(mínimoplus) - endpoints administrativos concentram operação de usuários, segurança, créditos, auditoria e observability
Admin endpoints relevantes:
GET /api/admin/users(retornaid,email,role,plan, status e flags de segurança)PUT /api/admin/users/{user_id}/plan?plan=free|plus|proPUT /api/admin/users/{user_id}/role?role=user|support_admin|adminPUT /api/admin/users/{user_id}/status?status=active|suspended&reason=...POST /api/admin/users/{user_id}/security/revoke-sessionsPUT /api/admin/users/{user_id}/security/force-password-change?enabled=true|false&revoke_sessions=true|falsePOST /api/admin/users/{user_id}/security/reset-mfa(placeholder)GET /api/admin/users/{user_id}/ai-creditsGET /api/admin/users/{user_id}/ai-credits/ledger?limit=50POST /api/admin/users/{user_id}/ai-credits/adjust?delta=10&reason=manual_topupPOST /api/admin/users/{user_id}/ai-credits/promo-grant?...POST /api/admin/ai-credits/topup-monthly?period=YYYY-MMGET /api/admin/audit-logs?...GET /api/admin/ai-observability/summary?hours=24&user_id=<optional>
Quando bloqueado por plano, a API retorna 403 com:
code: "plan_upgrade_required"current_planrequired_plan
Créditos de Overage (base preparada)
Para suportar overage de AI, o core expõe endpoints administrativos de crédito:
- consultar saldo de créditos AI por usuário
- listar ledger de movimentações de crédito
- ajustar crédito manualmente (
deltapositivo/negativo)
Essa base prepara o próximo passo de cobrança: consumo automático de créditos quando a quota mensal do plano for excedida.
Metadata (categorias / subcategorias / payment methods)
Para iniciar a migração de listas estáticas para dinâmicas no frontend (dropdowns), o backend-core expõe endpoints read-only:
GET /api/metadata/categoriesGET /api/metadata/payment-methods
Estratégia (dropdowns limpos)
- Dropdowns retornam apenas:
- categorias/subcategorias custom do usuário (persistidas)
- categorias/subcategorias aprendidas das transações do usuário (distinct)
O template demo não aparece automaticamente nos dropdowns. Ele pode ser visto via endpoint read-only e importado para a lista do usuário.
Contratos (respostas)
GET /api/metadata/categories
{
"categories": {
"MOTO": ["roda"],
"LEISURE": ["Bar"]
}
}GET /api/metadata/demo-categories
{
"categories": {
"HOUSE": ["Rent", "Net"],
"LEISURE": ["Bar"]
}
}GET /api/metadata/categories
{
"payment_methods": ["CASH", "RBC", "BMO"]
}CRUD (Fase 1)
CRUD de categorias/subcategorias por usuário (persistência dedicada).
Metadata (categorias/subcategorias) — CRUD (Fase 1)
Para suportar personalização pelo usuário (via tela de Settings/Profile no frontend), foram adicionados endpoints CRUD.
Migration
- Migration Alembic:
1f2a3b4c5d6e_add_user_categories_and_subcategories.py - Tabelas:
user_categoriesuser_subcategories
Regras
- Categoria case-insensitive: normalizada em uppercase e com unicidade por usuário.
- Subcategoria case-insensitive dentro da categoria: normalizada (lowercase) e com unicidade por categoria.
- A mesma subcategoria pode existir em categorias diferentes.
Endpoints
Além dos read-only:
GET /api/metadata/categories(custom + learned from transactions)GET /api/metadata/payment-methodsGET /api/metadata/demo-categories(template demo, read-only)
CRUD:
GET /api/metadata/user-categories— retorna apenas categorias/subcategorias criadas pelo usuário (sem defaults)POST /api/metadata/categories— cria categoria- body:
{ "name": "HOUSE" }
- body:
DELETE /api/metadata/categories/{category_name}— apaga categoria (cascade nas subcategorias)POST /api/metadata/categories/{category_name}/subcategories— cria subcategoria- body:
{ "name": "Rent" }
- body:
DELETE /api/metadata/categories/{category_name}/subcategories/{subcategory_name}— apaga subcategoria
Extra (UX):
POST /api/metadata/user-categories/import-defaults— copia o template demo (DEFAULT_CATEGORIES) para a lista do usuário (idempotente)GET /api/metadata/transaction-categories— retorna apenas categorias/subcategorias aprendidas das transações (sem defaults, sem user-defined)GET /api/metadata/demo-categories— retorna o template demo (read-only)
Rotas administrativas (RBAC)
Este projeto possui um router administrativo separado em /api/admin/*.
- Acesso: requer autenticação + role
adminousupport_admin. - Objetivo: concentrar tarefas de suporte/operacional (administração de usuários, manutenção), sem misturar com as rotas de domínio do usuário.
Endpoints atuais:
GET /api/admin/health— sanity-check do RBAC (não-admin recebe 403)GET /api/admin/users— lista usuários (plano, status e segurança)PUT /api/admin/users/{user_id}/role?role=user|support_admin|admin— altera role de um usuárioGET /api/admin/audit-logs— trilha de auditoria administrativaGET /api/admin/ai-observability/summary— observability AI centralizada por janela e usuário
Nota: se ainda não existir usuário com role administrativo (
adminousupport_admin), essas rotas retornarão 403 até que você promova um usuário.
Auditoria administrativa
Toda ação administrativa relevante gera registro em admin_audit_log, incluindo:
- quem executou (
admin_user_id) - quando (
created_at) - alvo (
target_user_id, quando aplicável) - endpoint/método
- IP e
request_id(X-Correlation-ID) - estado antes/depois (
before_state/after_state)
Segurança administrativa (sessão e senha)
O admin pode:
- revogar todas as sessões de um usuário (refresh tokens + invalidação imediata por
session_revoked_at) - forçar troca de senha no próximo login/uso (
force_password_change)
Quando force_password_change=true, o usuário precisa alterar senha (/api/users/me/password) antes de usar outras rotas protegidas.
Logging
O serviço utiliza um sistema de logging estruturado (JSON) centralizado na personal-finance-backend-shared.
- Correlation ID: Todas as requisições recebem um
X-Correlation-ID(seja um novo ou o que foi propagado). Este ID é adicionado a todas as linhas de log, permitindo rastrear uma operação através de todos os microsserviços. - Filtragem: Por ser JSON, os logs são facilmente pesquisáveis e filtráveis em ferramentas de monitoramento por campos como
level,logger_name,user_id,correlation_id, etc.
Helper log_ctx() (padrão de campos)
Para evitar repetição e garantir consistência de campos nos logs, existe o helper log_ctx() em:
personal-finance-backend-shared/personal_finance_shared/logging_utils.py
Exemplo:
from personal_finance_shared.logging_utils import get_logger, log_ctx
logger = get_logger("accounts")
logger.info(
"Forwarding request",
extra=log_ctx(endpoint="accounts.balance_v2", user_id=current_user.id, account=account_name),
)Campos suportados:
endpoint(obrigatório)user_id(opcional)account(opcional)- quaisquer outros campos via
**extra(ex.:status_code,latency_ms, etc.)
Níveis de log (dev vs prod)
O logging é configurado via personal_finance_shared.logging_utils.setup_logging().
Variáveis de ambiente suportadas:
LOG_LEVEL(default:INFO): nível do root logger (DEBUG|INFO|WARNING|ERROR).APP_ENV(default:prod): controla o ruído de loggers externos.- Em
prod: reduz ruído deuvicorn.access,httpx/httpcoreesqlalchemy. - Em
dev: permite mais logs para debugging.
- Em
Exemplo (dev):
export APP_ENV=dev
export LOG_LEVEL=DEBUGend
