# How the Year in Review 2025 Feature Calculates Claude Session Statistics: A Deep Dive into the Analytics Engine

> Discover how the Year in Review 2025 feature calculates Claude session statistics using deterministic aggregation of local logs. Learn about token usage, model distribution, and more without API calls.

- Repository: [Daniel Avila/claude-code-templates](https://github.com/davila7/claude-code-templates)
- Tags: deep-dive
- Published: 2026-04-26

---

**The Year in Review 2025 feature calculates Claude session statistics by filtering local conversation logs to the 2025 calendar year and applying deterministic aggregation methods in [`cli-tool/src/analytics/core/YearInReview2025.js`](https://github.com/davila7/claude-code-templates/blob/main/cli-tool/src/analytics/core/YearInReview2025.js) to derive metrics including token usage, model distribution, activity streaks, and tool utilization without external API calls.**

The `davila7/claude-code-templates` repository includes a comprehensive analytics engine that powers the Year in Review 2025 dashboard. This feature processes raw conversation data stored locally in the user's `.claude` directory to generate detailed statistics about coding sessions, productivity patterns, and AI model utilization. Every metric is computed through pure local analysis of JSON conversation logs, ensuring complete data privacy and offline functionality.

## Entry Point and Core Architecture

The analytics pipeline centers on the `YearInReview2025` class defined in [`cli-tool/src/analytics/core/YearInReview2025.js`](https://github.com/davila7/claude-code-templates/blob/main/cli-tool/src/analytics/core/YearInReview2025.js). When the CLI receives the `--2025` flag, it instantiates this class and invokes the `generateYearInReview()` method, passing the complete array of conversation records and the path to the user's `.claude` configuration directory.

The constructor initializes the analyzer, and the primary method orchestrates the entire computation workflow. It receives conversation data from the analytics engine and returns a comprehensive statistics object that powers both the terminal summary and the static HTML visualization.

## Data Filtering and Preparation

Before calculating statistics, the engine performs two critical preparation steps:

- **`filterConversations2025()`** parses the `lastModified` timestamp of each conversation, retaining only those falling between `2025-01-01` and `2025-12-31`. This creates the working dataset for all subsequent calculations.
- **`detectInstalledComponents()`** scans the `~/.claude/local` directory to identify installed MCPs (Model Context Protocols) and enabled plugins, providing metadata for the component installation visualization used by tools like Gource.

## Statistical Computation Methods

The analyzer derives every dashboard metric through deterministic aggregation functions operating on the filtered 2025 conversation array.

### Conversation Volume and Model Distribution

The foundation of the statistics starts with simple array operations:

- **`totalConversations`** is calculated as `conversations2025.length`, providing the raw count of sessions in the target year.
- **`analyzeModelUsage()`** iterates through the conversation array, counting occurrences of each primary model name. It formats model identifiers (e.g., converting internal codes to "Claude 3.5 Sonnet") and returns the top three most frequently used models with their respective percentages.

### Token Usage Calculations

The **`calculateTokenUsage()`** method performs granular token accounting:

```javascript
// Sums tokenUsage or falls back to conv.tokens
const totalTokens = conversations2025.reduce((sum, conv) => {
  return sum + (conv.tokenUsage || conv.tokens || 0);
}, 0);

// Breaks into input, output, and cache categories
// Returns formatted strings like "1.2B" or "450M"

```

This method aggregates `tokenUsage` objects (or falls back to flat `tokens` values) across all conversations, categorizing consumption into input, output, and cached tokens. The results are formatted into human-readable billions or millions for display.

### Activity Streaks and Heatmaps

Activity tracking relies on date mathematics:

- **`calculateStreak()`** constructs a `Set` of distinct activity dates from conversation timestamps, sorts them chronologically, then walks the array to identify consecutive day sequences. It tracks both the longest historical streak and the current active streak ending on the analysis date.
- **`generateActivityHeatmap()`** creates a GitHub-style 52-week visualization grid. For each day, it records conversation count, token sums, tool usage statistics, and model breakdowns. It assigns intensity levels 0 through 4 based on daily conversation volume, producing the color-coded annual activity map.

### Tool, Agent, and MCP Metrics

The analyzer tracks component utilization through specialized counters:

- **`countTools()`**, **`countAgents()`**, and **`countMCPs()`** return simple aggregations with formatted string outputs (e.g., "1.2K" for large numbers).
- **`analyzeToolUsage()`** performs deeper analysis by totaling `totalToolCalls` across conversations and aggregating per-tool statistics from `conv.toolUsage.toolStats`. It returns the grand total alongside the five most frequently invoked tools.

### Temporal and Project Analysis

Time-based patterns emerge through bucketing algorithms:

- **`analyzeTimeOfDay()`** extracts the hour from each conversation's `lastModified` timestamp, buckets sessions into 24 hourly slots, and identifies the peak activity hour.
- **`analyzeTopProjects()`** groups conversations by the `conv.project` field (defaulting to "Unknown" for untagged sessions). It counts conversations and sums tokens per project, returning the five most active projects ranked by engagement volume.

### Insights and Local Component History

Additional context comes from filesystem analysis:

- **`generateInsights()`** calculates overall message counts, averages messages per conversation, and identifies the single most productive day (the date with the highest conversation count).
- **`analyzeCommands()`**, **`analyzeSkills()`**, **`analyzeMCPs()`**, and **`analyzeSubagents()`** operate as asynchronous helpers that read history files from the `.claude` directory. These methods parse command logs, skill invocations, MCP interactions, and subagent spawns, returning usage counts and timestamped event arrays.

## Parallel Processing and Result Assembly

The computation workflow maximizes efficiency through parallelization:

1. **Async Component Analysis**: Commands, skills, MCPs, and subagent data are fetched concurrently using `Promise.all()`, preventing filesystem I/O from blocking the main thread.
2. **Synchronous Aggregation**: Core conversation statistics (tokens, streaks, time-of-day) compute synchronously on the filtered array.
3. **Final Assembly**: All results merge into a single `stats` object containing keys for `totalConversations`, `models`, `tokens`, `streak`, `activityHeatmap`, `topProjects`, `toolUsage`, and component-specific data.

The assembled statistics object returns to the CLI entry point in [`cli-tool/src/index.js`](https://github.com/davila7/claude-code-templates/blob/main/cli-tool/src/index.js), which writes the JSON output and generates the static HTML report using the template in [`cli-tool/src/analytics-web/2025.html`](https://github.com/davila7/claude-code-templates/blob/main/cli-tool/src/analytics-web/2025.html).

### Example: Invoking the Analyzer

```javascript
const YearInReview2025 = require('./analytics/core/YearInReview2025');
const analyzer = new YearInReview2025();

(async () => {
  const allConvs = await analytics.getAllConversations();
  const stats = await analyzer.generateYearInReview(allConvs, '/home/user/.claude');
  
  console.log(`Analyzed ${stats.totalConversations} conversations`);
  console.log('Top model:', stats.models[0].name);
})();

```

### Example: Streak Calculation Logic

```javascript
const sortedDays = Array.from(activeDays).sort();
let longest = 1, current = 1;

for (let i = 1; i < sortedDays.length; i++) {
  const diff = (new Date(sortedDays[i]) - new Date(sortedDays[i-1])) / (1000*60*60*24);
  if (diff === 1) { 
    current++; 
    longest = Math.max(longest, current); 
  } else { 
    current = 1; 
  }
}

```

## Summary

- The **Year in Review 2025** feature operates entirely on local conversation logs, requiring no external API connectivity or cloud services.
- All calculations occur in [`cli-tool/src/analytics/core/YearInReview2025.js`](https://github.com/davila7/claude-code-templates/blob/main/cli-tool/src/analytics/core/YearInReview2025.js), with entry point `generateYearInReview()` orchestrating the analysis pipeline.
- The engine filters conversations to the 2025 calendar year using `filterConversations2025()` before applying deterministic aggregation methods.
- Statistics range from simple counts (`totalConversations`) to complex temporal analysis (`calculateStreak()`, `analyzeTimeOfDay()`).
- Parallel async operations fetch command history, skill usage, and MCP data from the local `.claude` directory while synchronous methods process conversation arrays.
- Output feeds a static HTML template to render the visual dashboard, with all data formatted for human readability (e.g., "1.2B tokens", "1.2K tools").

## Frequently Asked Questions

### Where does the Year in Review 2025 feature get its data?

The feature reads from local conversation logs stored in the user's `.claude` directory. It accesses JSON conversation files, history logs, and local component manifests to build a complete picture of activity without transmitting data to external servers.

### Can the statistics calculation work offline?

Yes. Since the analyzer in [`cli-tool/src/analytics/core/YearInReview2025.js`](https://github.com/davila7/claude-code-templates/blob/main/cli-tool/src/analytics/core/YearInReview2025.js) processes only local files and conversation arrays, it functions completely offline. The deterministic calculations require no network access once the conversation data resides on the filesystem.

### How does the feature determine the "most productive day"?

The `generateInsights()` method creates a histogram of conversation counts by date, then identifies the date with the maximum value. This single day represents the highest volume of Claude interactions during the 2025 calendar year.

### What distinguishes the token calculation from simple message counting?

The `calculateTokenUsage()` method accesses granular token metadata within each conversation record, distinguishing between input tokens, output tokens, and cache hits. This provides accurate resource consumption metrics rather than approximating based on message length or conversation count.