Building a Natural Language Revenue Dashboard
Architecture: 1) Define tools for Paystack queries (list_transactions with date/channel/status filters, get_plan_subscriptions, get_settlements). 2) User types a natural language question. 3) LLM translates intent to tool calls — e.g., "last month revenue" → list_transactions({from: "2026-06-01", to: "2026-06-30", status: "success"}). 4) Tool results returned to LLM for aggregation and plain-English response. 5) Keep the dashboard read-only — no write tools in this context.
Dashboard Tools and Query Flow
// Tools for the natural language revenue dashboard
const dashboardTools = [
{
name: 'get_revenue_summary',
description: 'Get total revenue, transaction count, average order value, and channel breakdown for a date range.',
input_schema: {
type: 'object',
properties: {
from: { type: 'string', description: 'Start date YYYY-MM-DD' },
to: { type: 'string', description: 'End date YYYY-MM-DD' },
},
required: ['from', 'to'],
},
},
{
name: 'get_top_customers',
description: 'Get the top customers by total spend in a date range.',
input_schema: {
type: 'object',
properties: {
from: { type: 'string' },
to: { type: 'string' },
limit: { type: 'number', description: 'Number of customers, default 10' },
},
required: ['from', 'to'],
},
},
{
name: 'get_subscription_mrr',
description: 'Get Monthly Recurring Revenue from active Paystack subscriptions.',
input_schema: { type: 'object', properties: {} },
},
];
// Server-side: compute summaries, not raw data
async function executeRevenueTool(toolName, input) {
var headers = { Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY };
if (toolName === 'get_revenue_summary') {
// Fetch all transactions in range (paginate for large volumes)
var url = 'https://api.paystack.co/transaction?status=success&from=' + input.from + '&to=' + input.to + '&perPage=100';
var txns = (await (await fetch(url, { headers })).json()).data || [];
// Compute server-side — do not send raw array to LLM
var total = txns.reduce((sum, t) => sum + t.amount, 0);
var byChannel = txns.reduce((acc, t) => {
acc[t.channel] = (acc[t.channel] || 0) + t.amount;
return acc;
}, {});
return {
total_kobo: total,
total_ngn: total / 100,
transaction_count: txns.length,
average_order_ngn: txns.length ? total / 100 / txns.length : 0,
by_channel: Object.fromEntries(Object.entries(byChannel).map(([k, v]) => [k, v / 100])),
};
}
if (toolName === 'get_subscription_mrr') {
var subs = (await (await fetch('https://api.paystack.co/subscription?status=active&perPage=100', { headers })).json()).data || [];
var mrr = subs.reduce((sum, s) => sum + s.amount, 0) / 100;
return { mrr_ngn: mrr, active_subscriptions: subs.length };
}
}
Learn More
See building a Paystack query agent for the underlying tool calling pattern.
Key Takeaways
- ✓Natural language dashboards translate user questions to Paystack API calls via LLM tool calling.
- ✓Feed aggregated summaries to the LLM, not raw transaction arrays — reduce token cost and hallucination risk.
- ✓Include a date resolution tool so the LLM can convert "last month" to exact dates before querying.
- ✓The dashboard is read-only — no payment actions, only data retrieval and aggregation.
- ✓Compute KPIs server-side (total, average, growth %) before returning to LLM — do not rely on LLM arithmetic.
Frequently Asked Questions
- How do I handle "last month" or "this week" in natural language queries?
- Add a date resolution tool or pre-process the user query server-side. Before sending to the LLM, replace relative date references with absolute dates using your server's current date. Or add a resolve_date_range tool that the LLM calls first: resolve_date_range("last month") → { from: "2026-06-01", to: "2026-06-30" }. Never rely on the LLM to know what "today" is without giving it the current date in the system prompt.
- Can I display charts in the response?
- Yes — return structured data alongside the natural language answer. Have the LLM return a JSON block with chart_data (array of {label, value} objects) alongside the text response. Your frontend renders the chart using the structured data, while the text provides the narrative. The LLM is good at formatting data for charts; it just needs to know to include it.
- How do I handle multi-currency businesses?
- Paystack returns amounts in the smallest currency unit for the transaction currency. Your summary tool should group by currency before aggregating — do not add NGN and KES together. Return separate summaries per currency and let the LLM present them clearly. If you need a combined number, convert using an exchange rate you supply — do not let the LLM guess exchange rates.
Ready to build real-world apps?
Join the McTaba Labs full-stack marathon (4 months full-time · 6 months part-time). Learn M-Pesa, USSD, and WhatsApp engineering while shipping 8 production apps.
Apply to the McTaba Marathon