Skip to main content
Glama

Daily Report

report_daily
Read-onlyIdempotent

Retrieve a daily operational report for a store, including orders, revenue, top products, customer metrics, low stock alerts, and anomaly count.

Instructions

Daily operational report: orders, revenue, top products, new vs returning customers, low stock alerts, and anomaly count.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
store_idYesUUID of a connected store (returned by store_connect with action="connect" or visible in store_connect with action="list" / the store_overview resource)
dateNoCalendar date for the report in YYYY-MM-DD (e.g. "2026-04-25"). Defaults to today (UTC). The report is computed against orders whose created_at falls within that calendar day.

Implementation Reference

  • src/index.ts:307-325 (registration)
    Tool registration for 'report_daily' with input schema (store_id required, date optional) and handler that calls generateDailyReport.
    // ── Tool: report_daily ────────────────────────────────────────────
    server.registerTool(
      'report_daily',
      {
        title: 'Daily Report',
        description: 'Daily operational report: orders, revenue, top products, new vs returning customers, low stock alerts, and anomaly count.',
        inputSchema: z.object({
          store_id: z.string().uuid().describe('UUID of a connected store (returned by store_connect with action="connect" or visible in store_connect with action="list" / the store_overview resource)'),
          date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, 'Date must be YYYY-MM-DD format').optional().describe('Calendar date for the report in YYYY-MM-DD (e.g. "2026-04-25"). Defaults to today (UTC). The report is computed against orders whose created_at falls within that calendar day.'),
        }),
        annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
      },
      async ({ store_id, date }) => {
        try {
          const result = await generateDailyReport(store_id, date);
          return { content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }] };
        } catch (e) { return handleToolError(e); }
      }
    );
  • DailyReportSchema and DailyReport type defining the output shape: store_id, date, total_orders, total_revenue, avg_order_value, new_customers, returning_customers, top_products, low_stock_alerts, anomaly_count, and summary.
    export const DailyReportSchema = z.object({
      store_id: z.string().uuid(),
      date: z.string(),
      total_orders: z.number().int(),
      total_revenue: z.number(),
      avg_order_value: z.number(),
      new_customers: z.number().int(),
      returning_customers: z.number().int(),
      top_products: z.array(z.object({
        title: z.string(),
        units: z.number().int(),
        revenue: z.number(),
      })),
      low_stock_alerts: z.array(z.object({
        title: z.string(),
        current_stock: z.number().int(),
        days_left: z.number().nullable(),
      })),
      anomaly_count: z.number().int(),
      summary: z.string(),
    });
    export type DailyReport = z.infer<typeof DailyReportSchema>;
  • generateDailyReport function: the core implementation that fetches orders for the given date, computes revenue/avg order value, distinguishes new vs returning customers, finds top 5 products, checks low stock alerts (via forecastAll), counts anomalies (via detectAnomalies), and returns a DailyReport.
    export async function generateDailyReport(storeId: string, date?: string): Promise<DailyReport> {
      validateUUID(storeId, 'store');
      const store = await storage.getStoreById(storeId);
      if (!store) throw new NotFoundError('Store', storeId);
    
      const targetDate = date ?? new Date().toISOString().slice(0, 10);
      const dayStart = new Date(`${targetDate}T00:00:00Z`).getTime();
      const dayEnd = dayStart + MS_PER_DAY;
    
      const allOrders = await storage.getOrders(storeId);
      const dayOrders = allOrders.filter((o) => {
        const ts = new Date(o.created_at).getTime();
        return ts >= dayStart && ts < dayEnd && o.status !== 'cancelled' && o.status !== 'refunded';
      });
    
      const totalRevenue = dayOrders.reduce((sum, o) => sum + o.total, 0);
      const avgOrderValue = dayOrders.length > 0 ? totalRevenue / dayOrders.length : 0;
    
      // Count new vs returning
      const customerOrderCounts = new Map<string, number>();
      for (const order of allOrders) {
        if (!order.customer_id || order.status === 'cancelled') continue;
        const ts = new Date(order.created_at).getTime();
        if (ts < dayStart) {
          customerOrderCounts.set(order.customer_id, (customerOrderCounts.get(order.customer_id) ?? 0) + 1);
        }
      }
    
      let newCustomers = 0;
      let returningCustomers = 0;
      const seenCustomers = new Set<string>();
      for (const order of dayOrders) {
        if (!order.customer_id || seenCustomers.has(order.customer_id)) continue;
        seenCustomers.add(order.customer_id);
        if ((customerOrderCounts.get(order.customer_id) ?? 0) > 0) {
          returningCustomers++;
        } else {
          newCustomers++;
        }
      }
    
      // Top products
      const productSales = new Map<string, { title: string; units: number; revenue: number }>();
      for (const order of dayOrders) {
        for (const item of order.items) {
          const existing = productSales.get(item.product_id);
          if (existing) {
            existing.units += item.quantity;
            existing.revenue += item.total;
          } else {
            productSales.set(item.product_id, { title: item.title, units: item.quantity, revenue: item.total });
          }
        }
      }
      const topProducts = [...productSales.values()]
        .sort((a, b) => b.revenue - a.revenue)
        .slice(0, 5)
        .map((p) => ({ title: p.title, units: p.units, revenue: Math.round(p.revenue * 100) / 100 }));
    
      // Low stock alerts
      const products = await storage.getProducts(storeId);
      const forecasts = forecastAll(products, allOrders);
      const lowStockAlerts = forecasts
        .filter((f) => f.risk_level === 'critical' || f.risk_level === 'high')
        .slice(0, 5)
        .map((f) => ({ title: f.product_title, current_stock: f.current_stock, days_left: f.days_of_stock }));
    
      // Anomalies
      const anomalies = detectAnomalies(allOrders);
      const anomalyCount = anomalies.filter((a) => a.risk_level === 'high' || a.risk_level === 'critical').length;
    
      // Summary
      const summaryParts: string[] = [];
      summaryParts.push(`${dayOrders.length} orders totaling $${totalRevenue.toFixed(2)}`);
      if (newCustomers > 0) summaryParts.push(`${newCustomers} new customer(s)`);
      if (lowStockAlerts.length > 0) summaryParts.push(`${lowStockAlerts.length} product(s) need restocking`);
      if (anomalyCount > 0) summaryParts.push(`${anomalyCount} order anomaly alert(s)`);
    
      return {
        store_id: storeId,
        date: targetDate,
        total_orders: dayOrders.length,
        total_revenue: Math.round(totalRevenue * 100) / 100,
        avg_order_value: Math.round(avgOrderValue * 100) / 100,
        new_customers: newCustomers,
        returning_customers: returningCustomers,
        top_products: topProducts,
        low_stock_alerts: lowStockAlerts,
        anomaly_count: anomalyCount,
        summary: summaryParts.join('. ') + '.',
      };
    }
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=true, destructiveHint=false, idempotentHint=true, so the tool's safety profile is clear. The description adds the list of report contents but does not disclose additional behavioral details such as data aggregation method, timezone handling (though partially covered in date parameter), or performance characteristics.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single sentence of 14 words, front-loaded with the purpose ('Daily operational report') followed by a concise list of contents. Every phrase adds value, with no redundant or irrelevant information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given no output schema, the description enumerates all major report sections (orders, revenue, products, customers, alerts, anomalies), providing adequate context for an agent. Minor omission: no mention of the output format (e.g., JSON, table) or whether the report returns aggregated data for the exact day versus a rolling window.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100% for both parameters (store_id and date). The tool description does not add new meaning beyond what the schema already provides, so a baseline of 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states it provides a 'daily operational report' containing specific metrics: orders, revenue, top products, new vs returning customers, low stock alerts, and anomaly count. This differentiates it from the sibling tool 'report_weekly' which likely covers a different time period, and from other analytics tools like 'order_anomalies' which focus on a single metric.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies the tool is for generating a daily summary, but does not explicitly state when to use it over alternatives like 'report_weekly' or when a more granular tool like 'order_anomalies' would be preferred. No guidance on exclusions or prerequisites is provided.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/enzoemir1/shopops-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server