Skip to main content
Glama

Weekly Report

report_weekly
Read-onlyIdempotent

Compare weekly revenue and order changes, analyze customer segments, identify trending products, and receive AI-generated insights.

Instructions

Weekly trend report: revenue/order changes vs previous week, customer segment distribution, trending products, and AI-generated insights.

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)

Implementation Reference

  • src/index.ts:327-346 (registration)
    Tool 'report_weekly' registered with MCP server. Accepts store_id (UUID) input. Pro license-gated. Delegates to generateWeeklyReport().
    // ── Tool: report_weekly ───────────────────────────────────────────
    server.registerTool(
      'report_weekly',
      {
        title: 'Weekly Report',
        description: 'Weekly trend report: revenue/order changes vs previous week, customer segment distribution, trending products, and AI-generated insights.',
        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)'),
        }),
        annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
      },
      async ({ store_id }) => {
        try {
          const reject = await ensureProOrReject(LICENSE_CONFIG, 'report_weekly');
          if (reject) return reject;
          const result = await generateWeeklyReport(store_id);
          return { content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }] };
        } catch (e) { return handleToolError(e); }
      }
    );
  • Core business logic: computes weekly report comparing this week vs previous week, including revenue/order changes, RFM customer segment distribution, trending products (rising/stable/declining), and AI-generated text insights.
    export async function generateWeeklyReport(storeId: string): Promise<WeeklyReport> {
      validateUUID(storeId, 'store');
      const store = await storage.getStoreById(storeId);
      if (!store) throw new NotFoundError('Store', storeId);
    
      const now = Date.now();
      const weekEnd = new Date(now).toISOString().slice(0, 10);
      const weekStart = new Date(now - 7 * MS_PER_DAY).toISOString().slice(0, 10);
    
      const allOrders = await storage.getOrders(storeId);
      const thisWeek = allOrders.filter((o) => {
        const ts = new Date(o.created_at).getTime();
        return ts >= now - 7 * MS_PER_DAY && o.status !== 'cancelled' && o.status !== 'refunded';
      });
      const prevWeek = allOrders.filter((o) => {
        const ts = new Date(o.created_at).getTime();
        return ts >= now - 14 * MS_PER_DAY && ts < now - 7 * MS_PER_DAY && o.status !== 'cancelled' && o.status !== 'refunded';
      });
    
      const thisRevenue = thisWeek.reduce((sum, o) => sum + o.total, 0);
      const prevRevenue = prevWeek.reduce((sum, o) => sum + o.total, 0);
      const avgOrderValue = thisWeek.length > 0 ? thisRevenue / thisWeek.length : 0;
      const revenueChange = prevRevenue > 0 ? Math.round(((thisRevenue - prevRevenue) / prevRevenue) * 10000) / 100 : 0;
      const orderChange = prevWeek.length > 0 ? Math.round(((thisWeek.length - prevWeek.length) / prevWeek.length) * 10000) / 100 : 0;
    
      // Segment summary
      const customers = await storage.getCustomers(storeId);
      const rfmResults = segmentCustomers(customers, allOrders);
      const segmentMap = new Map<string, { count: number; revenue: number }>();
      for (const r of rfmResults) {
        const existing = segmentMap.get(r.segment) ?? { count: 0, revenue: 0 };
        existing.count++;
        existing.revenue += r.total_spent;
        segmentMap.set(r.segment, existing);
      }
      const topSegments = [...segmentMap.entries()]
        .sort((a, b) => b[1].revenue - a[1].revenue)
        .slice(0, 5)
        .map(([segment, data]) => ({
          segment: segment as RFMSegment,
          count: data.count,
          revenue: Math.round(data.revenue * 100) / 100,
        }));
    
      // Product trends
      const thisWeekProducts = new Map<string, { title: string; units: number }>();
      const prevWeekProducts = new Map<string, { title: string; units: number }>();
      for (const order of thisWeek) {
        for (const item of order.items) {
          const ex = thisWeekProducts.get(item.product_id) ?? { title: item.title, units: 0 };
          ex.units += item.quantity;
          thisWeekProducts.set(item.product_id, ex);
        }
      }
      for (const order of prevWeek) {
        for (const item of order.items) {
          const ex = prevWeekProducts.get(item.product_id) ?? { title: item.title, units: 0 };
          ex.units += item.quantity;
          prevWeekProducts.set(item.product_id, ex);
        }
      }
    
      const trendingProducts = [...thisWeekProducts.entries()]
        .map(([id, data]) => {
          const prev = prevWeekProducts.get(id)?.units ?? 0;
          const change = data.units - prev;
          const trend = change > 2 ? 'rising' as const : change < -2 ? 'declining' as const : 'stable' as const;
          return { title: data.title, units_change: change, trend };
        })
        .sort((a, b) => Math.abs(b.units_change) - Math.abs(a.units_change))
        .slice(0, 5);
    
      // Insights
      const insights: string[] = [];
      if (revenueChange > 10) insights.push(`Revenue up ${revenueChange}% vs last week — strong growth`);
      else if (revenueChange < -10) insights.push(`Revenue down ${Math.abs(revenueChange)}% vs last week — needs attention`);
      else insights.push('Revenue is stable compared to last week');
    
      const risingProducts = trendingProducts.filter((p) => p.trend === 'rising');
      if (risingProducts.length > 0) insights.push(`${risingProducts.length} product(s) trending upward: ${risingProducts.map((p) => p.title).join(', ')}`);
    
      const atRisk = rfmResults.filter((r) => r.segment === 'at_risk' || r.segment === 'lost');
      if (atRisk.length > 0) insights.push(`${atRisk.length} customer(s) at risk of churning — consider win-back campaigns`);
    
      return {
        store_id: storeId,
        week_start: weekStart,
        week_end: weekEnd,
        total_orders: thisWeek.length,
        total_revenue: Math.round(thisRevenue * 100) / 100,
        avg_order_value: Math.round(avgOrderValue * 100) / 100,
        revenue_change_percent: revenueChange,
        order_change_percent: orderChange,
        top_segments: topSegments,
        trending_products: trendingProducts,
        insights,
        summary: insights.join('. ') + '.',
      };
    }
  • Zod schema defining the WeeklyReport return type with fields: store_id, week_start/end, total_orders, total_revenue, avg_order_value, revenue/order change percent, top_segments, trending_products, insights, and summary.
    export const WeeklyReportSchema = z.object({
      store_id: z.string().uuid(),
      week_start: z.string(),
      week_end: z.string(),
      total_orders: z.number().int(),
      total_revenue: z.number(),
      avg_order_value: z.number(),
      revenue_change_percent: z.number(),
      order_change_percent: z.number(),
      top_segments: z.array(z.object({
        segment: RFMSegmentSchema,
        count: z.number().int(),
        revenue: z.number(),
      })),
      trending_products: z.array(z.object({
        title: z.string(),
        units_change: z.number().int(),
        trend: z.enum(['rising', 'stable', 'declining']),
      })),
      insights: z.array(z.string()),
      summary: z.string(),
    });
    export type WeeklyReport = z.infer<typeof WeeklyReportSchema>;
Behavior3/5

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

Annotations already indicate read-only, non-destructive, idempotent behavior. The description adds context about AI-generated insights but does not detail data freshness, authorization requirements, or return format. No contradiction with annotations.

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, efficient sentence that conveys all necessary information without redundancy. It is front-loaded with the core purpose and lists specific components.

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 a single parameter, no output schema, and moderate complexity, the description covers the report's contents well. It could mention that it compares to the previous week, but overall it is sufficiently complete.

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?

The input schema has one parameter (store_id) with a comprehensive description (100% coverage). The description does not add further parameter meaning beyond what the schema provides, so a baseline score 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 the tool produces a weekly trend report covering revenue/order changes, customer segments, trending products, and AI insights. It uses specific verbs and resources, distinguishing it from siblings like report_daily.

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 usage for generating weekly reports but does not explicitly state when to use this tool versus alternatives (e.g., report_daily). No exclusions or prerequisites are mentioned.

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