get_statistics
Retrieve journal statistics and analytics by specifying time grouping and optionally including top tags. Use this tool to analyze journal data trends and insights efficiently.
Instructions
Get journal statistics and analytics
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| includeTopTags | No | Include top tags in statistics | |
| journal | No | Journal name (uses current/default if not specified) | |
| timeGrouping | No | Group statistics by time period |
Implementation Reference
- src/handlers/statisticsHandlers.ts:19-99 (handler)The core handler function for the 'get_statistics' tool. It fetches journal data using jrnl stats, parses it, and computes statistics including totals, averages, time-grouped stats, and top tags.export async function getStatistics( journal: string | undefined, timeGrouping: string | undefined, includeTopTags: boolean, executor: JrnlExecutor, ): Promise<{ statistics: JournalStatistics }> { const command = buildStatsCommand(journal); const result = await executor.execute(command); try { const data = JSON.parse(result); const entries = data.entries || []; // Calculate basic statistics const totalEntries = entries.length; const totalWords = entries.reduce((sum: number, entry: any) => { const bodyWords = entry.body ? entry.body.split(/\s+/).length : 0; const titleWords = entry.title ? entry.title.split(/\s+/).length : 0; return sum + bodyWords + titleWords; }, 0); const averageWordsPerEntry = totalEntries > 0 ? Math.round(totalWords / totalEntries) : 0; const statistics: JournalStatistics = { totalEntries, totalWords, averageWordsPerEntry, }; // Time grouping statistics if (timeGrouping) { const grouping = parseTimeGrouping(timeGrouping); const grouped = groupEntriesByTime(entries, grouping); statistics.timeGrouping = Object.entries(grouped).map( ([period, entries]) => { const wordCount = entries.reduce((sum: number, entry: any) => { const bodyWords = entry.body ? entry.body.split(/\s+/).length : 0; const titleWords = entry.title ? entry.title.split(/\s+/).length : 0; return sum + bodyWords + titleWords; }, 0); return { period, entryCount: entries.length, wordCount, }; }, ); } // Top tags if (includeTopTags) { const tagCounts = new Map<string, number>(); entries.forEach((entry: any) => { if (entry.tags) { entry.tags.forEach((tag: string) => { const normalizedTag = tag.startsWith("@") ? tag : `@${tag}`; tagCounts.set( normalizedTag, (tagCounts.get(normalizedTag) || 0) + 1, ); }); } }); statistics.topTags = Array.from(tagCounts.entries()) .map(([tag, count]) => ({ tag, count })) .sort((a, b) => b.count - a.count) .slice(0, 10); // Top 10 tags } return { statistics }; } catch (error) { throw new Error(`Failed to calculate statistics: ${error}`); } }
- Type definitions for the output of get_statistics, including JournalStatistics and TimeGroupStats interfaces.export interface TimeGroupStats { period: string; entryCount: number; wordCount: number; } export interface JournalStatistics { totalEntries: number; totalWords: number; averageWordsPerEntry: number; timeGrouping?: TimeGroupStats[]; topTags?: Array<{ tag: string; count: number }>; }
- src/index.ts:98-119 (registration)Registration of the 'get_statistics' tool in the MCP server's listTools handler, defining name, description, and input schema.{ name: "get_statistics", description: "Get journal statistics and analytics", inputSchema: { type: "object", properties: { journal: { type: "string", description: "Journal name (uses current/default if not specified)", }, timeGrouping: { type: "string", enum: ["day", "week", "month", "year"], description: "Group statistics by time period", }, includeTopTags: { type: "boolean", description: "Include top tags in statistics", }, }, }, },
- Helper function used by getStatistics to group entries by specified time periods (daily, weekly, monthly, yearly).function groupEntriesByTime( entries: any[], grouping: string, ): Record<string, any[]> { const grouped: Record<string, any[]> = {}; entries.forEach((entry: any) => { const date = new Date(entry.date); let key: string; switch (grouping) { case "daily": key = date.toISOString().split("T")[0]; break; case "weekly": { // Get week number const weekNum = getWeekNumber(date); key = `${date.getFullYear()}-W${weekNum}`; break; } case "monthly": key = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}`; break; case "yearly": key = date.getFullYear().toString(); break; default: key = date.toISOString().split("T")[0]; } if (!grouped[key]) { grouped[key] = []; } grouped[key].push(entry); }); return grouped; }
- Helper function to compute ISO week number for dates, used in weekly grouping.function getWeekNumber(date: Date): number { const d = new Date( Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()), ); const dayNum = d.getUTCDay() || 7; d.setUTCDate(d.getUTCDate() + 4 - dayNum); const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1)); return Math.ceil(((d.getTime() - yearStart.getTime()) / 86400000 + 1) / 7); }