Spaces:
Sleeping
Sleeping
| 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""" | |
| 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 | |
| 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 | |
| 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()) | |
| 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""" | |
| 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<br><span style="font-size:16px;color:#666">{data["title"]} - {data["brand"]}</span>', | |
| '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 | |
| 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 | |
| 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 | |
| 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 | |
| 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, "<p>No recommendations available.</p>" | |
| if not creative_id: | |
| return "Please select a Creative ID.", None, None, None, None, None, False, "<p>No recommendations available.</p>" | |
| 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, "<p>No recommendations available.</p>" | |
| 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, "<p>Error generating recommendations.</p>" | |
| def show_recommendations(brand: str, decimal_star: float) -> str: | |
| """Generate HTML for recommendations and AI insights""" | |
| global current_recommendations | |
| if not current_recommendations: | |
| return "<p>No recommendations available.</p>" | |
| # 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 = """ | |
| <div style='padding: 20px; background: linear-gradient(45deg, #E91E63, #F8BBD9); border-radius: 15px;'> | |
| <h2 style='color: white; font-family: Arial; text-align: center; margin-bottom: 20px; font-size: 24px; text-transform: uppercase;'>Recommendations</h2> | |
| <p style='color: white; font-family: Arial; text-align: center; margin-bottom: 30px; font-size: 16px;'> | |
| 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. | |
| </p> | |
| <div style='display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px;'> | |
| """.format(decimal_star=decimal_star, brand=brand) | |
| for idx, insight in enumerate(insights): | |
| html += f""" | |
| <div class='insight-card'> | |
| <div style='display: flex; align-items: center; margin-bottom: 10px;'> | |
| <span style='color: #C2185B; font-size: 20px; margin-right: 10px;'>{insight['priority']}</span> | |
| <h3 style='color: #333; font-family: Arial; font-size: 16px; font-weight: bold; margin: 0; text-transform: uppercase;'>{insight['title']}</h3> | |
| </div> | |
| <p style='color: #333; font-family: Arial; font-size: 14px; line-height: 1.5;'>{insight['description']}</p> | |
| </div> | |
| """ | |
| html += """ | |
| </div> | |
| <p style='color: white; font-family: Arial; text-align: right; font-size: 12px; margin-top: 20px;'>Β© System1 Group PLC</p> | |
| </div> | |
| """ | |
| 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(""" | |
| <div style="text-align: center; padding: 30px; background: linear-gradient(45deg, #C2185B, #E91E63); color: white; border-radius: 20px; margin-bottom: 25px;"> | |
| <h1 style="margin: 0; font-size: 2.8em;"> Express Guidance Analyser </h1> | |
| <p style="margin: 15px 0 0 0; font-size: 1.3em; color: rgba(255,255,255,0.9);"> | |
| AI-Powered Advertising Intelligence Platform | |
| </p> | |
| </div> | |
| """) | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| gr.HTML("<h2>π Data Management</h2>") | |
| 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("<h2>π Analysis Results</h2>") | |
| 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("<h2>π AI Insights & Recommendations</h2>") | |
| 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() |