Express_Guide / app.py
rashmibezalwar's picture
Update app.py
e5d6023 verified
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<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
@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, "<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()