npm package: @marcos_feitoza/personal-finance-backend-trades-assets
Description: Main API gateway and orchestration service for the Personal Finance backend.
Latest version: 1.12.0

# 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/health`
- `GET /api/ai/insights`
- `POST /api/ai/chat`
- `POST /api/ai/feedback`
- `POST /api/ai/simulate`
- `POST /api/ai/plan/monthly`
- `GET /api/ai/observability/summary`
- `GET /api/ai/usage/summary`
- `POST /api/ai/notifications/generate`
- `GET /api/ai/notifications`
- `GET /api/ai/notifications/unread-count`
- `GET /api/ai/notifications/schedule`
- `POST /api/ai/notifications/{notification_id}/read`
- `POST /api/ai/notifications/read-all`
- `POST /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/stocks`
- `GET /api/core-market-data/search`
- `GET /api/core-crypto/prices`
- `GET /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-assets` para 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-service` para 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, `crud` e 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_KEY`
- `BALANCE_SERVICE_URL`
- `MARKET_DATA_SERVICE_URL`
- `MARKET_DATA_CRYPTO_SERVICE_URL`
- `AI_INSIGHTS_SERVICE_URL`
- `AI_ADVISOR_ORCHESTRATOR_SERVICE_URL`
- `AI_ADVISOR_HTTP_TIMEOUT_SECONDS`
- `AI_ADVISOR_HTTP_RETRY_ATTEMPTS`
- `AI_ADVISOR_ROLLOUT_ENABLED`
- `AI_ADVISOR_ROLLOUT_ALLOWED_PLANS`
- `AI_ADVISOR_ROLLOUT_PERCENT`
- `AI_ADVISOR_ROLLOUT_ALLOW_USER_IDS`
- `AI_ADVISOR_ROLLOUT_DENY_USER_IDS`
- `AI_ADVISOR_ROLLOUT_CHAT_FALLBACK_ENABLED`
- `AI_ADVISOR_EXPOSE_ROUTING_METADATA`
- `AI_ADVISOR_LIMIT_FREE_DAILY`
- `AI_ADVISOR_LIMIT_FREE_MONTHLY`
- `AI_ADVISOR_LIMIT_PLUS_DAILY`
- `AI_ADVISOR_LIMIT_PLUS_MONTHLY`
- `AI_ADVISOR_LIMIT_PRO_DAILY`
- `AI_ADVISOR_LIMIT_PRO_MONTHLY`
- `AI_ADVISOR_BUDGET_FREE_MONTHLY_USD`
- `AI_ADVISOR_BUDGET_PLUS_MONTHLY_USD`
- `AI_ADVISOR_BUDGET_PRO_MONTHLY_USD`
- `AI_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 ativo
  - `false`: desliga advisor para todos (exceto allowlist)
- `AI_ADVISOR_ROLLOUT_ALLOWED_PLANS`:
  - lista CSV de planos elegíveis (`free,plus,pro`)
  - exemplo: `plus,pro`
- `AI_ADVISOR_ROLLOUT_PERCENT`:
  - percentual de coorte por `user_id % 100`
  - exemplo: `10` ativa ~10% dos usuários elegíveis
- `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/chat` legado
  - `false`: retorna `403 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 comum
  - `true`: 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_MONTHLY`
  - `AI_ADVISOR_LIMIT_PLUS_DAILY` / `AI_ADVISOR_LIMIT_PLUS_MONTHLY`
  - `AI_ADVISOR_LIMIT_PRO_DAILY` / `AI_ADVISOR_LIMIT_PRO_MONTHLY`
  - valor `-1` pode ser usado para desativar um limite específico
- Orçamento de custo por plano (USD/mês, chat advisor):
  - `AI_ADVISOR_BUDGET_FREE_MONTHLY_USD`
  - `AI_ADVISOR_BUDGET_PLUS_MONTHLY_USD`
  - `AI_ADVISOR_BUDGET_PRO_MONTHLY_USD`
  - valor `< 0` desativa orçamento para o plano
  - `AI_ADVISOR_COST_POLICY_ACTION`:
    - `block`: retorna `429 advisor_cost_budget_reached` ao exceder orçamento
    - `warn_only`: não bloqueia (apenas registra evento de orçamento excedido)

Precedência do gate:
1. `DENY_USER_IDS`
2. `ALLOW_USER_IDS`
3. `AI_ADVISOR_ROLLOUT_ENABLED`
4. `AI_ADVISOR_ROLLOUT_ALLOWED_PLANS`
5. `AI_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 recebe `advisor_routed_to` e `rollout`.

Comportamento de limite:
- quando o usuário atinge limite diário ou mensal, `/api/ai/advisor/chat` retorna `429` com `code=advisor_usage_limit_reached`
- o evento é registrado em `ai_usage_events` com `endpoint=ai.advisor.chat` e `error_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/chat` retorna `429` com `code=advisor_cost_budget_reached`
  - com `AI_ADVISOR_COST_POLICY_ACTION=warn_only`: não bloqueia request, mas registra evento operacional
- o evento é registrado em `ai_usage_events` com `error_type=advisor_cost_budget_reached`

Metering LLM (advisor):
- o proxy persiste em `ai_usage_events` os campos de metering recebidos do orchestrator:
  - `provider`
  - `model`
  - `prompt_tokens`
  - `completion_tokens`
  - `total_tokens`
  - `cost_estimate`

Admin observability (advisor):
- `/api/admin/ai-observability/summary` agora 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`) com `p95_latency_ms`.


prod-var.yaml
```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.


```sql
-- 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,pro`
  - `AI_ADVISOR_ROLLOUT_PERCENT=100`

- Rollout seguro inicial:
  - `AI_ADVISOR_ROLLOUT_ALLOWED_PLANS=plus,pro`
  - `AI_ADVISOR_ROLLOUT_PERCENT=10`
  - `AI_ADVISOR_ROLLOUT_CHAT_FALLBACK_ENABLED=true`

## Local Run

```bash
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.sh` or monorepo `podman-build.sh`
- Helm deployment managed by Argo CD

## Notes

- Frontend should call AI endpoints only through `backend-core`, not directly to `ai-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:

```http
Authorization: Bearer <access_token>
```

### Endpoints

- **Login**: `POST /api/auth/token`
  - Envia credenciais no formato `application/x-www-form-urlencoded`:
    - `username=<email>`
    - `password=<senha>`
  - Retorna:
    ```json
    {"access_token": "...", "refresh_token": "...", "token_type": "bearer"}
    ```

- **Refresh**: `POST /api/auth/refresh`
  - Body:
    ```json
    {"refresh_token": "..."}
    ```
  - Retorna um novo par (rotaciona o refresh token):
    ```json
    {"access_token": "...", "refresh_token": "...", "token_type": "bearer"}
    ```

- **Logout**: `POST /api/auth/logout`
  - Body:
    ```json
    {"refresh_token": "..."}
    ```
  - Revoga o refresh token (logout idempotente)

- **Registro**: `POST /api/auth/register`

### Claims atuais do token

No estado atual do projeto, o access token inclui:

- `sub`: email do usuário
- `user_id`: id do usuário
- `role`: role do usuário (ex.: `user`, `admin`)
- `plan`: plano do usuário (ex.: `free`, `plus`, `pro`)
- `iat`: timestamp de emissão
- `jti`: id único do token
- `iss`: issuer (quando `JWT_ISSUER` estiver configurada)
- `aud`: audience (quando `JWT_AUDIENCE` estiver 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`: se `true`, rejeita tokens sem `iss/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 biblioteca `trades-assets`).
- `/api/assets`: Gerenciamento de ativos (da biblioteca `trades-assets`).
- `/api/ai`: Proxy para o serviço de insights de IA (`/health`, `/insights`).

## Planos e Controle de Acesso

O `backend-core` agora separa:

- `role` para autorização administrativa (`user`/`support_admin`/`admin`)
- `plan` para 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ínimo `plus`)
- endpoints administrativos concentram operação de usuários, segurança, créditos, auditoria e observability

Admin endpoints relevantes:

- `GET /api/admin/users` (retorna `id`, `email`, `role`, `plan`, status e flags de segurança)
- `PUT /api/admin/users/{user_id}/plan?plan=free|plus|pro`
- `PUT /api/admin/users/{user_id}/role?role=user|support_admin|admin`
- `PUT /api/admin/users/{user_id}/status?status=active|suspended&reason=...`
- `POST /api/admin/users/{user_id}/security/revoke-sessions`
- `PUT /api/admin/users/{user_id}/security/force-password-change?enabled=true|false&revoke_sessions=true|false`
- `POST /api/admin/users/{user_id}/security/reset-mfa` (placeholder)
- `GET /api/admin/users/{user_id}/ai-credits`
- `GET /api/admin/users/{user_id}/ai-credits/ledger?limit=50`
- `POST /api/admin/users/{user_id}/ai-credits/adjust?delta=10&reason=manual_topup`
- `POST /api/admin/users/{user_id}/ai-credits/promo-grant?...`
- `POST /api/admin/ai-credits/topup-monthly?period=YYYY-MM`
- `GET /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_plan`
- `required_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 (`delta` positivo/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/categories`
- `GET /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`

```json
{
  "categories": {
    "MOTO": ["roda"],
    "LEISURE": ["Bar"]
  }
}
```

`GET /api/metadata/demo-categories`

```json
{
  "categories": {
    "HOUSE": ["Rent", "Net"],
    "LEISURE": ["Bar"]
  }
}
```

`GET /api/metadata/categories`

```json
{
  "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_categories`
  - `user_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-methods`
- `GET /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" }`
- `DELETE /api/metadata/categories/{category_name}` — apaga categoria (cascade nas subcategorias)
- `POST /api/metadata/categories/{category_name}/subcategories` — cria subcategoria
  - body: `{ "name": "Rent" }`
- `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 `admin` ou `support_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ário
- `GET /api/admin/audit-logs` — trilha de auditoria administrativa
- `GET /api/admin/ai-observability/summary` — observability AI centralizada por janela e usuário

> Nota: se ainda não existir usuário com role administrativo (`admin` ou `support_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:

```py
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 de `uvicorn.access`, `httpx/httpcore` e `sqlalchemy`.
  - Em `dev`: permite mais logs para debugging.

Exemplo (dev):

```bash
export APP_ENV=dev
export LOG_LEVEL=DEBUG
```

end