Pular para o conteúdo principal

Dashboard de Analytics

Este guia explica como puxar dados do Ozzie para um dashboard de BI ou analytics. Você pode agregar dados de transações, rastrear aderência ao plano, medir taxas de conclusão de metas e construir visualizações de relatórios.


Quais dados estão disponíveis

Tipo de dadoEndpointCampos principais
TransaçõesGET /users/{id}/transactionsvalor, categoria, estabelecimento, data, fonte
Alocações do planoPOST /users/{id}/plan% de necessidades/desejos/poupança/dívida
Progresso da metaGET /users/{id}/goalstarget_amount, starting_amount, next_move_date
Conclusão de money moveGET /users/{id}/money-movesstatus por ciclo, taxas de conclusão de tarefas
Intake financeiroGET /users/{id}/financial-intakerenda, despesas, snapshot de dívida

Métricas principais que você pode calcular

MétricaComo calcular
Gastos mensais por categoriaSomar amount_cents agrupado por category para transações do mês atual
Taxa de poupança(monthly_net_income - total_expenses) / monthly_net_income * 100 do intake
% de progresso da meta(current_amount - starting_amount) / (target_amount - starting_amount) * 100
Taxa de conclusão de money moveciclos concluídos / total de ciclos * 100 por usuário ou em todos os usuários
Poupança média por cicloSoma de amount_cents para tarefas do marcadas como done, dividido pela contagem de ciclos
Aderência ao planoComparar categorias de gastos reais das transações vs. alvos de allocations do plano

Estratégia de paginação para exportação em massa

O Ozzie usa paginação baseada em cursor em todos os endpoints de lista. Para uma exportação completa de dados, itere até has_more ser false:

export async function fetchAllPages(path, params = {}) {
const allItems = [];
let cursor = null;
let hasMore = true;

while (hasMore) {
const queryParams = new URLSearchParams({ limit: '50', ...params });
if (cursor) queryParams.set('cursor', cursor);

const { data, pagination } = await ozzieRequest('GET', `${path}?${queryParams}`);
allItems.push(...data);
hasMore = pagination.has_more;
cursor = pagination.next_cursor;
}

return allItems;
}

Gastos mensais por categoria

async function getMonthlySpendingByCategory(ozzieUserId, year, month) {
const startDate = `${year}-${String(month).padStart(2, '0')}-01`;
const endDate = new Date(year, month, 0).toISOString().split('T')[0];

const transactions = await fetchAllPages(`/users/${ozzieUserId}/transactions`, {
start_date: startDate,
end_date: endDate,
});

const byCategory = transactions.reduce((acc, tx) => {
const category = tx.category ?? 'Sem categoria';
acc[category] = (acc[category] ?? 0) + tx.amount_cents;
return acc;
}, {});

return Object.entries(byCategory)
.map(([category, cents]) => ({ category, amount: cents / 100 }))
.sort((a, b) => b.amount - a.amount);
}

Taxa de poupança ao longo do tempo

async function getSavingsRateTrend(ozzieUserIds, monthsBack = 6) {
const results = [];

for (let i = 0; i < monthsBack; i++) {
const date = new Date();
date.setMonth(date.getMonth() - i);
const year = date.getFullYear();
const month = date.getMonth() + 1;
const label = `${year}-${String(month).padStart(2, '0')}`;

let totalSaved = 0;
let totalIncome = 0;

for (const userId of ozzieUserIds) {
const { data: intake } = await ozzieRequest('GET', `/users/${userId}/financial-intake`)
.catch(() => ({ data: null }));
if (!intake) continue;

const monthTransactions = await fetchAllPages(`/users/${userId}/transactions`, {
start_date: `${year}-${String(month).padStart(2, '0')}-01`,
});

const spent = monthTransactions.reduce((sum, tx) => sum + tx.amount_cents, 0) / 100;
const income = intake.monthly_net_income;
const saved = Math.max(0, income - spent);
totalIncome += income;
totalSaved += saved;
}

results.push({
month: label,
savings_rate: totalIncome > 0 ? ((totalSaved / totalIncome) * 100).toFixed(1) : '0.0',
total_saved: totalSaved.toFixed(2),
});
}

return results.reverse();
}

Exportação em massa para todos os usuários

// scripts/export-all.js — executar como job noturno
async function exportAll() {
const users = await db.users.findAll({ where: { ozzie_user_id: { not: null } } });
const results = [];
const errors = [];

for (const user of users) {
try {
const data = await exportUserFinancialData(user.ozzie_user_id);
results.push({ internal_user_id: user.id, ...data });
} catch (err) {
errors.push({ user_id: user.id, error: err.message });
}
await new Promise(r => setTimeout(r, 100)); // Delay amigável com o rate limit
}

writeFileSync(`exports/ozzie-${Date.now()}.json`, JSON.stringify(results, null, 2));
}
informação

Para bases de usuários muito grandes (10k+ usuários), execute exportações em lotes paralelos de 10–20 usuários, respeitando seu rate limit. Verifique os headers de resposta X-RateLimit-Remaining para evitar atingir o limite.


Gráficos sugeridos

Detalhamento de gastos — gráfico de pizza ou donut

Use os dados de getMonthlySpendingByCategory(). Codifique por cores por categoria. Sobreponha um anel pontilhado mostrando os alvos de allocations do plano para mostrar orçamento vs. real.

Tendência da taxa de poupança — gráfico de linhas

Use os dados de getSavingsRateTrend() ao longo de 6–12 meses. Adicione uma linha tracejada horizontal na target_savings_rate do plano.

Progresso da meta — barra de progresso horizontal

Use os dados de getGoalProgress(). Mostre porcentagem, valor restante e data projetada do plan.key_metrics.projected_goal_date.

Taxa de conclusão de money move — gauge ou barra

Use os dados de getMoneyMoveCompletionRate(). Para um dashboard voltado ao usuário, mostre um contador de sequência (ciclos consecutivos concluídos) para incentivar consistência.