<aside> 🔐

Ever wondered how you actually use ChatGPT — what topics you discuss most, how your usage has evolved over time, or how deeply you tend to engage in each session?

You can download your conversation history from ChatGPT and run a simple analysis script to uncover patterns like:

Just export your data from ChatGPT, run a python script over it, and you’ll get a csv file that you can analyze further in tools like Google Sheets, Excel, or back in ChatGPT

The result: a personalized look at how you think, learn, and create with AI — over time.

</aside>

Table of Contents

Step 1: Download Your Conversation History

To download your ChatGPT conversation history, click on your profile picture in the bottom-left corner of the ChatGPT browser interface. Then go to Settings → Data Controls → Export Data. ChatGPT will email you a link to download a .zip file containing all your conversations in JSON format. Once you’ve downloaded and unzipped the file, you’ll find a folder that includes conversations.json — that’s the file you’ll use for analysis.

<aside> ✅

Save the conversations.json file in a folder and make a note of the file path. E.g. /Users/yourname/Downloads/chatgpt-export/conversations.json

</aside>

Step 2: Modify a Python Script to Analyze Your Data

Most people’s ChatGPT conversation history is too large for the app to analyze in a single prompt. Instead, we’ll run a Python script locally — which keeps your data private — to process each conversation, automatically categorize it, and build a clean dataset that’s easy to explore in tools like Google Sheets or Excel.

Below, you'll find a template Python script and a prompt you can paste into an LLM to customize it for your setup—including file paths, categories, and outputs. Paste both in a single prompt and simply replace the placeholders.

Prompt

<aside> 💬

I need you to customize this ChatGPT conversation analyzer script for my specific use case.

My ChatGPT Export File Location: [REPLACE THIS: Enter the full path to your conversations.json file] Example: /Users/myname/Downloads/conversations.json

Types of Conversations I Have: [REPLACE THIS: Describe what you typically use ChatGPT for - be specific about topics] Example: I'm a software engineer and I use ChatGPT for debugging Python code, learning about web development, writing SQL queries, and occasionally for personal things like recipes and travel planning.

My Preferred Insights: [OPTIONAL - REPLACE THIS: If you have specific category names you want, list them here. Otherwise, the LLM will create appropriate ones based on your description above] Example: Most used word, Work vs. Personal %s, Most active day, Increase in activity over the course of 2025

Please take the attached Python template and:

  1. Replace {{JSON_FILE_PATH}} with my file path
  2. Replace {{CATEGORIES_PLACEHOLDER}} with relevant categories and keywords based on my usage
  3. Return a complete, ready-to-run Python script </aside>

Template Code

#!/usr/bin/env python3
"""
ChatGPT Wrapped Analyzer
This script analyzes your ChatGPT usage and generates "Wrapped" style statistics
"""

import json
import pandas as pd
import re
from collections import Counter
from datetime import datetime
import os

# ============================================================================
# CONFIGURATION - TO BE CUSTOMIZED BY LLM BASED ON USER REQUIREMENTS
# ============================================================================

# REPLACE THIS: Insert the file path from user's prompt
JSON_FILE_PATH = "{{JSON_FILE_PATH}}"

# REPLACE THIS: Which year to analyze (2024 or 2025)
ANALYSIS_YEAR = {{YEAR}}

# REPLACE THIS (OPTIONAL): Add custom keywords for "work output" detection
# These help identify conversations where you're creating deliverables vs just exploring
WORK_OUTPUT_KEYWORDS = [
    'draft', 'create', 'write', 'build', 'design', 'analyze', 
    'review', 'edit', 'help', 'feedback', 'improve', 'generate',
    'post', 'email', 'deck', 'presentation', 'report', 'document',
    'script', 'code', 'analysis', 'strategy', 'make', 'better'
    # ADD YOUR OWN: {{CUSTOM_WORK_KEYWORDS}}
]

# REPLACE THIS (OPTIONAL): Add custom keywords for "advice-seeking" conversations
ADVICE_KEYWORDS = [
    r'\\bshould i\\b',
    r'\\badvice\\b',
    r'\\bwhat would you\\b',
    r'\\bam i overthinking\\b',
    r'\\bam i wrong\\b',
    r'\\bis this a good idea\\b',
    r'\\bhelp me decide\\b',
    r'\\bwhat do you think\\b',
    r'\\bdoes this make sense\\b',
    r'\\bthoughts\\?\\b',
    r'\\bopinion\\b',
    r'\\bwhat should\\b'
    # ADD YOUR OWN: {{CUSTOM_ADVICE_KEYWORDS}}
]

# ============================================================================
# MAIN SCRIPT - NO CHANGES NEEDED BELOW THIS LINE
# ============================================================================

def load_and_process_json(json_path):
    """Load conversations.json and extract all data"""
    print(f"Loading conversations from {json_path}...")
    
    if not os.path.exists(json_path):
        raise FileNotFoundError(f"File not found: {json_path}")
    
    with open(json_path, 'r', encoding='utf-8') as f:
        conversations = json.load(f)
    
    print(f"  → Loaded {len(conversations)} total conversations")
    
    processed = []
    
    for conv in conversations:
        conv_data = {
            'conversation_id': conv.get('id'),
            'title': conv.get('title', 'Untitled'),
            'create_time': conv.get('create_time'),
            'update_time': conv.get('update_time'),
            'user_messages': 0,
            'assistant_messages': 0,
            'user_text': '',
            'assistant_text': ''
        }
        
        # Extract messages from mapping
        mapping = conv.get('mapping', {})
        for node_id, node in mapping.items():
            if node.get('message') and node['message'].get('content'):
                author = node['message'].get('author', {}).get('role')
                parts = node['message']['content'].get('parts', [])
                
                # Extract text from parts
                text = ''
                for part in parts:
                    if isinstance(part, str):
                        text += part + ' '
                
                text = text.strip()
                
                if text and author == 'user':
                    conv_data['user_messages'] += 1
                    conv_data['user_text'] += text + ' '
                elif text and author == 'assistant':
                    conv_data['assistant_messages'] += 1
                    conv_data['assistant_text'] += text + ' '
        
        # Only include conversations with user messages
        if conv_data['user_messages'] > 0:
            processed.append(conv_data)
    
    return processed

def filter_to_year(conversations, year):
    """Filter conversations to specified year only"""
    filtered = []
    for conv in conversations:
        if conv['create_time']:
            try:
                dt = datetime.fromtimestamp(conv['create_time'])
                if dt.year == year:
                    conv['datetime'] = dt
                    filtered.append(conv)
            except:
                pass
    
    print(f"  → Filtered to {year}: {len(filtered)} conversations")
    if filtered:
        dates = [c['datetime'] for c in filtered]
        print(f"  → Date range: {min(dates).date()} to {max(dates).date()}")
    
    return filtered

def calculate_basic_stats(conversations):
    """Calculate basic message statistics"""
    total_user_messages = sum(c['user_messages'] for c in conversations)
    total_conversations = len(conversations)
    
    # Get date range
    dates = [c['datetime'].date() for c in conversations]
    unique_dates = set(dates)
    date_range_days = (max(dates) - min(dates)).days + 1
    
    return {
        'total_messages': total_user_messages,
        'total_conversations': total_conversations,
        'active_days': len(unique_dates),
        'messages_per_day': round(total_user_messages / len(unique_dates), 1),
        'messages_per_conversation': round(total_user_messages / total_conversations, 1),
        'date_range': f"{min(dates).strftime('%B %d, %Y')} to {max(dates).strftime('%B %d, %Y')}"
    }

def calculate_politeness(conversations):
    """Count please and thank you"""
    all_user_text = ' '.join(c['user_text'].lower() for c in conversations)
    
    please_count = len(re.findall(r'\\bplease\\b', all_user_text))
    thank_pattern = r'\\bthank\\b|\\bthanks\\b|\\bthank you\\b'
    thank_count = len(re.findall(thank_pattern, all_user_text))
    
    total_messages = sum(c['user_messages'] for c in conversations)
    
    return {
        'please_count': please_count,
        'thank_count': thank_count,
        'total_politeness': please_count + thank_count,
        'politeness_per_message': round((please_count + thank_count) / total_messages * 100, 1) if total_messages > 0 else 0
    }

def calculate_life_coach_stats(conversations):
    """Detect advice-seeking conversations"""
    life_coach_count = 0
    for conv in conversations:
        text = conv['user_text'].lower()
        if any(re.search(pattern, text) for pattern in ADVICE_KEYWORDS):
            life_coach_count += 1
    
    return {
        'life_coach_conversations': life_coach_count,
        'life_coach_percentage': round(life_coach_count / len(conversations) * 100, 1)
    }

def calculate_most_used_words(conversations):
    """Find most commonly used words"""
    stopwords = {
        'the', 'a', 'an', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for', 
        'of', 'with', 'by', 'from', 'up', 'about', 'into', 'through', 'during',
        'is', 'are', 'was', 'were', 'be', 'have', 'has', 'had', 'do', 'does', 
        'did', 'will', 'would', 'could', 'should', 'may', 'might', 'can', 
        'this', 'that', 'these', 'those', 'i', 'you', 'he', 'she', 'it', 
        'we', 'they', 'what', 'which', 'who', 'when', 'where', 'why', 'how',
        'my', 'your', 'me', 'him', 'her', 'us', 'them', 'there', 'their',
        'just', 'like', 'make', 'get', 'need', 'want', 'know', 'also',
        'chatgpt', 'please', 'thank', 'thanks', 'hi', 'hello', 'hey'
    }
    
    all_text = ' '.join(c['user_text'].lower() for c in conversations)
    words = re.findall(r'\\b\\w+\\b', all_text)
    
    filtered = [w for w in words if w not in stopwords and len(w) > 3]
    word_counts = Counter(filtered).most_common(10)
    
    return {
        'top_word': word_counts[0][0] if word_counts else 'N/A',
        'top_word_count': word_counts[0][1] if word_counts else 0,
        'top_10': word_counts
    }

def calculate_refinement_stats(conversations):
    """Calculate refinement and work output stats"""
    work_output_count = 0
    work_output_iterations = []
    
    for conv in conversations:
        is_work = False
        
        # Check title and text for work keywords
        title_text = (conv['title'] + ' ' + conv['user_text'][:500]).lower()
        if any(keyword in title_text for keyword in WORK_OUTPUT_KEYWORDS):
            if conv['user_messages'] >= 3:  # Must have refinement
                is_work = True
        
        if is_work:
            work_output_count += 1
            work_output_iterations.append(conv['user_messages'])
    
    avg_work_iterations = sum(work_output_iterations) / len(work_output_iterations) if work_output_iterations else 0
    
    # Overall stats
    all_iterations = [c['user_messages'] for c in conversations]
    refinement_3plus = sum(1 for i in all_iterations if i >= 3)
    refinement_6plus = sum(1 for i in all_iterations if i >= 6)
    
    # Longest conversation
    longest = max(conversations, key=lambda c: c['user_messages'])
    
    return {
        'avg_iterations_all': round(sum(all_iterations) / len(all_iterations), 1),
        'avg_iterations_work': round(avg_work_iterations, 1),
        'work_output_count': work_output_count,
        'work_output_pct': round(work_output_count / len(conversations) * 100, 1),
        'refinement_3plus_pct': round(refinement_3plus / len(conversations) * 100, 1),
        'refinement_6plus_pct': round(refinement_6plus / len(conversations) * 100, 1),
        'longest_messages': longest['user_messages'],
        'longest_title': longest['title']
    }

def calculate_time_patterns(conversations):
    """Calculate time-based patterns"""
    late_night = sum(1 for c in conversations if c['datetime'].hour >= 23 or c['datetime'].hour <= 5)
    weekends = sum(1 for c in conversations if c['datetime'].weekday() >= 5)
    
    # Busiest month
    months = [c['datetime'].strftime('%Y-%m') for c in conversations]
    month_counts = Counter(months)
    busiest_month = month_counts.most_common(1)[0]
    
    # Busiest day of week
    days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
    weekdays = [days[c['datetime'].weekday()] for c in conversations]
    day_counts = Counter(weekdays)
    busiest_day = day_counts.most_common(1)[0]
    
    return {
        'late_night_count': late_night,
        'late_night_pct': round(late_night / len(conversations) * 100, 1),
        'weekend_count': weekends,
        'weekend_pct': round(weekends / len(conversations) * 100, 1),
        'busiest_month': busiest_month[0],
        'busiest_month_count': busiest_month[1],
        'busiest_day': busiest_day[0],
        'busiest_day_count': busiest_day[1]
    }

def generate_report(metrics, year):
    """Generate formatted report"""
    print("\\n" + "="*80)
    print(" "*20 + f"CHATGPT WRAPPED {year} - FULL ANALYSIS")
    print("="*80 + "\\n")
    
    print("📊 BASIC STATS")
    print("-"*80)
    print(f"Date Range: {metrics['basic']['date_range']}")
    print(f"Total Messages: {metrics['basic']['total_messages']:,}")
    print(f"Total Conversations: {metrics['basic']['total_conversations']:,}")
    print(f"Messages Per Day: {metrics['basic']['messages_per_day']}")
    print(f"Messages Per Conversation: {metrics['basic']['messages_per_conversation']}")
    print()
    
    print("🙏 POLITENESS")
    print("-"*80)
    print(f"'Please' count: {metrics['politeness']['please_count']}")
    print(f"'Thank you' count: {metrics['politeness']['thank_count']}")
    print(f"Total politeness: {metrics['politeness']['total_politeness']} ({metrics['politeness']['politeness_per_message']}% of messages)")
    print()
    
    print("🔄 REFINEMENT & WORK OUTPUT")
    print("-"*80)
    print(f"Average iterations (all): {metrics['refinement']['avg_iterations_all']}")
    print(f"Work output conversations: {metrics['refinement']['work_output_count']} ({metrics['refinement']['work_output_pct']}%)")
    print(f"Average iterations (work): {metrics['refinement']['avg_iterations_work']}")
    print(f"3+ iterations: {metrics['refinement']['refinement_3plus_pct']}%")
    print(f"6+ iterations: {metrics['refinement']['refinement_6plus_pct']}%")
    print(f"Longest: {metrics['refinement']['longest_messages']} messages - '{metrics['refinement']['longest_title']}'")
    print()
    
    print("💭 LIFE COACH SESSIONS")
    print("-"*80)
    print(f"Advice-seeking conversations: {metrics['life_coach']['life_coach_conversations']} ({metrics['life_coach']['life_coach_percentage']}%)")
    print()
    
    print("💬 MOST USED WORDS")
    print("-"*80)
    print(f"Top word: '{metrics['words']['top_word']}' ({metrics['words']['top_word_count']} times)")
    print("Top 10:")
    for i, (word, count) in enumerate(metrics['words']['top_10'], 1):
        print(f"  {i}. {word}: {count}")
    print()
    
    print("⏰ TIME PATTERNS")
    print("-"*80)
    print(f"Late night (11pm-5am): {metrics['time']['late_night_count']} ({metrics['time']['late_night_pct']}%)")
    print(f"Weekends: {metrics['time']['weekend_count']} ({metrics['time']['weekend_pct']}%)")
    print(f"Busiest month: {metrics['time']['busiest_month']} ({metrics['time']['busiest_month_count']} conversations)")
    print(f"Busiest day: {metrics['time']['busiest_day']} ({metrics['time']['busiest_day_count']} conversations)")
    print()
    
    print("="*80)

def validate_configuration():
    """Validate that all required configuration is properly set"""
    errors = []
    
    # Check file path
    if JSON_FILE_PATH == "{{JSON_FILE_PATH}}" or not JSON_FILE_PATH:
        errors.append("JSON_FILE_PATH must be set to the path of your conversations.json file")
    
    # Check year
    if str(ANALYSIS_YEAR) == "{{YEAR}}" or not isinstance(ANALYSIS_YEAR, int):
        errors.append("ANALYSIS_YEAR must be set to the year you want to analyze (e.g., 2024 or 2025)")
    
    if errors:
        print("\\n❌ Configuration Errors Found:")
        print("-" * 40)
        for error in errors:
            print(f"  • {error}")
        print("\\nPlease update the configuration section at the top of this script.")
        return False
    
    return True

def main():
    """Main execution function"""
    print("\\n" + "="*80)
    print(" "*25 + "ChatGPT Wrapped Analyzer")
    print("="*80)
    
    # Validate configuration
    if not validate_configuration():
        return
    
    try:
        # Load and process
        conversations = load_and_process_json(JSON_FILE_PATH)
        conversations_filtered = filter_to_year(conversations, ANALYSIS_YEAR)
        
        if not conversations_filtered:
            print(f"❌ No {ANALYSIS_YEAR} conversations found!")
            return
        
        print("\\nCalculating metrics...")
        
        metrics = {
            'basic': calculate_basic_stats(conversations_filtered),
            'politeness': calculate_politeness(conversations_filtered),
            'refinement': calculate_refinement_stats(conversations_filtered),
            'life_coach': calculate_life_coach_stats(conversations_filtered),
            'words': calculate_most_used_words(conversations_filtered),
            'time': calculate_time_patterns(conversations_filtered)
        }
        
        generate_report(metrics, ANALYSIS_YEAR)
        
        # Generate ChatGPT prompt
        print("\\n" + "="*80)
        print("📋 CHATGPT PROMPT - Copy and paste this into ChatGPT to generate carousel copy:")
        print("="*80 + "\\n")
        
        prompt = f"""I have these data points from my ChatGPT usage in {ANALYSIS_YEAR}. Generate 8-10 catchy, viral-worthy carousel slides in the style of Spotify Wrapped/Granola Wrapped. Each slide should have a headline and subtext that's funny, relatable, and impressive.

**Data Points ({ANALYSIS_YEAR} - {metrics['basic']['date_range']}):**
- {metrics['basic']['total_messages']:,} total messages
- {metrics['basic']['messages_per_day']} messages per day
- {metrics['basic']['total_conversations']:,} conversations
- {metrics['basic']['messages_per_conversation']} messages per conversation
- {metrics['refinement']['work_output_pct']}% were work output ({metrics['refinement']['work_output_count']} conversations)
- Work output averaged {metrics['refinement']['avg_iterations_work']} iterations
- {metrics['refinement']['refinement_3plus_pct']}% had 3+ back-and-forths
- Longest: {metrics['refinement']['longest_messages']} messages
- Said 'please' {metrics['politeness']['please_count']} times, 'thank you' {metrics['politeness']['thank_count']} times
- {metrics['life_coach']['life_coach_percentage']}% were advice-seeking
- Most-used word: '{metrics['words']['top_word']}'
- {metrics['time']['late_night_pct']}% after 11pm
- Busiest: {metrics['time']['busiest_day']}s

Create slides that would make people stop scrolling. Mix impressive numbers with humor."""
        
        print(prompt)
        print("\\n" + "="*80)
        
        # Save results to output directory
        output_dir = os.path.dirname(JSON_FILE_PATH)
        output_file = os.path.join(output_dir, f'chatgpt_wrapped_{ANALYSIS_YEAR}_results.txt')
        
        with open(output_file, 'w') as f:
            f.write(f"ChatGPT Wrapped {ANALYSIS_YEAR} Results\\n")
            f.write("="*80 + "\\n\\n")
            f.write(f"Total Messages: {metrics['basic']['total_messages']:,}\\n")
            f.write(f"Messages Per Day: {metrics['basic']['messages_per_day']}\\n")
            f.write(f"Work Output: {metrics['refinement']['work_output_pct']}%\\n")
            f.write(f"Most-Used Word: {metrics['words']['top_word']}\\n")
            f.write(f"Politeness: {metrics['politeness']['total_politeness']} please/thank yous\\n")
            f.write(f"Busiest Day: {metrics['time']['busiest_day']}\\n\\n")
            f.write("Full prompt for ChatGPT:\\n")
            f.write("-"*80 + "\\n")
            f.write(prompt)
        
        print(f"\\n✅ Results saved to: {output_file}")
        
    except FileNotFoundError as e:
        print(f"\\n❌ Error: {e}")
        print("Please check that the JSON file path is correct.")
    except json.JSONDecodeError as e:
        print(f"\\n❌ Error parsing JSON file: {e}")
        print("Please ensure the file is a valid ChatGPT export.")
    except Exception as e:
        print(f"\\n❌ Unexpected error: {e}")
        import traceback
        traceback.print_exc()

if __name__ == "__main__":
    main()

Step 3: Check Your Python Setup

Before we run the script that we created, make sure you have Python 3 and the required dependencies installed.

  1. Check if Python 3 is installed

    Open your terminal (or iTerm / Warp) and run:

    python3 --version
    

    You should see something like Python 3.9.6 or higher.

    If you get “command not found,” download it from python.org/downloads and then re-run the command.

  2. Install the dependencies

    Once Python is ready, install the required libraries:

    python3 -m pip install pandas
    

    (This also installs numpy, which pandas depends on.)

You only need to do this once — after that, you can run the analyzer script anytime.

Step 4: Run Python Script Locally

Follow these steps to run your ChatGPT analysis on your own computer: