import gradio as gr import pandas as pd import plotly.graph_objects as go import plotly.express as px from plotly.subplots import make_subplots import numpy as np import json import os from typing import Dict, List, Any, Tuple, Optional import tempfile import uuid import warnings warnings.filterwarnings('ignore') # Initialize Claude API def get_claude_client(): """Initialize Claude API client without proxies""" api_key = os.getenv("CLAUDE_API_KEY") if not api_key: print("CLAUDE_API_KEY not set. Falling back to default recommendations.") return None try: import anthropic return anthropic.Anthropic(api_key=api_key) except Exception as e: print(f"Error initializing Claude client: {e}") return None def convert_numpy_types(obj: Any) -> Any: """Recursively convert NumPy types to Python native types""" if isinstance(obj, np.integer): return int(obj) elif isinstance(obj, np.floating): return float(obj) elif isinstance(obj, np.ndarray): return obj.tolist() elif isinstance(obj, dict): return {key: convert_numpy_types(value) for key, value in obj.items()} elif isinstance(obj, list): return [convert_numpy_types(item) for item in obj] elif isinstance(obj, tuple): return tuple(convert_numpy_types(item) for item in obj) return obj class DataProcessor: """Handles data loading and processing operations""" @staticmethod def load_and_clean_data(file_path: str) -> Optional[pd.DataFrame]: """Load and clean CSV/Excel data""" try: if not os.path.exists(file_path): raise FileNotFoundError(f"File {file_path} not found") if file_path.endswith('.csv'): df = pd.read_csv(file_path) else: df = pd.read_excel(file_path) # Clean column names df.columns = df.columns.str.strip() # Ensure required columns exist required_columns = ['CreativeID', 'Title', 'ReportingBrand'] missing_columns = [col for col in required_columns if col not in df.columns] if missing_columns: print(f"Missing required columns: {missing_columns}") return None return DataProcessor.calculate_creativity_scores(df) except Exception as e: print(f"Error loading file: {e}") return None @staticmethod def calculate_creativity_scores(df: pd.DataFrame) -> pd.DataFrame: """Calculate comprehensive creativity scores with optimized operations""" df = df.copy() df['creativity_score'] = 0 # Cache emotion columns emotion_cols = [col for col in df.columns if any(emotion in col for emotion in ['Happiness', 'Surprise', 'Sadness', 'Fear', 'Anger'])] if emotion_cols: df['emotional_range'] = df[emotion_cols].std(axis=1, skipna=True) df['creativity_score'] += df['emotional_range'].fillna(0) * 3 # Performance metrics with vectorized operations score_mappings = { 'EIA': 8, 'DecimalStar': 6, 'FinalFluency': lambda x: (x.fillna(0) / 15) * 4, 'Spike': 5, 'TotalDynamism': lambda x: (x.fillna(0) / 10) * 3, 'Duration': lambda x: np.log1p(x.fillna(30)) * 2 } for col, weight in score_mappings.items(): if col in df.columns: if callable(weight): df['creativity_score'] += weight(df[col]) else: df['creativity_score'] += df[col].fillna(0) * weight return df @staticmethod def get_creative_ids_list(df: pd.DataFrame) -> List[str]: """Get list of Creative IDs for dropdown""" if df is None or 'CreativeID' not in df.columns: return [] return sorted(df['CreativeID'].unique().tolist()) @staticmethod def get_campaign_data(df: pd.DataFrame, creative_id: str) -> Optional[Dict]: """Get comprehensive campaign data for analysis""" if df is None or creative_id not in df['CreativeID'].values: return None campaign = df[df['CreativeID'] == creative_id].iloc[0] analysis_data = { 'creative_id': campaign.get('CreativeID', 'N/A'), 'title': campaign.get('Title', 'Untitled Campaign'), 'brand': campaign.get('ReportingBrand', 'Unknown Brand'), 'advertiser': campaign.get('ReportingAdvertiser', campaign.get('ReportingBrand', 'Unknown')), 'category': campaign.get('ReportingSubCategory', 'General'), 'market': campaign.get('Market', 'Unknown'), 'duration': campaign.get('Duration', 30), 'creativity_score': campaign.get('creativity_score', 0), 'performance_metrics': { 'eia': campaign.get('EIA', 0), 'decimal_star': campaign.get('DecimalStar', 0), 'final_fluency': campaign.get('FinalFluency', 0), 'fast_fluency': campaign.get('FastFluency', 0), 'spike': campaign.get('Spike', 0), 'oei': campaign.get('OEI', 0), 'total_dynamism': campaign.get('TotalDynamism', 0), 'dynamism_index': campaign.get('DynamismIndex', 0) }, 'emotional_profile': { 'contempt': campaign.get('Contempt (%)', 0), 'disgust': campaign.get('Disgust (%)', 0), 'anger': campaign.get('Anger (%)', 0), 'fear': campaign.get('Fear (%)', 0), 'sadness': campaign.get('Sadness (%)', 0), 'neutral': campaign.get('Neutral (%)', 0), 'happiness': campaign.get('Happiness (%)', 0), 'surprise': campaign.get('Surprise (%)', 0) }, 'sample_info': { 'count_completes': campaign.get('CountCompletes', 0), 'sample_description': campaign.get('SampleDescription', 'General Population'), 'sample_details': campaign.get('SampleDetails', 'Mixed Demographics') }, 'technical_details': { 'display_type': campaign.get('DisplayType', 'Standard'), 'survey_type': campaign.get('SurveyType', 'Online'), 'stimuli_type': campaign.get('StimuliType', 'Video'), 'advert_medium': campaign.get('AdvertMedium', 'Digital'), 'advert_format': campaign.get('AdvertFormat', '30s Spot') } } return convert_numpy_types(analysis_data) class ChartGenerator: """Generates professional charts with dark pink theme""" @staticmethod def create_executive_dashboard(data: Dict) -> go.Figure: """Create comprehensive executive dashboard with fixed text overlap""" fig = make_subplots( rows=3, cols=4, subplot_titles=[''] * 12, specs=[[{'type': 'indicator'}] * 4] * 3, vertical_spacing=0.15, horizontal_spacing=0.1 ) # Dark pink color scheme primary_color = "#C2185B" secondary_color = "#E91E63" accent_color = "#F8BBD9" # Custom titles titles = [ '🎯 Creativity Score', '⚡ EIA Performance', '🎭 Fluency Rating', '📈 Spike Factor', '💫 Emotional Impact', '🔄 Dynamism Index', '⭐ Star Rating', '⏱️ Duration', '👥 Sample Size', '🌍 Market Score', '🏢 Brand Power', '📊 Category Score' ] # Row 1 - Core Performance fig.add_trace(go.Indicator( mode="gauge+number+delta", value=round(data['creativity_score'], 1), domain={'x': [0, 1], 'y': [0, 1]}, title={'text': titles[0], 'font': {'size': 12, 'color': primary_color, 'family': 'Arial'}}, gauge={ 'axis': {'range': [None, 100], 'tickcolor': primary_color}, 'bar': {'color': primary_color, 'thickness': 0.8}, 'bgcolor': "rgba(248, 187, 217, 0.2)", 'borderwidth': 2, 'bordercolor': accent_color, 'steps': [ {'range': [0, 30], 'color': '#FCE4EC'}, {'range': [30, 60], 'color': '#F8BBD9'}, {'range': [60, 80], 'color': '#F48FB1'}, {'range': [80, 100], 'color': accent_color} ], 'threshold': { 'line': {'color': secondary_color, 'width': 4}, 'thickness': 0.8, 'value': 75 } }, number={'font': {'size': 20, 'color': primary_color}}, delta={'reference': 50, 'relative': True, 'font': {'size': 14}} ), row=1, col=1) fig.add_trace(go.Indicator( mode="gauge+number", value=round(data['performance_metrics']['eia'], 2), title={'text': titles[1], 'font': {'size': 12, 'color': primary_color, 'family': 'Arial'}}, gauge={ 'axis': {'range': [0, 10], 'tickcolor': primary_color}, 'bar': {'color': secondary_color, 'thickness': 0.7}, 'bgcolor': "rgba(194, 24, 91, 0.1)", 'steps': [ {'range': [0, 3], 'color': '#FCE4EC'}, {'range': [3, 6], 'color': '#F8BBD9'}, {'range': [6, 8], 'color': '#F48FB1'}, {'range': [8, 10], 'color': accent_color} ] }, number={'font': {'size': 18, 'color': secondary_color}} ), row=1, col=2) fig.add_trace(go.Indicator( mode="number+delta", value=round(data['performance_metrics']['final_fluency'], 1), title={'text': titles[2], 'font': {'size': 12, 'color': primary_color, 'family': 'Arial'}}, delta={'reference': 60.0, 'relative': True, 'font': {'color': secondary_color}}, number={'font': {'size': 18, 'color': primary_color}, 'suffix': '%'} ), row=1, col=3) fig.add_trace(go.Indicator( mode="number+delta", value=round(data['performance_metrics']['spike'], 2), title={'text': titles[3], 'font': {'size': 12, 'color': secondary_color, 'family': 'Arial'}}, delta={'reference': 8.0, 'relative': True, 'font': {'color': primary_color}}, number={'font': {'size': 18, 'color': secondary_color}} ), row=1, col=4) # Row 2 - Engagement Metrics emotional_impact = (data['emotional_profile']['happiness'] + data['emotional_profile']['surprise'] + abs(data['emotional_profile']['sadness']) * 0.5) / 2 fig.add_trace(go.Indicator( mode="gauge+number", value=round(emotional_impact, 1), title={'text': titles[4], 'font': {'size': 12, 'color': primary_color, 'family': 'Arial'}}, gauge={ 'axis': {'range': [0, 50], 'tickcolor': primary_color}, 'bar': {'color': primary_color, 'thickness': 0.7}, 'bgcolor': "rgba(233, 30, 99, 0.1)", 'steps': [ {'range': [0, 15], 'color': '#FCE4EC'}, {'range': [15, 30], 'color': '#F8BBD9'}, {'range': [30, 50], 'color': accent_color} ] }, number={'font': {'size': 16, 'color': primary_color}, 'suffix': '%'} ), row=2, col=1) fig.add_trace(go.Indicator( mode="number+delta", value=round(data['performance_metrics']['dynamism_index'], 1), title={'text': titles[5], 'font': {'size': 12, 'color': secondary_color, 'family': 'Arial'}}, delta={'reference': 15.0, 'relative': True}, number={'font': {'size': 18, 'color': secondary_color}} ), row=2, col=2) fig.add_trace(go.Indicator( mode="number", value=round(data['performance_metrics']['decimal_star'], 1), title={'text': titles[6], 'font': {'size': 12, 'color': primary_color, 'family': 'Arial'}}, number={'suffix': " ⭐", 'font': {'size': 18, 'color': primary_color}} ), row=2, col=3) fig.add_trace(go.Indicator( mode="number", value=int(data['duration']), title={'text': titles[7], 'font': {'size': 12, 'color': secondary_color, 'family': 'Arial'}}, number={'suffix': " sec", 'font': {'size': 18, 'color': secondary_color}} ), row=2, col=4) # Row 3 - Context Metrics fig.add_trace(go.Indicator( mode="number", value=int(data['sample_info']['count_completes']), title={'text': titles[8], 'font': {'size': 12, 'color': primary_color, 'family': 'Arial'}}, number={'font': {'size': 16, 'color': primary_color}} ), row=3, col=1) market_scores = {'US': 10, 'UK': 9, 'DE': 8, 'FR': 8, 'JP': 9, 'AU': 7, 'IN': 7, 'BR': 6, 'CA': 8} market_score = market_scores.get(data['market'], 5) fig.add_trace(go.Indicator( mode="number", value=market_score, title={'text': titles[9], 'font': {'size': 12, 'color': secondary_color, 'family': 'Arial'}}, number={'suffix': "/10", 'font': {'size': 16, 'color': secondary_color}} ), row=3, col=2) premium_brands = ['Apple', 'Google', 'Microsoft', 'Amazon', 'Tesla'] brand_score = 10 if data['brand'] in premium_brands else 7 fig.add_trace(go.Indicator( mode="number", value=brand_score, title={'text': titles[10], 'font': {'size': 12, 'color': primary_color, 'family': 'Arial'}}, number={'suffix': "/10", 'font': {'size': 16, 'color': primary_color}} ), row=3, col=3) tech_categories = ['Technology', 'E-commerce'] category_score = 9 if data['category'] in tech_categories else 7 fig.add_trace(go.Indicator( mode="number", value=category_score, title={'text': titles[11], 'font': {'size': 12, 'color': secondary_color, 'family': 'Arial'}}, number={'suffix': "/10", 'font': {'size': 16, 'color': secondary_color}} ), row=3, col=4) fig.update_layout( height=900, showlegend=False, paper_bgcolor='white', plot_bgcolor='rgba(248, 187, 217, 0.05)', font={'color': '#333', 'family': 'Arial', 'size': 12}, title={ 'text': f'🎯 Executive Performance Dashboard
{data["title"]} - {data["brand"]}', 'x': 0.5, 'font': {'size': 24, 'color': primary_color, 'family': 'Arial'}, 'y': 0.95 }, margin=dict(t=120, b=50, l=50, r=50) ) return fig @staticmethod def create_emotional_radar_chart(data: Dict) -> go.Figure: """Create emotional profile radar chart""" emotions = list(data['emotional_profile'].keys()) values = list(data['emotional_profile'].values()) emotion_labels = { 'contempt': '😤 Contempt', 'disgust': '🤢 Disgust', 'anger': '😡 Anger', 'fear': '😨 Fear', 'sadness': '😢 Sadness', 'neutral': '😐 Neutral', 'happiness': '😊 Happiness', 'surprise': '😲 Surprise' } labeled_emotions = [emotion_labels.get(emotion, emotion.title()) for emotion in emotions] fig = go.Figure() fig.add_trace(go.Scatterpolar( r=values + [values[0]], theta=labeled_emotions + [labeled_emotions[0]], fill='toself', name='Campaign Profile', line=dict(color='#E91E63', width=3), fillcolor='rgba(233, 30, 99, 0.3)', marker=dict(size=8, color='#C2185B', symbol='circle') )) benchmark_values = {'contempt': 3, 'disgust': 2, 'anger': 4, 'fear': 6, 'sadness': 8, 'neutral': 35, 'happiness': 28, 'surprise': 14} benchmark = [benchmark_values.get(emotion, 10) for emotion in emotions] fig.add_trace(go.Scatterpolar( r=benchmark + [benchmark[0]], theta=labeled_emotions + [labeled_emotions[0]], fill='toself', name='Industry Benchmark', line=dict(color='#666', width=2, dash='dot'), fillcolor='rgba(102, 102, 102, 0.2)', marker=dict(size=6, color='#444', symbol='diamond') )) fig.update_layout( polar=dict( radialaxis=dict( visible=True, range=[0, max(max(values), max(benchmark)) + 5], tickfont=dict(size=11, color='#666', family='Arial'), gridcolor='rgba(233, 30, 99, 0.2)' ), angularaxis=dict( tickfont=dict(size=12, color='#333', family='Arial'), gridcolor='rgba(233, 30, 99, 0.2)' ), bgcolor='rgba(248, 187, 217, 0.05)' ), showlegend=True, title={ 'text': '🎭 Emotional Response Analysis', 'x': 0.5, 'font': {'size': 18, 'color': '#E91E63', 'family': 'Arial'} }, height=650, paper_bgcolor='white', margin=dict(t=100, b=80, l=50, r=50) ) return fig @staticmethod def create_performance_comparison(data: Dict) -> go.Figure: """Create performance metrics comparison""" metrics_data = data['performance_metrics'] categories = ['EIA Score', 'Fluency', 'Spike', 'Dynamism', 'Stars'] values = [ min(metrics_data['eia'], 10), metrics_data['final_fluency'] / 10, min(metrics_data['spike'], 10), metrics_data['total_dynamism'] / 10, metrics_data['decimal_star'] * 2 ] fig = go.Figure() fig.add_trace(go.Bar( x=categories, y=values, marker=dict( color=['#E91E63', '#C2185B', '#AD1457', '#F48FB1', '#F8BBD9'], opacity=0.85, line=dict(color='#880E4F', width=1.5) ), text=[f'{v:.1f}' for v in values], textposition='auto', textfont=dict(color='white', size=12, family='Arial') )) fig.update_layout( title={ 'text': '📊 Performance Metrics Comparison', 'x': 0.5, 'font': {'size': 18, 'color': '#E91E63', 'family': 'Arial'} }, xaxis_title='Metrics', yaxis_title='Normalized Score (0-10)', height=550, paper_bgcolor='white', plot_bgcolor='rgba(248, 187, 217, 0.05)', font=dict(family='Arial', size=11) ) return fig @staticmethod def create_left_brain_chart(data: Dict) -> go.Figure: """Create left-brain (logical) metrics chart""" metrics_data = data['performance_metrics'] categories = ['EIA', 'Fluency', 'Dynamism Index'] values = [ min(metrics_data['eia'], 10), metrics_data['final_fluency'] / 10, min(metrics_data['dynamism_index'] / 10, 10) ] fig = go.Figure() fig.add_trace(go.Bar( x=categories, y=values, marker=dict( color='#C2185B', opacity=0.85, line=dict(color='#880E4F', width=1.5) ), text=[f'{v:.1f}' for v in values], textposition='auto', textfont=dict(color='white', size=12, family='Arial') )) fig.update_layout( title={ 'text': '🧠 Left-Brain Metrics (Logical)', 'x': 0.5, 'font': {'size': 18, 'color': '#C2185B', 'family': 'Arial'} }, xaxis_title='Logical Metrics', yaxis_title='Normalized Score (0-10)', height=450, paper_bgcolor='white', plot_bgcolor='rgba(248, 187, 217, 0.05)', font=dict(family='Arial', size=11) ) return fig @staticmethod def create_right_brain_chart(data: Dict) -> go.Figure: """Create right-brain (emotional) metrics chart""" emotional_data = data['emotional_profile'] categories = ['Happiness', 'Surprise', 'Sadness'] values = [ emotional_data['happiness'], emotional_data['surprise'], emotional_data['sadness'] ] fig = go.Figure() fig.add_trace(go.Bar( x=categories, y=values, marker=dict( color='#E91E63', opacity=0.85, line=dict(color='#880E4F', width=1.5) ), text=[f'{v:.1f}%' for v in values], textposition='auto', textfont=dict(color='white', size=12, family='Arial') )) fig.update_layout( title={ 'text': '🎨 Right-Brain Metrics (Emotional)', 'x': 0.5, 'font': {'size': 18, 'color': '#E91E63', 'family': 'Arial'} }, xaxis_title='Emotional Metrics', yaxis_title='Percentage (%)', height=450, paper_bgcolor='white', plot_bgcolor='rgba(248, 187, 217, 0.05)', font=dict(family='Arial', size=11) ) return fig class RecommendationsGenerator: """Generates optimization recommendations with detailed explanations""" def __init__(self): self.claude_client = get_claude_client() self.raw_response = None # Store raw Claude response def generate_recommendations(self, data: Dict) -> List[Dict]: """Generate optimization recommendations with explanations""" if self.claude_client: try: prompt = self._create_optimization_prompt(data) message = self.claude_client.messages.create( model="claude-3-5-sonnet-20241022", max_tokens=1000, temperature=0.7, messages=[{"role": "user", "content": prompt}] ) self.raw_response = message.content[0].text return self._parse_recommendations(self.raw_response, data) except Exception as e: print(f"Claude API error: {e}") self.raw_response = None return self._generate_fallback_recommendations(data) else: self.raw_response = None return self._generate_fallback_recommendations(data) def _create_optimization_prompt(self, data: Dict) -> str: """Create prompt for optimization recommendations""" return f"""Provide optimization recommendations for the following campaign data: Metrics: {json.dumps(data['performance_metrics'])} Emotional Profile: {json.dumps(data['emotional_profile'])} Market: {data['market']} Duration: {data['duration']} seconds Category: {data['category']} Brand: {data['brand']} Provide a JSON response with: {{ "recommendations": [ {{ "priority": "High|Medium|Low", "action": "Specific actionable recommendation", "impact": "Expected performance lift", "explanation": "Detailed explanation of why this recommendation is made and how to implement it" }} ] }}""" def _parse_recommendations(self, response_text: str, data: Dict) -> List[Dict]: """Parse Claude's recommendations response""" try: response = json.loads(response_text) recommendations = response.get('recommendations', []) # Ensure explanation field exists; if not, add a default one for rec in recommendations: if 'explanation' not in rec: rec['explanation'] = f"This recommendation aims to improve engagement by focusing on key metrics like {data['performance_metrics']['eia']:.1f} EIA and emotional response such as {data['emotional_profile']['happiness']:.1f}% happiness." return recommendations except json.JSONDecodeError: return self._generate_fallback_recommendations(data) def _generate_fallback_recommendations(self, data: Dict) -> List[Dict]: """Generate detailed fallback recommendations based on campaign data""" metrics = data['performance_metrics'] emotions = data['emotional_profile'] market = data['market'] duration = data['duration'] category = data['category'] brand = data['brand'] # Identify dominant emotion dominant_emotion = max(emotions.items(), key=lambda x: x[1])[0] dominant_value = emotions[dominant_emotion] # Determine weak metrics weak_metrics = [] if metrics['eia'] < 5: weak_metrics.append(f"EIA ({metrics['eia']:.1f})") if metrics['final_fluency'] < 50: weak_metrics.append(f"Fluency ({metrics['final_fluency']:.1f})") if metrics['spike'] < 5: weak_metrics.append(f"Spike ({metrics['spike']:.1f})") # Market-specific adjustment market_strength = {'US': 10, 'UK': 9, 'DE': 8, 'FR': 8, 'JP': 9, 'AU': 7, 'IN': 7, 'BR': 6, 'CA': 8}.get(market, 5) recommendations = [ { 'priority': 'High', 'action': f"Amplify {dominant_emotion} to drive engagement", 'impact': '10-15% engagement lift', 'explanation': f"The campaign's {dominant_emotion} score of {dominant_value:.1f}% is strong in the {market} market (market strength: {market_strength}/10). Emphasize {dominant_emotion} by featuring more scenes that showcase the {category} product's emotional benefits, such as customer testimonials or relatable scenarios for {brand}." }, { 'priority': 'Medium', 'action': 'Adjust duration for optimal impact', 'impact': '8-12% performance increase', 'explanation': f"The current duration of {duration} seconds may {'be too long' if duration > 30 else 'limit impact'}. For {category} campaigns in {market}, {'shorten to 20-30 seconds to maintain attention' if duration > 30 else 'extend to 30-40 seconds to build narrative'}. Edit the ad to focus on key {brand} messages while maintaining emotional peaks." }, { 'priority': 'Medium', 'action': f"Improve {' and '.join(weak_metrics) if weak_metrics else 'key performance metrics'}", 'impact': '5-10% metric improvement', 'explanation': f"{'Weak metrics include ' + ', '.join(weak_metrics) + '.' if weak_metrics else 'Metrics like EIA and Fluency can improve.'} For {brand}'s {category} campaign, enhance clarity by simplifying messaging and increasing visual cues. In {market}, consider A/B testing with varied calls-to-action to boost {'EIA and Spike' if weak_metrics else 'overall performance'}." }, { 'priority': 'Low', 'action': 'Integrate market-specific cultural elements', 'impact': '3-7% brand resonance increase', 'explanation': f"To align with {market}'s cultural context, incorporate local references or trends relevant to {category}. For {brand}, this could mean using regional influencers or tailoring visuals to resonate with {market} audiences, enhancing emotional connection beyond the current {dominant_emotion} focus." } ] return recommendations # Global variables current_df = None recommendations_generator = RecommendationsGenerator() current_recommendations = [] def load_data(file): """Load and process uploaded data file""" global current_df if file is None: return "Please upload a CSV or Excel file.", gr.update(choices=[], value=None), "No data loaded" try: # Handle different types of file input file_path = None is_csv = False if isinstance(file, bytes): # Handle raw bytes with tempfile.NamedTemporaryFile(delete=False, suffix='.csv') as tmp: tmp.write(file) file_path = tmp.name is_csv = True # Assume CSV for raw bytes elif hasattr(file, 'name'): # Handle file object with name attribute file_path = file.name is_csv = file_path.endswith('.csv') elif isinstance(file, gr.FileData): # Handle Gradio FileData object file_path = file.path is_csv = file_path.endswith('.csv') else: raise ValueError("Unsupported file input type") current_df = DataProcessor.load_and_clean_data(file_path) # Clean up temporary file if created if isinstance(file, bytes): os.unlink(file_path) if current_df is None: return "Error: Could not load data. Please check file format and required columns (CreativeID, Title, ReportingBrand).", gr.update(choices=[], value=None), "Load failed" creative_ids = DataProcessor.get_creative_ids_list(current_df) return ( f"✅ Successfully loaded {len(current_df)} campaigns from {len(creative_ids)} Creative IDs", gr.update(choices=creative_ids, value=creative_ids[0] if creative_ids else None), f"Data loaded: {len(current_df)} rows, {len(current_df.columns)} columns" ) except Exception as e: return f"Error loading file: {str(e)}", gr.update(choices=[], value=None), "Load error" def analyze_campaign(creative_id): """Analyze selected campaign and generate charts, recommendations, and insights""" global current_df, recommendations_generator, current_recommendations if current_df is None: return "Please load data first.", None, None, None, None, None, False, "

No recommendations available.

" if not creative_id: return "Please select a Creative ID.", None, None, None, None, None, False, "

No recommendations available.

" try: campaign_data = DataProcessor.get_campaign_data(current_df, creative_id) if campaign_data is None: return "Campaign not found.", None, None, None, None, None, False, "

No recommendations available.

" dashboard_chart = ChartGenerator.create_executive_dashboard(campaign_data) emotional_chart = ChartGenerator.create_emotional_radar_chart(campaign_data) performance_chart = ChartGenerator.create_performance_comparison(campaign_data) left_brain_chart = ChartGenerator.create_left_brain_chart(campaign_data) right_brain_chart = ChartGenerator.create_right_brain_chart(campaign_data) current_recommendations = recommendations_generator.generate_recommendations(campaign_data) recommendations_html = show_recommendations(campaign_data['brand'], campaign_data['performance_metrics']['decimal_star']) return ( f"✅ Analysis complete for {campaign_data['title']} ({creative_id})", dashboard_chart, emotional_chart, performance_chart, left_brain_chart, right_brain_chart, True, # Enable recommendation buttons recommendations_html ) except Exception as e: return f"Error analyzing campaign: {str(e)}", None, None, None, None, None, False, "

Error generating recommendations.

" def show_recommendations(brand: str, decimal_star: float) -> str: """Generate HTML for recommendations and AI insights""" global current_recommendations if not current_recommendations: return "

No recommendations available.

" # Ensure we have exactly 4 recommendations to match the image layout insights = [] for i in range(4): if i < len(current_recommendations): rec = current_recommendations[i] priority_star = "★" * (3 if rec['priority'] == "High" else 2 if rec['priority'] == "Medium" else 1) insights.append({ 'priority': priority_star, 'title': rec['action'].upper(), 'description': rec['explanation'] }) else: # Fallback if fewer than 4 recommendations insights.append({ 'priority': "★", 'title': "CONTINUE MONITORING PERFORMANCE", 'description': "Keep tracking key metrics to identify further optimization opportunities." }) html = """

Recommendations

At {decimal_star:.1f} Stars, this campaign demonstrates modest long-term growth potential for {brand} but consider the following optimizations for now and in the future.

""".format(decimal_star=decimal_star, brand=brand) for idx, insight in enumerate(insights): html += f"""
{insight['priority']}

{insight['title']}

{insight['description']}

""" html += """

© System1 Group PLC

""" return html def create_sample_data(): """Create sample dataset for demonstration""" global current_df sample_data = { 'CreativeID': ['CR35383', 'CR82756', 'CR10436', 'CR90002', 'CR43967'], 'Title': [ 'Microsoft - Premium Smartphones Campaign', 'Google - Classic Mobile Plans Campaign', 'Amazon - New Mobile Plans Campaign', 'Microsoft - Premium Soft Drinks Campaign', 'Starbucks - New Soft Drinks Campaign' ], 'ReportingBrand': ['Microsoft', 'Google', 'Amazon', 'Microsoft', 'Starbucks'], 'ReportingAdvertiser': ['Elite Advertising', 'Dynamic Advertising Group', 'Elite Advertising', 'Platinum Marketing', 'Elite Advertising'], 'ReportingSubCategory': ['Technology', 'Telecommunications', 'Telecommunications', 'Beverages', 'Beverages'], 'Market': ['US', 'FR', 'JP', 'US', 'IN'], 'Duration': [48, 20, 23, 72, 27], 'EIA': [6.6, 3.7, 7.8, 3.0, 2.9], 'DecimalStar': [3.4, 4.5, 2.8, 1.5, 3.0], 'FinalFluency': [18.7, 0.9, 18.2, 0.2, 96.1], 'FastFluency': [73.8, 96.5, 91.9, 97.0, 98.1], 'Spike': [7.33, 3.5, 1.84, 8.05, 0.15], 'OEI': [53.1, 34.2, 37.3, 8.3, 52.0], 'TotalDynamism': [73.5, 38.6, 17.6, 11.1, 58.8], 'DynamismIndex': [66.7, 56.1, 107.2, 136.3, 126.3], 'Happiness (%)': [29.5, 17.0, 25.6, 31.0, 29.4], 'Surprise (%)': [12.1, 7.9, 9.8, 6.0, 7.9], 'Neutral (%)': [24.8, 49.9, 21.6, 38.6, 34.6], 'Sadness (%)': [10.7, 0.2, 7.3, 6.8, 0.5], 'Fear (%)': [3.7, 1.8, 4.5, 3.3, 3.6], 'Anger (%)': [14.7, 12.3, 12.6, 3.4, 6.4], 'Contempt (%)': [3.6, 6.9, 10.8, 8.0, 13.1], 'Disgust (%)': [0.9, 4.0, 7.8, 2.9, 4.5], 'CountCompletes': [196, 331, 183, 168, 459], 'SampleDescription': ['Sample of 196 respondents from UK', 'Sample of 331 respondents from UK', 'Sample of 183 respondents from CN', 'Sample of 168 respondents from DE', 'Sample of 459 respondents from AU'], 'SampleDetails': ['Mixed respondents, ages 27-42', 'Mixed respondents, ages 20-36', 'Mixed respondents, ages 26-37', 'Male respondents, ages 20-38', 'Mixed respondents, ages 27-34'], 'DisplayType': ['Video', 'Banner', 'Interactive', 'Image', 'Image'], 'SurveyType': ['Product Testing', 'Ad Effectiveness', 'Consumer Feedback', 'Product Testing', 'Brand Awareness'], 'StimuliType': ['Radio Spot', 'Radio Spot', 'Commercial', 'Billboard', 'Billboard'], 'AdvertMedium': ['Online', 'Online', 'Online', 'Television', 'Television'], 'AdvertFormat': ['Social media post', 'Banner ad', 'Banner ad', 'Banner ad', 'Pre-roll video'] } current_df = pd.DataFrame(sample_data) current_df = DataProcessor.calculate_creativity_scores(current_df) creative_ids = DataProcessor.get_creative_ids_list(current_df) return ( f"✅ Sample data loaded: {len(current_df)} campaigns", gr.update(choices=creative_ids, value=creative_ids[0] if creative_ids else None), f"Sample data ready for analysis" ) # Custom CSS for enhanced UI custom_css = """ .gradio-container { background: linear-gradient(135deg, #fce4ec 0%, #ffffff 100%); font-family: 'Arial', sans-serif; } .gr-button { background: linear-gradient(45deg, #C2185B, #E91E63) !important; color: white !important; border: none !important; border-radius: 10px !important; font-weight: bold !important; transition: all 0.3s ease !important; padding: 12px 24px !important; } .gr-button:hover { background: linear-gradient(45deg, #AD1457, #C2185B) !important; transform: translateY(-2px) !important; box-shadow: 0 6px 20px rgba(194, 24, 91, 0.3) !important; } .gr-form { background: white !important; border-radius: 15px !important; box-shadow: 0 8px 25px rgba(194, 24, 91, 0.1) !important; border: 2px solid #F8BBD9 !important; padding: 20px !important; } .gr-panel { background: white !important; border-radius: 12px !important; border: 1px solid #F8BBD9 !important; padding: 15px !important; } h1 { color: white !important; font-weight: bold !important; text-shadow: 1px 1px 2px rgba(0,0,0,0.2) !important; } h2 { color: #C2185B !important; font-weight: bold !important; margin-bottom: 15px !important; } .gr-dropdown { border: 2px solid #F8BBD9 !important; border-radius: 10px !important; padding: 8px !important; } .gr-file { border: 2px dashed #E91E63 !important; border-radius: 12px !important; background: #FCE4EC !important; padding: 15px !important; } .gr-textbox { border: 2px solid #F8BBD9 !important; border-radius: 10px !important; padding: 10px !important; background: #FCE4EC !important; } .gr-html { border-radius: 12px !important; border: 1px solid #F8BBD9 !important; padding: 20px !important; background: white !important; } .insight-card { background: white !important; border-radius: 10px !important; padding: 15px !important; box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1) !important; } """ def build_interface(): with gr.Blocks(css=custom_css, title="🎯 Advanced Ad Campaign Analyzer") as demo: gr.HTML("""

Express Guidance Analyser

AI-Powered Advertising Intelligence Platform

""") with gr.Row(): with gr.Column(scale=1): gr.HTML("

📊 Data Management

") file_input = gr.File( label="Upload Campaign Data (CSV/Excel)", file_types=[".csv", ".xlsx", ".xls"], type="binary" ) load_button = gr.Button("🚀 Load Data", variant="primary") sample_button = gr.Button("📋 Use Sample Data", variant="primary") load_status = gr.Textbox(label="Load Status", interactive=False) creative_dropdown = gr.Dropdown( label="Select Creative ID for Analysis", choices=[], interactive=True ) analyze_button = gr.Button("🔍 Analyze Campaign", variant="primary") with gr.Column(scale=3): gr.HTML("

📈 Analysis Results

") analysis_status = gr.Textbox(label="Analysis Status", interactive=False) with gr.Tabs(): with gr.TabItem("📊 Executive Dashboard"): dashboard_plot = gr.Plot(label="Performance Dashboard") with gr.TabItem("🎭 Emotional Analysis"): emotional_plot = gr.Plot(label="Emotional Profile") with gr.TabItem("📈 Performance Metrics"): performance_plot = gr.Plot(label="Performance Comparison") with gr.TabItem("🧠 Left-Brain Metrics"): left_brain_plot = gr.Plot(label="Logical Metrics") with gr.TabItem("🎨 Right-Brain Metrics"): right_brain_plot = gr.Plot(label="Emotional Metrics") gr.HTML("

🚀 AI Insights & Recommendations

") insights_output = gr.HTML(label="AI Insights & Recommendations") load_button.click( fn=load_data, inputs=file_input, outputs=[load_status, creative_dropdown, analysis_status] ) sample_button.click( fn=create_sample_data, inputs=None, outputs=[load_status, creative_dropdown, analysis_status] ) analyze_button.click( fn=analyze_campaign, inputs=creative_dropdown, outputs=[ analysis_status, dashboard_plot, emotional_plot, performance_plot, left_brain_plot, right_brain_plot, gr.State(), # State to enable/disable buttons insights_output ] ) return demo if __name__ == "__main__": demo = build_interface() demo.launch()