Bonaventure OgetoBy Bonaventure Ogeto|

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.

Sign up for the McTaba newsletter

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