Chapter 9: Analytics and Reporting

Data-Driven Decision Making

The Etsy API provides access to rich data about your shop’s performance. By extracting, analyzing, and visualizing this data, you can make informed decisions about pricing, product development, and marketing strategies.

Core Analytics Module

# etsy_client/analytics.py
"""Analytics and reporting for Etsy shops."""

from typing import Dict, Any, List, Optional, Tuple
from datetime import datetime, timedelta
from dataclasses import dataclass
from collections import defaultdict
import statistics
from .client import EtsyClient
from .listings import ListingOperations
from .orders import OrderOperations


@dataclass
class ListingMetrics:
    """Metrics for a single listing."""
    listing_id: int
    title: str
    views: int
    favorites: int
    sales: int
    revenue: float
    price: float
    created_date: datetime
    
    @property
    def conversion_rate(self) -> float:
        return self.sales / self.views if self.views > 0 else 0
    
    @property
    def favorite_rate(self) -> float:
        return self.favorites / self.views if self.views > 0 else 0
    
    @property
    def revenue_per_view(self) -> float:
        return self.revenue / self.views if self.views > 0 else 0


@dataclass
class ShopMetrics:
    """Aggregate metrics for a shop."""
    total_listings: int
    active_listings: int
    total_views: int
    total_favorites: int
    total_sales: int
    total_revenue: float
    avg_price: float
    avg_conversion_rate: float
    period_start: datetime
    period_end: datetime


class ShopAnalytics:
    """
    Analytics engine for Etsy shop data.
    
    Provides methods to analyze listings, sales, and performance.
    """
    
    def __init__(self, client: EtsyClient, shop_id: int):
        self.client = client
        self.shop_id = shop_id
        self.listing_ops = ListingOperations(client)
        self.order_ops = OrderOperations(client)
    
    def get_listing_metrics(
        self,
        listing_id: int,
        days: int = 30
    ) -> ListingMetrics:
        """
        Get metrics for a single listing.
        
        Args:
            listing_id: The listing ID
            days: Period to analyze (for sales)
            
        Returns:
            ListingMetrics object
        """
        listing = self.listing_ops.get_listing(listing_id)
        
        # Get sales
        transactions = self.order_ops.get_listing_transactions(
            self.shop_id, listing_id, limit=100
        )
        
        # Filter by date
        min_date = datetime.now() - timedelta(days=days)
        recent_sales = []
        
        for txn in transactions.get('results', []):
            txn_date = datetime.fromtimestamp(txn.get('created_timestamp', 0))
            if txn_date >= min_date:
                recent_sales.append(txn)
        
        price = listing['price']['amount'] / listing['price']['divisor']
        revenue = sum(
            (t['price']['amount'] / t['price']['divisor']) * t.get('quantity', 1)
            for t in recent_sales
        )
        
        return ListingMetrics(
            listing_id=listing_id,
            title=listing['title'],
            views=listing.get('views', 0),
            favorites=listing.get('num_favorers', 0),
            sales=len(recent_sales),
            revenue=revenue,
            price=price,
            created_date=datetime.fromtimestamp(listing.get('created_timestamp', 0))
        )
    
    def get_all_listing_metrics(
        self,
        state: str = "active"
    ) -> List[ListingMetrics]:
        """
        Get metrics for all listings.
        
        Args:
            state: Listing state filter
            
        Returns:
            List of ListingMetrics
        """
        listings = self.listing_ops.get_all_listings(self.shop_id, state)
        
        metrics = []
        for listing in listings:
            price = listing['price']['amount'] / listing['price']['divisor']
            
            metrics.append(ListingMetrics(
                listing_id=listing['listing_id'],
                title=listing['title'],
                views=listing.get('views', 0),
                favorites=listing.get('num_favorers', 0),
                sales=0,  # Would need transactions for accurate count
                revenue=0,
                price=price,
                created_date=datetime.fromtimestamp(
                    listing.get('created_timestamp', 0)
                )
            ))
        
        return metrics
    
    def get_shop_metrics(
        self,
        days: int = 30
    ) -> ShopMetrics:
        """
        Get aggregate shop metrics.
        
        Args:
            days: Period to analyze
            
        Returns:
            ShopMetrics object
        """
        # Get listings
        active_listings = self.listing_ops.get_all_listings(self.shop_id, "active")
        
        # Calculate totals
        total_views = sum(l.get('views', 0) for l in active_listings)
        total_favorites = sum(l.get('num_favorers', 0) for l in active_listings)
        
        prices = [
            l['price']['amount'] / l['price']['divisor'] 
            for l in active_listings
        ]
        avg_price = statistics.mean(prices) if prices else 0
        
        # Get recent orders
        min_date = datetime.now() - timedelta(days=days)
        receipts = self.order_ops.get_all_receipts(
            self.shop_id, min_created=min_date
        )
        
        total_revenue = sum(
            r.get('grandtotal', {}).get('amount', 0) / 
            r.get('grandtotal', {}).get('divisor', 100)
            for r in receipts
        )
        
        total_sales = len(receipts)
        
        # Estimate conversion rate
        avg_conversion = total_sales / total_views if total_views > 0 else 0
        
        return ShopMetrics(
            total_listings=len(active_listings),
            active_listings=len(active_listings),
            total_views=total_views,
            total_favorites=total_favorites,
            total_sales=total_sales,
            total_revenue=total_revenue,
            avg_price=avg_price,
            avg_conversion_rate=avg_conversion,
            period_start=min_date,
            period_end=datetime.now()
        )
    
    def top_performers(
        self,
        metric: str = "revenue",
        limit: int = 10
    ) -> List[ListingMetrics]:
        """
        Get top performing listings by a metric.
        
        Args:
            metric: Metric to sort by (views, favorites, sales, revenue)
            limit: Number of results
            
        Returns:
            Sorted list of ListingMetrics
        """
        all_metrics = self.get_all_listing_metrics()
        
        sorted_metrics = sorted(
            all_metrics,
            key=lambda x: getattr(x, metric, 0),
            reverse=True
        )
        
        return sorted_metrics[:limit]
    
    def underperformers(
        self,
        min_age_days: int = 30,
        min_views: int = 100,
        max_conversion: float = 0.005
    ) -> List[ListingMetrics]:
        """
        Find underperforming listings.
        
        Args:
            min_age_days: Minimum listing age to consider
            min_views: Minimum views required
            max_conversion: Maximum conversion to be "underperforming"
            
        Returns:
            List of underperforming ListingMetrics
        """
        all_metrics = self.get_all_listing_metrics()
        cutoff_date = datetime.now() - timedelta(days=min_age_days)
        
        underperformers = []
        
        for m in all_metrics:
            if m.created_date > cutoff_date:
                continue  # Too new
            if m.views < min_views:
                continue  # Not enough data
            if m.conversion_rate <= max_conversion:
                underperformers.append(m)
        
        return sorted(underperformers, key=lambda x: x.conversion_rate)
    
    def tag_performance(self) -> Dict[str, Dict]:
        """
        Analyze performance by tag.
        
        Returns:
            Dictionary of tag performance metrics
        """
        listings = self.listing_ops.get_all_listings(self.shop_id, "active")
        
        tag_stats = defaultdict(lambda: {
            'listings': 0,
            'total_views': 0,
            'total_favorites': 0,
            'avg_price': []
        })
        
        for listing in listings:
            views = listing.get('views', 0)
            favorites = listing.get('num_favorers', 0)
            price = listing['price']['amount'] / listing['price']['divisor']
            
            for tag in listing.get('tags', []):
                tag_stats[tag]['listings'] += 1
                tag_stats[tag]['total_views'] += views
                tag_stats[tag]['total_favorites'] += favorites
                tag_stats[tag]['avg_price'].append(price)
        
        # Calculate averages
        results = {}
        for tag, stats in tag_stats.items():
            results[tag] = {
                'listings': stats['listings'],
                'total_views': stats['total_views'],
                'total_favorites': stats['total_favorites'],
                'avg_views': stats['total_views'] / stats['listings'],
                'avg_price': statistics.mean(stats['avg_price'])
            }
        
        return dict(sorted(
            results.items(),
            key=lambda x: x[1]['total_views'],
            reverse=True
        ))


class ReportGenerator:
    """Generate formatted reports from analytics data."""
    
    def __init__(self, analytics: ShopAnalytics):
        self.analytics = analytics
    
    def daily_summary(self) -> str:
        """Generate a daily summary report."""
        metrics = self.analytics.get_shop_metrics(days=1)
        
        report = []
        report.append("=" * 60)
        report.append("DAILY SHOP SUMMARY")
        report.append(f"Date: {datetime.now().strftime('%Y-%m-%d')}")
        report.append("=" * 60)
        report.append("")
        report.append(f"Active Listings: {metrics.active_listings}")
        report.append(f"Total Views (all time): {metrics.total_views:,}")
        report.append(f"Total Favorites (all time): {metrics.total_favorites:,}")
        report.append("")
        report.append(f"Today's Sales: {metrics.total_sales}")
        report.append(f"Today's Revenue: ${metrics.total_revenue:.2f}")
        report.append(f"Average Order Value: ${metrics.total_revenue / metrics.total_sales:.2f}" 
                      if metrics.total_sales > 0 else "Average Order Value: N/A")
        
        return "\n".join(report)
    
    def weekly_report(self) -> str:
        """Generate a weekly performance report."""
        current = self.analytics.get_shop_metrics(days=7)
        previous = self.analytics.get_shop_metrics(days=14)
        
        # Calculate changes
        revenue_change = (
            (current.total_revenue / (previous.total_revenue / 2) - 1) * 100
            if previous.total_revenue > 0 else 0
        )
        
        sales_change = (
            (current.total_sales / (previous.total_sales / 2) - 1) * 100
            if previous.total_sales > 0 else 0
        )
        
        report = []
        report.append("=" * 60)
        report.append("WEEKLY PERFORMANCE REPORT")
        report.append(f"Week ending: {datetime.now().strftime('%Y-%m-%d')}")
        report.append("=" * 60)
        report.append("")
        report.append("SALES METRICS")
        report.append("-" * 40)
        report.append(f"Orders: {current.total_sales} ({revenue_change:+.1f}% vs last week)")
        report.append(f"Revenue: ${current.total_revenue:.2f} ({sales_change:+.1f}% vs last week)")
        report.append(f"Avg Order Value: ${current.total_revenue / current.total_sales:.2f}"
                      if current.total_sales > 0 else "")
        report.append("")
        report.append("TOP PERFORMERS")
        report.append("-" * 40)
        
        top = self.analytics.top_performers("views", 5)
        for i, m in enumerate(top, 1):
            report.append(f"{i}. {m.title[:40]}")
            report.append(f"   Views: {m.views:,} | Favorites: {m.favorites}")
        
        return "\n".join(report)
    
    def listing_report(self, listing_id: int) -> str:
        """Generate a detailed report for a single listing."""
        metrics = self.analytics.get_listing_metrics(listing_id, days=30)
        
        report = []
        report.append("=" * 60)
        report.append("LISTING PERFORMANCE REPORT")
        report.append("=" * 60)
        report.append("")
        report.append(f"Title: {metrics.title}")
        report.append(f"Listing ID: {metrics.listing_id}")
        report.append(f"Price: ${metrics.price:.2f}")
        report.append(f"Created: {metrics.created_date.strftime('%Y-%m-%d')}")
        report.append("")
        report.append("METRICS (30 days)")
        report.append("-" * 40)
        report.append(f"Views: {metrics.views:,}")
        report.append(f"Favorites: {metrics.favorites}")
        report.append(f"Sales: {metrics.sales}")
        report.append(f"Revenue: ${metrics.revenue:.2f}")
        report.append("")
        report.append("CALCULATED RATES")
        report.append("-" * 40)
        report.append(f"Conversion Rate: {metrics.conversion_rate * 100:.2f}%")
        report.append(f"Favorite Rate: {metrics.favorite_rate * 100:.2f}%")
        report.append(f"Revenue per View: ${metrics.revenue_per_view:.4f}")
        
        # Performance assessment
        report.append("")
        report.append("ASSESSMENT")
        report.append("-" * 40)
        
        if metrics.conversion_rate > 0.02:
            report.append("✓ Conversion rate is excellent (>2%)")
        elif metrics.conversion_rate > 0.01:
            report.append("● Conversion rate is good (1-2%)")
        else:
            report.append("⚠ Conversion rate needs improvement (<1%)")
        
        if metrics.favorite_rate > 0.05:
            report.append("✓ High interest (>5% favorite rate)")
        
        return "\n".join(report)

Data Visualization

# etsy_client/visualization.py
"""Data visualization for Etsy analytics."""

from typing import Dict, Any, List
from datetime import datetime, timedelta
import json


def generate_chart_data(
    data: List[Dict],
    x_key: str,
    y_key: str
) -> Dict[str, List]:
    """
    Prepare data for charting.
    
    Args:
        data: List of data dictionaries
        x_key: Key for x-axis values
        y_key: Key for y-axis values
        
    Returns:
        Dictionary with 'labels' and 'values' lists
    """
    return {
        'labels': [d[x_key] for d in data],
        'values': [d[y_key] for d in data]
    }


def create_ascii_bar_chart(
    data: Dict[str, float],
    title: str = "Chart",
    max_width: int = 40
) -> str:
    """
    Create a simple ASCII bar chart.
    
    Args:
        data: Dictionary of label: value pairs
        title: Chart title
        max_width: Maximum bar width in characters
        
    Returns:
        ASCII chart string
    """
    if not data:
        return "No data to display"
    
    max_value = max(data.values())
    max_label_len = max(len(str(k)) for k in data.keys())
    
    lines = []
    lines.append(title)
    lines.append("=" * (max_label_len + max_width + 15))
    
    for label, value in data.items():
        bar_length = int((value / max_value) * max_width) if max_value > 0 else 0
        bar = "" * bar_length
        lines.append(f"{label:<{max_label_len}}{bar} {value:.2f}")
    
    return "\n".join(lines)


def create_html_report(
    shop_metrics: Dict,
    listing_metrics: List[Dict],
    output_file: str
):
    """
    Create an HTML analytics report.
    
    Args:
        shop_metrics: Shop-level metrics
        listing_metrics: List of listing metrics
        output_file: Output HTML file path
    """
    html = """
<!DOCTYPE html>
<html>
<head>
    <title>Etsy Shop Analytics Report</title>
    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
    <style>
        body { font-family: Arial, sans-serif; margin: 20px; }
        .metric-card { 
            display: inline-block; 
            padding: 20px; 
            margin: 10px;
            border: 1px solid #ddd;
            border-radius: 8px;
            text-align: center;
        }
        .metric-value { font-size: 2em; font-weight: bold; color: #F1641E; }
        .metric-label { color: #666; }
        table { border-collapse: collapse; width: 100%; margin-top: 20px; }
        th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
        th { background-color: #f2f2f2; }
        .chart-container { width: 600px; margin: 20px auto; }
    </style>
</head>
<body>
    <h1>📊 Shop Analytics Report</h1>
    <p>Generated: """ + datetime.now().strftime('%Y-%m-%d %H:%M') + """</p>
    
    <h2>Overview</h2>
    <div class="metrics">
        <div class="metric-card">
            <div class="metric-value">""" + str(shop_metrics.get('total_listings', 0)) + """</div>
            <div class="metric-label">Active Listings</div>
        </div>
        <div class="metric-card">
            <div class="metric-value">""" + f"${shop_metrics.get('total_revenue', 0):.2f}" + """</div>
            <div class="metric-label">Total Revenue</div>
        </div>
        <div class="metric-card">
            <div class="metric-value">""" + f"{shop_metrics.get('total_views', 0):,}" + """</div>
            <div class="metric-label">Total Views</div>
        </div>
        <div class="metric-card">
            <div class="metric-value">""" + str(shop_metrics.get('total_sales', 0)) + """</div>
            <div class="metric-label">Total Sales</div>
        </div>
    </div>
    
    <h2>Top Listings by Views</h2>
    <table>
        <tr>
            <th>Listing</th>
            <th>Views</th>
            <th>Favorites</th>
            <th>Price</th>
            <th>Conversion</th>
        </tr>
"""
    
    # Add listing rows
    for listing in listing_metrics[:20]:
        conversion = listing.get('conversion_rate', 0) * 100
        html += f"""
        <tr>
            <td>{listing.get('title', '')[:50]}</td>
            <td>{listing.get('views', 0):,}</td>
            <td>{listing.get('favorites', 0)}</td>
            <td>${listing.get('price', 0):.2f}</td>
            <td>{conversion:.2f}%</td>
        </tr>
"""
    
    html += """
    </table>
    
    <div class="chart-container">
        <canvas id="viewsChart"></canvas>
    </div>
    
    <script>
        // Views chart
        const ctx = document.getElementById('viewsChart').getContext('2d');
        new Chart(ctx, {
            type: 'bar',
            data: {
                labels: """ + json.dumps([l.get('title', '')[:20] for l in listing_metrics[:10]]) + """,
                datasets: [{
                    label: 'Views',
                    data: """ + json.dumps([l.get('views', 0) for l in listing_metrics[:10]]) + """,
                    backgroundColor: '#F1641E'
                }]
            },
            options: {
                plugins: { title: { display: true, text: 'Top 10 Listings by Views' } }
            }
        });
    </script>
</body>
</html>
"""
    
    with open(output_file, 'w') as f:
        f.write(html)
    
    print(f"Report generated: {output_file}")

Analytics Scripts

Comprehensive Shop Report

# scripts/generate_report.py
"""Generate comprehensive analytics reports."""

import sys
from pathlib import Path
from datetime import datetime

sys.path.insert(0, str(Path(__file__).parent.parent))

from config import Config
from etsy_client import EtsyClient
from etsy_client.analytics import ShopAnalytics, ReportGenerator
from etsy_client.visualization import create_html_report, create_ascii_bar_chart
from etsy_client.users import get_my_shop_id


def generate_all_reports(output_dir: str = "data/reports"):
    """Generate all analytics reports."""
    
    client = EtsyClient(
        api_key=Config.ETSY_API_KEY,
        access_token=Config.ETSY_ACCESS_TOKEN
    )
    
    shop_id = get_my_shop_id(client)
    analytics = ShopAnalytics(client, shop_id)
    report_gen = ReportGenerator(analytics)
    
    # Create output directory
    output_path = Path(output_dir)
    output_path.mkdir(parents=True, exist_ok=True)
    
    timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
    
    # Daily summary (console)
    print(report_gen.daily_summary())
    print()
    
    # Weekly report (file)
    weekly = report_gen.weekly_report()
    weekly_file = output_path / f"weekly_{timestamp}.txt"
    weekly_file.write_text(weekly)
    print(f"Weekly report saved: {weekly_file}")
    
    # Get metrics for HTML report
    shop_metrics = analytics.get_shop_metrics(days=30)
    listing_metrics = analytics.get_all_listing_metrics()
    
    # Sort by views
    listing_metrics_sorted = sorted(
        [{'title': m.title, 'views': m.views, 'favorites': m.favorites,
          'price': m.price, 'conversion_rate': m.conversion_rate}
         for m in listing_metrics],
        key=lambda x: x['views'],
        reverse=True
    )
    
    # HTML report
    html_file = output_path / f"report_{timestamp}.html"
    create_html_report(
        {
            'total_listings': shop_metrics.active_listings,
            'total_revenue': shop_metrics.total_revenue,
            'total_views': shop_metrics.total_views,
            'total_sales': shop_metrics.total_sales
        },
        listing_metrics_sorted,
        str(html_file)
    )
    
    # Tag analysis (console)
    print("\n" + "=" * 60)
    print("TAG PERFORMANCE ANALYSIS")
    print("=" * 60)
    
    tag_perf = analytics.tag_performance()
    top_tags = dict(list(tag_perf.items())[:10])
    tag_views = {tag: data['total_views'] for tag, data in top_tags.items()}
    
    print(create_ascii_bar_chart(tag_views, "Top Tags by Views"))
    
    # Underperformers
    print("\n" + "=" * 60)
    print("UNDERPERFORMING LISTINGS")
    print("=" * 60)
    
    under = analytics.underperformers(min_age_days=30, min_views=50)
    
    if under:
        print(f"\nFound {len(under)} underperforming listings:\n")
        for m in under[:5]:
            print(f"{m.title[:50]}")
            print(f"  Views: {m.views} | Conversion: {m.conversion_rate*100:.2f}%")
    else:
        print("\nNo underperforming listings found!")


if __name__ == "__main__":
    generate_all_reports()

Automated Daily Analytics

# scripts/daily_analytics.py
"""Send daily analytics via email or webhook."""

import sys
from pathlib import Path
from datetime import datetime
import json
import requests

sys.path.insert(0, str(Path(__file__).parent.parent))

from config import Config
from etsy_client import EtsyClient
from etsy_client.analytics import ShopAnalytics
from etsy_client.users import get_my_shop_id


def send_slack_analytics(webhook_url: str, shop_id: int, analytics: ShopAnalytics):
    """Send analytics to Slack."""
    
    metrics = analytics.get_shop_metrics(days=1)
    
    # Compare to yesterday
    yesterday = analytics.get_shop_metrics(days=2)
    
    revenue_change = (
        (metrics.total_revenue / (yesterday.total_revenue / 2) - 1) * 100
        if yesterday.total_revenue > 0 else 0
    )
    
    # Build Slack message
    message = {
        "blocks": [
            {
                "type": "header",
                "text": {
                    "type": "plain_text",
                    "text": f"📊 Daily Shop Analytics - {datetime.now().strftime('%Y-%m-%d')}"
                }
            },
            {
                "type": "section",
                "fields": [
                    {
                        "type": "mrkdwn",
                        "text": f"*Sales Today:*\n{metrics.total_sales}"
                    },
                    {
                        "type": "mrkdwn",
                        "text": f"*Revenue:*\n${metrics.total_revenue:.2f}"
                    }
                ]
            },
            {
                "type": "section",
                "fields": [
                    {
                        "type": "mrkdwn",
                        "text": f"*Active Listings:*\n{metrics.active_listings}"
                    },
                    {
                        "type": "mrkdwn",
                        "text": f"*Revenue Trend:*\n{revenue_change:+.1f}%"
                    }
                ]
            }
        ]
    }
    
    # Add top performer if there were sales
    if metrics.total_sales > 0:
        top = analytics.top_performers("views", 1)
        if top:
            message["blocks"].append({
                "type": "section",
                "text": {
                    "type": "mrkdwn",
                    "text": f"*Top Listing:* {top[0].title[:50]}"
                }
            })
    
    # Send to Slack
    response = requests.post(webhook_url, json=message)
    
    if response.status_code == 200:
        print("✓ Analytics sent to Slack")
    else:
        print(f"✗ Failed to send to Slack: {response.status_code}")


def save_daily_metrics(analytics: ShopAnalytics, output_file: str):
    """Save daily metrics to JSON for historical tracking."""
    
    metrics = analytics.get_shop_metrics(days=1)
    
    # Load existing data
    data = []
    try:
        with open(output_file, 'r') as f:
            data = json.load(f)
    except FileNotFoundError:
        pass
    
    # Add today's data
    data.append({
        'date': datetime.now().strftime('%Y-%m-%d'),
        'sales': metrics.total_sales,
        'revenue': metrics.total_revenue,
        'active_listings': metrics.active_listings,
        'total_views': metrics.total_views,
        'total_favorites': metrics.total_favorites
    })
    
    # Keep last 365 days
    data = data[-365:]
    
    with open(output_file, 'w') as f:
        json.dump(data, f, indent=2)
    
    print(f"✓ Metrics saved to {output_file}")


def main():
    """Run daily analytics."""
    client = EtsyClient(
        api_key=Config.ETSY_API_KEY,
        access_token=Config.ETSY_ACCESS_TOKEN
    )
    
    shop_id = get_my_shop_id(client)
    analytics = ShopAnalytics(client, shop_id)
    
    # Save metrics
    save_daily_metrics(analytics, "data/daily_metrics.json")
    
    # Send to Slack if configured
    slack_webhook = Config.SLACK_WEBHOOK_URL if hasattr(Config, 'SLACK_WEBHOOK_URL') else None
    if slack_webhook:
        send_slack_analytics(slack_webhook, shop_id, analytics)


if __name__ == "__main__":
    main()

Building a Dashboard

# scripts/dashboard.py
"""Simple web dashboard for Etsy analytics."""

import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).parent.parent))

# Note: Requires dash installation: pip install dash plotly

try:
    from dash import Dash, html, dcc
    import plotly.express as px
    import plotly.graph_objects as go
except ImportError:
    print("Dashboard requires dash and plotly: pip install dash plotly")
    sys.exit(1)

from config import Config
from etsy_client import EtsyClient
from etsy_client.analytics import ShopAnalytics
from etsy_client.users import get_my_shop_id


def create_dashboard():
    """Create and run the analytics dashboard."""
    
    # Initialize data
    client = EtsyClient(
        api_key=Config.ETSY_API_KEY,
        access_token=Config.ETSY_ACCESS_TOKEN
    )
    
    shop_id = get_my_shop_id(client)
    analytics = ShopAnalytics(client, shop_id)
    
    # Get data
    shop_metrics = analytics.get_shop_metrics(days=30)
    listing_metrics = analytics.get_all_listing_metrics()
    
    # Sort listings
    listing_data = sorted(
        [{'title': m.title[:30], 'views': m.views, 'favorites': m.favorites,
          'price': m.price, 'conversion': m.conversion_rate * 100}
         for m in listing_metrics],
        key=lambda x: x['views'],
        reverse=True
    )
    
    # Create app
    app = Dash(__name__)
    
    app.layout = html.Div([
        html.H1("📊 Etsy Shop Dashboard", style={'textAlign': 'center'}),
        
        # Metrics cards
        html.Div([
            html.Div([
                html.H3(f"{shop_metrics.active_listings}"),
                html.P("Active Listings")
            ], className='metric-card', style={
                'display': 'inline-block', 'padding': '20px',
                'margin': '10px', 'border': '1px solid #ddd',
                'borderRadius': '8px', 'textAlign': 'center'
            }),
            
            html.Div([
                html.H3(f"${shop_metrics.total_revenue:.2f}"),
                html.P("Revenue (30 days)")
            ], className='metric-card', style={
                'display': 'inline-block', 'padding': '20px',
                'margin': '10px', 'border': '1px solid #ddd',
                'borderRadius': '8px', 'textAlign': 'center'
            }),
            
            html.Div([
                html.H3(f"{shop_metrics.total_views:,}"),
                html.P("Total Views")
            ], className='metric-card', style={
                'display': 'inline-block', 'padding': '20px',
                'margin': '10px', 'border': '1px solid #ddd',
                'borderRadius': '8px', 'textAlign': 'center'
            }),
            
            html.Div([
                html.H3(f"{shop_metrics.total_sales}"),
                html.P("Sales (30 days)")
            ], className='metric-card', style={
                'display': 'inline-block', 'padding': '20px',
                'margin': '10px', 'border': '1px solid #ddd',
                'borderRadius': '8px', 'textAlign': 'center'
            }),
        ], style={'textAlign': 'center'}),
        
        # Charts
        html.Div([
            dcc.Graph(
                id='views-chart',
                figure=px.bar(
                    listing_data[:10],
                    x='title', y='views',
                    title='Top 10 Listings by Views'
                )
            )
        ], style={'width': '50%', 'display': 'inline-block'}),
        
        html.Div([
            dcc.Graph(
                id='conversion-chart',
                figure=px.scatter(
                    listing_data,
                    x='views', y='conversion',
                    size='favorites', hover_name='title',
                    title='Views vs Conversion Rate'
                )
            )
        ], style={'width': '50%', 'display': 'inline-block'}),
        
    ])
    
    return app


if __name__ == "__main__":
    app = create_dashboard()
    print("Dashboard running at http://localhost:8050")
    app.run_server(debug=True)

Key Takeaways

  1. Track the right metrics: Views, favorites, conversion rate, and revenue
  2. Identify patterns: Use data to find what works and what doesn’t
  3. Automate reporting: Daily/weekly automated reports keep you informed
  4. Visualize data: Charts make trends easier to spot
  5. Act on insights: Data is only valuable if you use it to improve

Moving Forward

Analytics tell you what’s happening, but webhooks tell you when it happens. The next chapter covers implementing real-time notifications through Etsy’s webhook system.


← Chapter 8: Inventory and Pricing Automation Table of Contents Chapter 10: Webhooks and Real-Time Updates →