Analytics Dashboard
This guide covers pulling Ozzie data into a BI or analytics dashboard. You can aggregate parsed transaction data, track plan adherence, measure money move completion rates, and build reporting views β either for your own internal use or as a customer-facing product feature.
What data is availableβ
Your application stores the results of ozzie-openapi calls. The data available for analytics depends on what you've collected and stored:
| Data type | Source | Key fields |
|---|---|---|
| Parsed transactions | POST /v1/transactions/parse response | amount_cents, category, envelope, transaction_date |
| Plan allocations | POST /v1/users/{id}/plan/generate response | envelope allocated_pct, allocated_amount, plan_tier |
| Money move completion | Your database (tracked by your app) | status per cycle, task completion rates, amount_cents per do task |
| Financial intake | GET /v1/users/{id}/financial-intake | monthly_income, monthly_expenses, monthly_surplus |
Key metrics you can computeβ
| Metric | How to compute |
|---|---|
| Monthly spending by category | Sum amount_cents grouped by category for stored transactions in the current month |
| Savings rate | (monthly_income - monthly_expenses) / monthly_income * 100 from stored intake |
| Goal progress % | Sum of do task amount_cents completed Γ· goal target |
| Money move completion rate | Completed cycles Γ· total cycles Γ 100 |
| Average savings per cycle | Sum of amount_cents for completed do tasks Γ· cycle count |
| Plan adherence | Compare stored transaction totals per envelope vs. plan allocated_amount |
Sample queries using stored dataβ
Monthly spending by categoryβ
async function getMonthlySpendingByCategory(userId, year, month) {
const startDate = `${year}-${String(month).padStart(2, '0')}-01`;
const endDate = new Date(year, month, 0).toISOString().split('T')[0]; // Last day of month
// Query your database for stored transaction records
const transactions = await db.transactions.findAll({
where: {
user_id: userId,
transaction_date: { gte: startDate, lte: endDate },
}
});
// Group and sum by category
const byCategory = transactions.reduce((acc, tx) => {
const category = tx.category ?? 'Uncategorized';
acc[category] = (acc[category] ?? 0) + tx.amount_cents;
return acc;
}, {});
// Convert cents to dollars and sort descending
return Object.entries(byCategory)
.map(([category, cents]) => ({ category, amount: cents / 100 }))
.sort((a, b) => b.amount - a.amount);
}
// Example output:
// [
// { category: "food", amount: 487.50 },
// { category: "transport", amount: 210.00 },
// { category: "housing", amount: 1200.00 },
// ]
Savings rate over timeβ
async function getSavingsRateTrend(userIds, 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 userIds) {
// Load stored intake from your database
const intake = await db.intakes.findLatest(userId);
if (!intake) continue;
// Load stored transactions for this month
const transactions = await db.transactions.findAll({
where: {
user_id: userId,
transaction_date: { gte: `${year}-${String(month).padStart(2, '0')}-01` },
}
});
const spent = transactions.reduce((sum, tx) => sum + tx.amount_cents, 0) / 100;
const income = intake.monthly_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(); // Oldest first
}
Money move completion rateβ
async function getMoneyMoveCompletionRate(userId) {
// Query your stored money move records
const allMoves = await db.moneyMoves.findAll({ where: { user_id: userId } });
const terminal = allMoves.filter(m => ['completed', 'skipped'].includes(m.status));
const completed = allMoves.filter(m => m.status === 'completed');
if (terminal.length === 0) return { completion_rate: null, cycles: 0 };
return {
total_cycles: terminal.length,
completed_cycles: completed.length,
skipped_cycles: terminal.length - completed.length,
completion_rate: ((completed.length / terminal.length) * 100).toFixed(1),
};
}
Plan adherence by envelopeβ
async function getEnvelopeAdherence(userId, year, month) {
// Load the user's stored plan
const plan = await db.plans.findLatest(userId);
if (!plan) return null;
// Load stored transactions for this month
const transactions = await db.transactions.findAll({
where: {
user_id: userId,
transaction_date: { gte: `${year}-${String(month).padStart(2, '0')}-01` },
}
});
// Sum spending by envelope
const spentByEnvelope = transactions.reduce((acc, tx) => {
if (tx.envelope) {
acc[tx.envelope] = (acc[tx.envelope] ?? 0) + tx.amount_cents;
}
return acc;
}, {});
// Compare against plan allocations
return Object.entries(plan.envelopes).map(([envelope, allocation]) => ({
envelope,
allocated_amount: allocation.allocated_amount,
spent_amount: (spentByEnvelope[envelope] ?? 0) / 100,
remaining: allocation.allocated_amount - (spentByEnvelope[envelope] ?? 0) / 100,
adherence_pct: allocation.allocated_amount > 0
? ((1 - (spentByEnvelope[envelope] ?? 0) / 100 / allocation.allocated_amount) * 100).toFixed(1)
: null,
}));
}
Refreshing intelligence dataβ
When you need fresh AI-computed data for analytics (e.g., regenerating a plan or computing current envelope allocations), call ozzie-openapi and store the result:
// Refresh envelope allocations for a user
async function refreshFinancialProfile(userId) {
const user = await db.users.findById(userId);
const { data: profile } = await ozzieRequest(
'GET',
`/users/${user.ozzie_user_id}/financial-profile?personality_type=${user.personality_type}`
);
await db.financialProfiles.upsert({
user_id: userId,
data: profile,
refreshed_at: new Date(),
});
return profile;
}
Suggested chartsβ
Spending breakdown β pie or donut chartβ
Use getMonthlySpendingByCategory() data:
Category | Amount | % of Total
---------------|---------|------------
food | $487 | 27.1%
housing | $1,200 | 66.7%
transport | $210 | 11.7%
entertainment | $134 | 7.5%
Chart type: Pie or donut. Color-code by category or by envelope.
Plan adherence β grouped bar chartβ
Use getEnvelopeAdherence() data:
Envelope | Allocated | Spent | Remaining
---------------|-----------|-------|----------
daily_living | $525 | $410 | $115
lifestyle | $300 | $180 | $120
people | $375 | $90 | $285
Chart type: Grouped bar chart comparing allocated vs. spent per envelope.
Savings rate trend β line chartβ
Use getSavingsRateTrend() data over 6β12 months.
Chart type: Line chart with monthly x-axis. Add a horizontal dashed line at the plan's implied savings target rate.
Money move completion β bar or gauge chartβ
Use getMoneyMoveCompletionRate() data:
Completion Rate: 78% (14 completed / 18 total cycles)
Chart type: Gauge or simple stat card. For a user-facing dashboard, show a streak counter (consecutive completed cycles) to encourage consistency.