code icon Code

Compute Gmail Stats

Compute analytics from fetched email data. Reads from file, computes requested metrics.

Source Code

import fs from "fs";

const [inputPath = "session/emails.json", metricsArg = "senders,volume"] =
  process.argv.slice(2);
const requestedMetrics = metricsArg
  .split(",")
  .map((m) => m.trim().toLowerCase());

console.log(`Computing metrics from: ${inputPath}`);
console.log(`Metrics: ${requestedMetrics.join(", ")}`);

try {
  if (!fs.existsSync(inputPath)) {
    console.error(`File not found: ${inputPath}`);
    console.error("Run code:gmail.inbox.fetch first to generate email data");
    process.exit(1);
  }

  const raw = fs.readFileSync(inputPath, "utf-8");
  const data = JSON.parse(raw);
  const messages = data.messages || [];

  if (messages.length === 0) {
    console.log("No messages to analyze");
    console.log(JSON.stringify({ metrics: {}, messageCount: 0 }));
    process.exit(0);
  }

  console.log(`Analyzing ${messages.length} messages...`);

  const stats = {
    messageCount: messages.length,
    query: data.query,
    fetchedAt: data.fetchedAt,
  };

  // Sender frequency
  if (requestedMetrics.includes("senders")) {
    const senderCounts = {};
    for (const msg of messages) {
      const sender = extractEmail(msg.from);
      senderCounts[sender] = (senderCounts[sender] || 0) + 1;
    }
    stats.senders = Object.entries(senderCounts)
      .sort((a, b) => b[1] - a[1])
      .map(([email, count]) => ({
        email,
        count,
        percentage: Math.round((count / messages.length) * 100),
      }));
    stats.uniqueSenders = stats.senders.length;
  }

  // Volume by date
  if (requestedMetrics.includes("volume")) {
    const byDate = {};
    const byDayOfWeek = {
      Sun: 0,
      Mon: 0,
      Tue: 0,
      Wed: 0,
      Thu: 0,
      Fri: 0,
      Sat: 0,
    };
    const days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];

    for (const msg of messages) {
      if (msg.date) {
        const d = new Date(msg.date);
        if (!isNaN(d.getTime())) {
          const dateKey = d.toISOString().split("T")[0];
          byDate[dateKey] = (byDate[dateKey] || 0) + 1;
          byDayOfWeek[days[d.getDay()]]++;
        }
      }
    }

    const sortedDates = Object.entries(byDate).sort((a, b) =>
      a[0].localeCompare(b[0])
    );
    const counts = sortedDates.map(([, c]) => c);

    stats.volume = {
      byDate: sortedDates.map(([date, count]) => ({ date, count })),
      byDayOfWeek,
      dailyAverage:
        counts.length > 0
          ? Math.round(counts.reduce((a, b) => a + b, 0) / counts.length)
          : 0,
      peakDay:
        sortedDates.length > 0
          ? sortedDates.reduce((a, b) => (b[1] > a[1] ? b : a))[0]
          : null,
      peakCount: counts.length > 0 ? Math.max(...counts) : 0,
    };
  }

  // Label distribution
  if (requestedMetrics.includes("labels")) {
    const labelCounts = {};
    for (const msg of messages) {
      for (const label of msg.labelIds || []) {
        labelCounts[label] = (labelCounts[label] || 0) + 1;
      }
    }
    stats.labels = Object.entries(labelCounts)
      .sort((a, b) => b[1] - a[1])
      .map(([label, count]) => ({ label, count }));
  }

  // Domain analysis
  if (requestedMetrics.includes("domains")) {
    const domainCounts = {};
    for (const msg of messages) {
      const email = extractEmail(msg.from);
      const domain = email.split("@")[1] || "unknown";
      domainCounts[domain] = (domainCounts[domain] || 0) + 1;
    }
    stats.domains = Object.entries(domainCounts)
      .sort((a, b) => b[1] - a[1])
      .map(([domain, count]) => ({
        domain,
        count,
        percentage: Math.round((count / messages.length) * 100),
      }));
  }

  // Hour of day distribution
  if (requestedMetrics.includes("hours")) {
    const byHour = {};
    for (let i = 0; i < 24; i++) byHour[i] = 0;

    for (const msg of messages) {
      if (msg.date) {
        const d = new Date(msg.date);
        if (!isNaN(d.getTime())) {
          byHour[d.getHours()]++;
        }
      }
    }

    const peakHour = Object.entries(byHour).reduce((a, b) =>
      b[1] > a[1] ? b : a
    );
    stats.hours = {
      distribution: byHour,
      peakHour: parseInt(peakHour[0]),
      peakHourCount: peakHour[1],
      businessHours: Object.entries(byHour)
        .filter(([h]) => parseInt(h) >= 9 && parseInt(h) < 17)
        .reduce((sum, [, c]) => sum + c, 0),
      afterHours: Object.entries(byHour)
        .filter(([h]) => parseInt(h) < 9 || parseInt(h) >= 17)
        .reduce((sum, [, c]) => sum + c, 0),
    };
  }

  // Response time analysis (basic - based on thread clustering)
  if (requestedMetrics.includes("response_times")) {
    const threadMessages = {};
    for (const msg of messages) {
      if (!threadMessages[msg.threadId]) {
        threadMessages[msg.threadId] = [];
      }
      threadMessages[msg.threadId].push(msg);
    }

    const threadSizes = Object.values(threadMessages).map(
      (msgs) => msgs.length
    );
    stats.threads = {
      total: Object.keys(threadMessages).length,
      averageLength:
        threadSizes.length > 0
          ? Math.round(
              (threadSizes.reduce((a, b) => a + b, 0) / threadSizes.length) * 10
            ) / 10
          : 0,
      maxLength: threadSizes.length > 0 ? Math.max(...threadSizes) : 0,
      singleMessage: threadSizes.filter((s) => s === 1).length,
      multiMessage: threadSizes.filter((s) => s > 1).length,
    };
  }

  // Output summary
  console.log(`\n✓ Analysis complete`);
  console.log(`  Total messages: ${stats.messageCount}`);

  if (stats.uniqueSenders) {
    console.log(`  Unique senders: ${stats.uniqueSenders}`);
    console.log(`  Top senders:`);
    stats.senders.slice(0, 5).forEach((s) => {
      console.log(`    - ${s.email}: ${s.count} (${s.percentage}%)`);
    });
  }

  if (stats.volume) {
    console.log(`  Daily average: ${stats.volume.dailyAverage} emails`);
    console.log(
      `  Peak day: ${stats.volume.peakDay} (${stats.volume.peakCount} emails)`
    );
  }

  if (stats.threads) {
    console.log(`  Threads: ${stats.threads.total}`);
    console.log(`  Avg thread length: ${stats.threads.averageLength}`);
  }

  console.log("\n--- Full Stats ---");
  console.log(JSON.stringify(stats, null, 2));
} catch (error) {
  console.error("Error computing stats:", error.message);
  throw error;
}

function extractEmail(fromHeader) {
  if (!fromHeader) return "unknown";
  const match = fromHeader.match(/<([^>]+)>/);
  return match ? match[1].toLowerCase() : fromHeader.toLowerCase().trim();
}
                  import fs from "fs";

const [inputPath = "session/emails.json", metricsArg = "senders,volume"] =
  process.argv.slice(2);
const requestedMetrics = metricsArg
  .split(",")
  .map((m) => m.trim().toLowerCase());

console.log(`Computing metrics from: ${inputPath}`);
console.log(`Metrics: ${requestedMetrics.join(", ")}`);

try {
  if (!fs.existsSync(inputPath)) {
    console.error(`File not found: ${inputPath}`);
    console.error("Run code:gmail.inbox.fetch first to generate email data");
    process.exit(1);
  }

  const raw = fs.readFileSync(inputPath, "utf-8");
  const data = JSON.parse(raw);
  const messages = data.messages || [];

  if (messages.length === 0) {
    console.log("No messages to analyze");
    console.log(JSON.stringify({ metrics: {}, messageCount: 0 }));
    process.exit(0);
  }

  console.log(`Analyzing ${messages.length} messages...`);

  const stats = {
    messageCount: messages.length,
    query: data.query,
    fetchedAt: data.fetchedAt,
  };

  // Sender frequency
  if (requestedMetrics.includes("senders")) {
    const senderCounts = {};
    for (const msg of messages) {
      const sender = extractEmail(msg.from);
      senderCounts[sender] = (senderCounts[sender] || 0) + 1;
    }
    stats.senders = Object.entries(senderCounts)
      .sort((a, b) => b[1] - a[1])
      .map(([email, count]) => ({
        email,
        count,
        percentage: Math.round((count / messages.length) * 100),
      }));
    stats.uniqueSenders = stats.senders.length;
  }

  // Volume by date
  if (requestedMetrics.includes("volume")) {
    const byDate = {};
    const byDayOfWeek = {
      Sun: 0,
      Mon: 0,
      Tue: 0,
      Wed: 0,
      Thu: 0,
      Fri: 0,
      Sat: 0,
    };
    const days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];

    for (const msg of messages) {
      if (msg.date) {
        const d = new Date(msg.date);
        if (!isNaN(d.getTime())) {
          const dateKey = d.toISOString().split("T")[0];
          byDate[dateKey] = (byDate[dateKey] || 0) + 1;
          byDayOfWeek[days[d.getDay()]]++;
        }
      }
    }

    const sortedDates = Object.entries(byDate).sort((a, b) =>
      a[0].localeCompare(b[0])
    );
    const counts = sortedDates.map(([, c]) => c);

    stats.volume = {
      byDate: sortedDates.map(([date, count]) => ({ date, count })),
      byDayOfWeek,
      dailyAverage:
        counts.length > 0
          ? Math.round(counts.reduce((a, b) => a + b, 0) / counts.length)
          : 0,
      peakDay:
        sortedDates.length > 0
          ? sortedDates.reduce((a, b) => (b[1] > a[1] ? b : a))[0]
          : null,
      peakCount: counts.length > 0 ? Math.max(...counts) : 0,
    };
  }

  // Label distribution
  if (requestedMetrics.includes("labels")) {
    const labelCounts = {};
    for (const msg of messages) {
      for (const label of msg.labelIds || []) {
        labelCounts[label] = (labelCounts[label] || 0) + 1;
      }
    }
    stats.labels = Object.entries(labelCounts)
      .sort((a, b) => b[1] - a[1])
      .map(([label, count]) => ({ label, count }));
  }

  // Domain analysis
  if (requestedMetrics.includes("domains")) {
    const domainCounts = {};
    for (const msg of messages) {
      const email = extractEmail(msg.from);
      const domain = email.split("@")[1] || "unknown";
      domainCounts[domain] = (domainCounts[domain] || 0) + 1;
    }
    stats.domains = Object.entries(domainCounts)
      .sort((a, b) => b[1] - a[1])
      .map(([domain, count]) => ({
        domain,
        count,
        percentage: Math.round((count / messages.length) * 100),
      }));
  }

  // Hour of day distribution
  if (requestedMetrics.includes("hours")) {
    const byHour = {};
    for (let i = 0; i < 24; i++) byHour[i] = 0;

    for (const msg of messages) {
      if (msg.date) {
        const d = new Date(msg.date);
        if (!isNaN(d.getTime())) {
          byHour[d.getHours()]++;
        }
      }
    }

    const peakHour = Object.entries(byHour).reduce((a, b) =>
      b[1] > a[1] ? b : a
    );
    stats.hours = {
      distribution: byHour,
      peakHour: parseInt(peakHour[0]),
      peakHourCount: peakHour[1],
      businessHours: Object.entries(byHour)
        .filter(([h]) => parseInt(h) >= 9 && parseInt(h) < 17)
        .reduce((sum, [, c]) => sum + c, 0),
      afterHours: Object.entries(byHour)
        .filter(([h]) => parseInt(h) < 9 || parseInt(h) >= 17)
        .reduce((sum, [, c]) => sum + c, 0),
    };
  }

  // Response time analysis (basic - based on thread clustering)
  if (requestedMetrics.includes("response_times")) {
    const threadMessages = {};
    for (const msg of messages) {
      if (!threadMessages[msg.threadId]) {
        threadMessages[msg.threadId] = [];
      }
      threadMessages[msg.threadId].push(msg);
    }

    const threadSizes = Object.values(threadMessages).map(
      (msgs) => msgs.length
    );
    stats.threads = {
      total: Object.keys(threadMessages).length,
      averageLength:
        threadSizes.length > 0
          ? Math.round(
              (threadSizes.reduce((a, b) => a + b, 0) / threadSizes.length) * 10
            ) / 10
          : 0,
      maxLength: threadSizes.length > 0 ? Math.max(...threadSizes) : 0,
      singleMessage: threadSizes.filter((s) => s === 1).length,
      multiMessage: threadSizes.filter((s) => s > 1).length,
    };
  }

  // Output summary
  console.log(`\n✓ Analysis complete`);
  console.log(`  Total messages: ${stats.messageCount}`);

  if (stats.uniqueSenders) {
    console.log(`  Unique senders: ${stats.uniqueSenders}`);
    console.log(`  Top senders:`);
    stats.senders.slice(0, 5).forEach((s) => {
      console.log(`    - ${s.email}: ${s.count} (${s.percentage}%)`);
    });
  }

  if (stats.volume) {
    console.log(`  Daily average: ${stats.volume.dailyAverage} emails`);
    console.log(
      `  Peak day: ${stats.volume.peakDay} (${stats.volume.peakCount} emails)`
    );
  }

  if (stats.threads) {
    console.log(`  Threads: ${stats.threads.total}`);
    console.log(`  Avg thread length: ${stats.threads.averageLength}`);
  }

  console.log("\n--- Full Stats ---");
  console.log(JSON.stringify(stats, null, 2));
} catch (error) {
  console.error("Error computing stats:", error.message);
  throw error;
}

function extractEmail(fromHeader) {
  if (!fromHeader) return "unknown";
  const match = fromHeader.match(/<([^>]+)>/);
  return match ? match[1].toLowerCase() : fromHeader.toLowerCase().trim();
}