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();
}