← back to Handbag Auth Nextjs

analytics/visualizations.py

438 lines

#!/usr/bin/env python3
"""
LUXVAULT Analytics Visualizations
Generate chart-ready data for frontend visualization libraries
"""

import json
from analytics_engine import AuctionAnalytics
import numpy as np
from datetime import datetime, timedelta

class AnalyticsVisualizations:
    """Generate visualization data for charts and graphs"""

    def __init__(self):
        self.analytics = AuctionAnalytics()

    def price_trend_chart(self, brand=None, months=12):
        """
        Generate time series data for price trends
        Format: Ready for Chart.js line chart
        """
        df = self.analytics.df

        if brand:
            df = df[df['search_term'] == brand]

        # Group by month
        df['month'] = df['end_date'].dt.to_period('M')
        monthly = df.groupby('month').agg({
            'current_price': ['mean', 'min', 'max', 'count']
        }).reset_index()

        monthly.columns = ['month', 'avg', 'min', 'max', 'count']
        monthly = monthly.tail(months)

        return {
            'labels': [str(m) for m in monthly['month']],
            'datasets': [
                {
                    'label': 'Average Price',
                    'data': [float(x) for x in monthly['avg']],
                    'borderColor': 'rgb(102, 126, 234)',
                    'backgroundColor': 'rgba(102, 126, 234, 0.1)',
                    'tension': 0.4
                },
                {
                    'label': 'Max Price',
                    'data': [float(x) for x in monthly['max']],
                    'borderColor': 'rgb(118, 75, 162)',
                    'backgroundColor': 'rgba(118, 75, 162, 0.1)',
                    'borderDash': [5, 5],
                    'tension': 0.4
                },
                {
                    'label': 'Min Price',
                    'data': [float(x) for x in monthly['min']],
                    'borderColor': 'rgb(16, 185, 129)',
                    'backgroundColor': 'rgba(16, 185, 129, 0.1)',
                    'borderDash': [5, 5],
                    'tension': 0.4
                }
            ]
        }

    def brand_volume_chart(self, top_n=10):
        """
        Generate bar chart data for auction volume by brand
        Format: Ready for Chart.js bar chart
        """
        brand_counts = self.analytics.df['search_term'].value_counts().head(top_n)

        return {
            'labels': brand_counts.index.tolist(),
            'datasets': [{
                'label': 'Total Auctions',
                'data': brand_counts.values.tolist(),
                'backgroundColor': [
                    'rgba(102, 126, 234, 0.8)',
                    'rgba(118, 75, 162, 0.8)',
                    'rgba(16, 185, 129, 0.8)',
                    'rgba(59, 130, 246, 0.8)',
                    'rgba(139, 92, 246, 0.8)',
                    'rgba(245, 158, 11, 0.8)',
                    'rgba(239, 68, 68, 0.8)',
                    'rgba(236, 72, 153, 0.8)',
                    'rgba(14, 165, 233, 0.8)',
                    'rgba(34, 197, 94, 0.8)'
                ][:top_n],
                'borderWidth': 0
            }]
        }

    def price_distribution_histogram(self, brand=None, bins=20):
        """
        Generate histogram data for price distribution
        Format: Ready for Chart.js bar chart
        """
        df = self.analytics.df

        if brand:
            df = df[df['search_term'] == brand]

        prices = df['current_price'].values
        hist, bin_edges = np.histogram(prices, bins=bins)

        # Create bin labels
        labels = [f"${int(bin_edges[i])}-${int(bin_edges[i+1])}" for i in range(len(bin_edges)-1)]

        return {
            'labels': labels,
            'datasets': [{
                'label': 'Number of Auctions',
                'data': hist.tolist(),
                'backgroundColor': 'rgba(102, 126, 234, 0.6)',
                'borderColor': 'rgb(102, 126, 234)',
                'borderWidth': 1
            }]
        }

    def savings_scatter_plot(self, sample_size=200):
        """
        Generate scatter plot data showing price vs savings
        Format: Ready for Chart.js scatter plot
        """
        df = self.analytics.df.sample(min(sample_size, len(self.analytics.df)))

        data_points = []
        for _, row in df.iterrows():
            data_points.append({
                'x': float(row['current_price']),
                'y': float(row['savings_percent']),
                'brand': row['search_term'],
                'title': row['title']
            })

        return {
            'datasets': [{
                'label': 'Auction Items',
                'data': data_points,
                'backgroundColor': 'rgba(102, 126, 234, 0.6)',
                'pointRadius': 5,
                'pointHoverRadius': 8
            }]
        }

    def brand_comparison_radar(self, brands=None):
        """
        Generate radar chart for brand comparison
        Format: Ready for Chart.js radar chart
        """
        if brands is None:
            # Use top 5 brands by volume
            brands = self.analytics.df['search_term'].value_counts().head(5).index.tolist()

        metrics = []
        for brand in brands:
            brand_data = self.analytics.df[self.analytics.df['search_term'] == brand]

            # Normalize metrics to 0-100 scale
            avg_price_norm = min((brand_data['current_price'].mean() / self.analytics.df['current_price'].max()) * 100, 100)
            savings_norm = min(brand_data['savings_percent'].mean(), 100)
            volume_norm = min((len(brand_data) / len(self.analytics.df)) * 1000, 100)

            # Get rarity score
            rarity = self.analytics.calculate_rarity_score(brand)

            # Get trend
            trends = self.analytics.analyze_brand_trends(days=90)
            brand_trend = next((t for t in trends if t['brand'] == brand), None)
            momentum_norm = min(max(brand_trend['momentum_score'] + 50, 0), 100) if brand_trend else 50

            metrics.append({
                'label': brand,
                'data': [
                    round(avg_price_norm, 1),
                    round(savings_norm, 1),
                    round(volume_norm, 1),
                    round(rarity, 1),
                    round(momentum_norm, 1)
                ]
            })

        return {
            'labels': ['Price', 'Savings', 'Volume', 'Rarity', 'Momentum'],
            'datasets': [
                {
                    'label': m['label'],
                    'data': m['data'],
                    'borderColor': color,
                    'backgroundColor': color.replace('rgb', 'rgba').replace(')', ', 0.2)'),
                    'pointBackgroundColor': color,
                    'pointBorderColor': '#fff',
                    'pointHoverBackgroundColor': '#fff',
                    'pointHoverBorderColor': color
                }
                for m, color in zip(metrics, [
                    'rgb(102, 126, 234)',
                    'rgb(118, 75, 162)',
                    'rgb(16, 185, 129)',
                    'rgb(59, 130, 246)',
                    'rgb(139, 92, 246)'
                ])
            ]
        }

    def auction_house_pie(self):
        """
        Generate pie chart for auction house market share
        Format: Ready for Chart.js pie chart
        """
        house_counts = self.analytics.df['auction_house'].value_counts()

        return {
            'labels': house_counts.index.tolist(),
            'datasets': [{
                'data': house_counts.values.tolist(),
                'backgroundColor': [
                    'rgba(102, 126, 234, 0.8)',
                    'rgba(118, 75, 162, 0.8)',
                    'rgba(16, 185, 129, 0.8)',
                    'rgba(59, 130, 246, 0.8)',
                    'rgba(139, 92, 246, 0.8)'
                ],
                'borderWidth': 2,
                'borderColor': '#fff'
            }]
        }

    def seasonality_heatmap(self, brand):
        """
        Generate heatmap data for seasonal patterns
        Format: Matrix data for heatmap visualization
        """
        patterns = self.analytics.detect_seasonal_patterns(brand)

        if not patterns:
            return None

        # Create month names
        month_names = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
                      'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']

        return {
            'months': month_names,
            'metrics': ['Avg Price', 'Volume', 'Savings'],
            'data': [
                [p['avg_price'] for p in patterns],
                [p['auction_count'] for p in patterns],
                [p['avg_savings'] for p in patterns]
            ]
        }

    def deal_score_distribution(self):
        """
        Generate histogram of deal scores
        Format: Ready for Chart.js bar chart
        """
        scores = []

        # Sample 200 random auctions for performance
        sample = self.analytics.df.sample(min(200, len(self.analytics.df)))

        for auction_id in sample['auction_id']:
            score_data = self.analytics.calculate_deal_score(auction_id)
            scores.append(score_data['deal_score'])

        # Create bins for scores
        bins = [0, 30, 40, 50, 60, 70, 80, 100]
        labels = ['D', 'C', 'C+', 'B', 'B+', 'A', 'A+']
        hist, _ = np.histogram(scores, bins=bins)

        return {
            'labels': labels,
            'datasets': [{
                'label': 'Number of Deals',
                'data': hist.tolist(),
                'backgroundColor': [
                    'rgba(239, 68, 68, 0.6)',
                    'rgba(245, 158, 11, 0.6)',
                    'rgba(245, 158, 11, 0.8)',
                    'rgba(59, 130, 246, 0.6)',
                    'rgba(139, 92, 246, 0.6)',
                    'rgba(59, 130, 246, 0.8)',
                    'rgba(16, 185, 129, 0.8)'
                ],
                'borderWidth': 0
            }]
        }

    def roi_projection_chart(self, brand):
        """
        Generate line chart showing ROI projections
        Format: Ready for Chart.js line chart
        """
        # Get sample auction for this brand
        brand_auctions = self.analytics.df[self.analytics.df['search_term'] == brand]
        if len(brand_auctions) == 0:
            return None

        sample_auction = brand_auctions.iloc[0]
        roi_data = self.analytics.calculate_roi_potential(sample_auction['auction_id'])

        if not roi_data:
            return None

        years = [0, 1, 2, 3, 4, 5]
        values = [
            roi_data['current_price'],
            roi_data['projected_value_1yr'],
            roi_data['current_price'] * ((1 + roi_data['annual_appreciation_percent']/100) ** 2),
            roi_data['projected_value_3yr'],
            roi_data['current_price'] * ((1 + roi_data['annual_appreciation_percent']/100) ** 4),
            roi_data['projected_value_5yr']
        ]

        return {
            'labels': [f'Year {y}' for y in years],
            'datasets': [{
                'label': f'{brand} Value Projection',
                'data': [round(v, 2) for v in values],
                'borderColor': 'rgb(16, 185, 129)',
                'backgroundColor': 'rgba(16, 185, 129, 0.1)',
                'tension': 0.4,
                'fill': True
            }]
        }

    def trending_brands_timeline(self, days=180):
        """
        Generate timeline showing brand momentum over time
        Format: Multi-line chart
        """
        # Track top 5 brands over time
        top_brands = self.analytics.df['search_term'].value_counts().head(5).index.tolist()

        # Split time into 6 periods
        periods = 6
        days_per_period = days // periods

        datasets = []
        colors = [
            'rgb(102, 126, 234)',
            'rgb(118, 75, 162)',
            'rgb(16, 185, 129)',
            'rgb(59, 130, 246)',
            'rgb(139, 92, 246)'
        ]

        for idx, brand in enumerate(top_brands):
            brand_data = self.analytics.df[self.analytics.df['search_term'] == brand]
            momentum_over_time = []

            for period in range(periods):
                period_end = datetime.now() - timedelta(days=period * days_per_period)
                period_start = period_end - timedelta(days=days_per_period)

                period_auctions = brand_data[
                    (brand_data['end_date'] >= period_start) &
                    (brand_data['end_date'] < period_end)
                ]

                # Simple momentum: auction count
                momentum = len(period_auctions)
                momentum_over_time.insert(0, momentum)

            datasets.append({
                'label': brand,
                'data': momentum_over_time,
                'borderColor': colors[idx],
                'backgroundColor': colors[idx].replace('rgb', 'rgba').replace(')', ', 0.1)'),
                'tension': 0.4
            })

        labels = [f'{i*days_per_period}-{(i+1)*days_per_period} days ago' for i in range(periods)]
        labels.reverse()

        return {
            'labels': labels,
            'datasets': datasets
        }

    def generate_all_charts(self):
        """Generate all chart data at once"""
        return {
            'price_trends': {
                'hermes_birkin': self.price_trend_chart('Hermes Birkin'),
                'chanel_classic': self.price_trend_chart('Chanel Classic Flap'),
                'all_brands': self.price_trend_chart()
            },
            'brand_volume': self.brand_volume_chart(),
            'price_distribution': self.price_distribution_histogram(),
            'savings_scatter': self.savings_scatter_plot(),
            'brand_radar': self.brand_comparison_radar(),
            'auction_house_pie': self.auction_house_pie(),
            'deal_scores': self.deal_score_distribution(),
            'trending_timeline': self.trending_brands_timeline()
        }

    def export_to_file(self, filename='chart_data.json'):
        """Export all chart data to JSON file"""
        charts = self.generate_all_charts()

        with open(filename, 'w') as f:
            json.dump(charts, f, indent=2)

        return filename


def main():
    """Generate and display sample visualizations"""
    print("Generating LUXVAULT Analytics Visualizations...")

    viz = AnalyticsVisualizations()

    print("\n=== PRICE TREND CHART (Hermes Birkin) ===")
    price_chart = viz.price_trend_chart('Hermes Birkin')
    print(f"Data points: {len(price_chart['labels'])}")
    print(f"Datasets: {len(price_chart['datasets'])}")

    print("\n=== BRAND VOLUME CHART ===")
    volume_chart = viz.brand_volume_chart()
    print(f"Brands: {', '.join(volume_chart['labels'][:5])}...")

    print("\n=== DEAL SCORE DISTRIBUTION ===")
    score_chart = viz.deal_score_distribution()
    print(f"Grade distribution: {dict(zip(score_chart['labels'], score_chart['datasets'][0]['data']))}")

    print("\n=== EXPORTING ALL CHARTS ===")
    filename = viz.export_to_file('/root/Projects/handbag-auth-nextjs/analytics/chart_data.json')
    print(f"Exported to: {filename}")

    print("\nVisualization generation complete!")


if __name__ == "__main__":
    main()