Upload app.py
Browse files
app.py
CHANGED
|
@@ -11,7 +11,6 @@ try:
|
|
| 11 |
models = joblib.load('email_quality_models.pkl')
|
| 12 |
scaler = joblib.load('feature_scaler.pkl')
|
| 13 |
day_encoder = joblib.load('day_encoder.pkl')
|
| 14 |
-
list_encoder = joblib.load('list_encoder.pkl')
|
| 15 |
feature_names = joblib.load('feature_names.pkl')
|
| 16 |
model_results = joblib.load('model_results.pkl')
|
| 17 |
except Exception as e:
|
|
@@ -53,50 +52,115 @@ def section_score(features):
|
|
| 53 |
score = max(0, min(100, score))
|
| 54 |
return score
|
| 55 |
|
| 56 |
-
def
|
| 57 |
-
|
| 58 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 59 |
subject_features = extract_text_features(subject)
|
| 60 |
preview_features = extract_text_features(preview_text)
|
| 61 |
body_features = extract_text_features(body_text)
|
| 62 |
-
|
| 63 |
-
# Section scores (placeholder logic)
|
| 64 |
subject_score = section_score(subject_features)
|
| 65 |
preview_score = section_score(preview_features)
|
| 66 |
body_score = section_score(body_features)
|
| 67 |
-
|
| 68 |
-
|
|
|
|
|
|
|
|
|
|
| 69 |
performance_score = int(round(0.4 * subject_score + 0.3 * preview_score + 0.3 * body_score))
|
| 70 |
-
|
|
|
|
| 71 |
# Sentiment analysis
|
| 72 |
text_for_sentiment = f"{subject}\n{preview_text}\n{body_text}"
|
| 73 |
sentiment_result = sentiment(text_for_sentiment)[0]
|
| 74 |
-
|
| 75 |
# Zero-shot classification
|
| 76 |
labels = ["engaging", "promotional", "informative", "urgent", "personal"]
|
| 77 |
classification_result = classifier(text_for_sentiment, labels)
|
| 78 |
-
|
| 79 |
-
# Recommendations (simple, based on features)
|
| 80 |
-
recommendations = []
|
| 81 |
-
if subject_features['length'] > 50:
|
| 82 |
-
recommendations.append(f"📧 Consider shortening your subject line (currently {subject_features['length']} chars)")
|
| 83 |
-
if subject_features['emoji_count'] == 0:
|
| 84 |
-
recommendations.append("😊 Consider adding an emoji to your subject line")
|
| 85 |
-
if preview_features['length'] < 20:
|
| 86 |
-
recommendations.append("👀 Add more detail to your preview text")
|
| 87 |
-
if body_features['word_count'] < 50:
|
| 88 |
-
recommendations.append("✍️ Consider a longer, more detailed email body")
|
| 89 |
-
if sentiment_result['label'] == "NEGATIVE":
|
| 90 |
-
recommendations.append("😬 Try a more positive tone in your email")
|
| 91 |
-
|
| 92 |
# Format output
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 93 |
output = f"""
|
| 94 |
## 📊 Performance Score: {performance_score}/100
|
| 95 |
|
| 96 |
-
###
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
- **
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 100 |
|
| 101 |
### 📈 Sentiment Analysis
|
| 102 |
- **Sentiment:** {sentiment_result['label']} (confidence: {sentiment_result['score']:.2f})
|
|
@@ -105,30 +169,17 @@ def analyze_email_complete(subject, preview_text, campaign_name, body_text, day_
|
|
| 105 |
"""
|
| 106 |
for i, (label, score) in enumerate(zip(classification_result['labels'][:3], classification_result['scores'][:3])):
|
| 107 |
output += f"- **{label.title()}**: {score:.2f}\n"
|
| 108 |
-
if recommendations:
|
| 109 |
-
output += "\n### 💡 Recommendations\n"
|
| 110 |
-
for rec in recommendations[:5]:
|
| 111 |
-
output += f"{rec}\n"
|
| 112 |
output += f"""
|
| 113 |
### 📋 Email Details
|
| 114 |
- **Subject Length:** {subject_features['length']} characters
|
| 115 |
- **Preview Length:** {preview_features['length']} characters
|
| 116 |
- **Body Word Count:** {body_features['word_count']} words
|
| 117 |
- **Send Time:** {send_time} on {day_of_week}
|
| 118 |
-
- **Target Audience:** {int(total_recipients):,} recipients
|
| 119 |
"""
|
| 120 |
return output
|
| 121 |
|
| 122 |
# Available options
|
| 123 |
day_options = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
|
| 124 |
-
list_options = [
|
| 125 |
-
'C4 - Very Engaged',
|
| 126 |
-
'C4 - Less Engaged',
|
| 127 |
-
'C4 - Very Engaged, C4 - Less Engaged',
|
| 128 |
-
'C4 - Re-Engage',
|
| 129 |
-
'New Users added in last 30 days (all entry points)',
|
| 130 |
-
'FMP: 2024 Premium Users Opted In to Weekly'
|
| 131 |
-
]
|
| 132 |
|
| 133 |
demo = gr.Interface(
|
| 134 |
fn=analyze_email_complete,
|
|
@@ -138,20 +189,18 @@ demo = gr.Interface(
|
|
| 138 |
gr.Textbox(label="📋 Campaign Name", placeholder="Enter campaign name"),
|
| 139 |
gr.Textbox(label="📝 Email Body", placeholder="Paste your email body here"),
|
| 140 |
gr.Dropdown(choices=day_options, label="📅 Day of Week", value="Thursday"),
|
| 141 |
-
gr.Dropdown(choices=list_options, label="📮 Email List", value="C4 - Very Engaged"),
|
| 142 |
gr.Textbox(label="⏰ Send Time", placeholder="9:00 AM", value="9:00 AM"),
|
| 143 |
-
gr.
|
| 144 |
-
gr.Radio(choices=['open_rate', 'click_rate', 'unsubscribe_rate'],
|
| 145 |
label="🎯 Target Metric", value='click_rate')
|
| 146 |
],
|
| 147 |
outputs=gr.Markdown(),
|
| 148 |
title="🚀 Email Performance Predictor - Forks Over Knives",
|
| 149 |
-
description="Predict email performance and get actionable recommendations based on your campaign data",
|
| 150 |
examples=[
|
| 151 |
-
["Wrap Up Your Monday with Flavor 🌯🥑", "Ready in minutes—perfect for lunch, dinner, or...",
|
| 152 |
-
"Meatless Monday | Black Bean Avo Wraps", "Try our new wraps! They're delicious and easy to make.", "Monday", "
|
| 153 |
-
["NEW Special Issue: Plant-Based Bowls", "Get your first look inside the latest issue...",
|
| 154 |
-
"Plant-Based Bowls Special Issue", "Discover 50+ new recipes in this special issue.", "Saturday", "
|
| 155 |
]
|
| 156 |
)
|
| 157 |
|
|
|
|
| 11 |
models = joblib.load('email_quality_models.pkl')
|
| 12 |
scaler = joblib.load('feature_scaler.pkl')
|
| 13 |
day_encoder = joblib.load('day_encoder.pkl')
|
|
|
|
| 14 |
feature_names = joblib.load('feature_names.pkl')
|
| 15 |
model_results = joblib.load('model_results.pkl')
|
| 16 |
except Exception as e:
|
|
|
|
| 52 |
score = max(0, min(100, score))
|
| 53 |
return score
|
| 54 |
|
| 55 |
+
def section_suggestion(section, features):
|
| 56 |
+
# Simple, section-specific suggestions
|
| 57 |
+
if section == "subject":
|
| 58 |
+
if features['length'] > 50:
|
| 59 |
+
return "Try shortening your subject line for better impact."
|
| 60 |
+
if features['emoji_count'] == 0:
|
| 61 |
+
return "Add an emoji to make your subject line stand out."
|
| 62 |
+
if features['exclamation_count'] == 0:
|
| 63 |
+
return "Consider adding an exclamation mark for urgency."
|
| 64 |
+
return "Your subject line looks good!"
|
| 65 |
+
elif section == "preview":
|
| 66 |
+
if features['length'] < 20:
|
| 67 |
+
return "Add more detail to your preview text."
|
| 68 |
+
if features['emoji_count'] == 0:
|
| 69 |
+
return "Try adding an emoji to your preview text."
|
| 70 |
+
return "Your preview text is engaging!"
|
| 71 |
+
elif section == "body":
|
| 72 |
+
if features['word_count'] < 50:
|
| 73 |
+
return "Consider a longer, more detailed email body."
|
| 74 |
+
if features['exclamation_count'] == 0:
|
| 75 |
+
return "Try using an exclamation mark to highlight key points."
|
| 76 |
+
return "Your email body is well-structured!"
|
| 77 |
+
return ""
|
| 78 |
+
|
| 79 |
+
def predict_email_performance(subject, preview_text, campaign_name, body_text, day_of_week, send_time, target_metric):
|
| 80 |
+
# Extract text features
|
| 81 |
+
subject_features = extract_text_features(subject)
|
| 82 |
+
campaign_features = extract_text_features(campaign_name)
|
| 83 |
+
preview_features = extract_text_features(preview_text)
|
| 84 |
+
body_features = extract_text_features(body_text)
|
| 85 |
+
# Parse send time
|
| 86 |
+
try:
|
| 87 |
+
send_hour = datetime.strptime(send_time, '%I:%M %p').hour
|
| 88 |
+
except:
|
| 89 |
+
send_hour = 9 # Default to 9 AM
|
| 90 |
+
# Encode categorical variables
|
| 91 |
+
try:
|
| 92 |
+
day_encoded = day_encoder.transform([day_of_week])[0]
|
| 93 |
+
except:
|
| 94 |
+
day_encoded = 0 # Default encoding
|
| 95 |
+
# Create feature vector (no list or audience size)
|
| 96 |
+
features = [
|
| 97 |
+
500000, # Placeholder for audience size (kept for model compatibility)
|
| 98 |
+
send_hour,
|
| 99 |
+
day_encoded,
|
| 100 |
+
0 # Placeholder for list (kept for model compatibility)
|
| 101 |
+
]
|
| 102 |
+
# Add text features in correct order
|
| 103 |
+
for prefix, feats in zip(['subject_', 'campaign_', 'preview_'], [subject_features, campaign_features, preview_features]):
|
| 104 |
+
for suffix in ['length', 'word_count', 'exclamation_count', 'question_count', 'emoji_count', 'number_count', 'caps_ratio']:
|
| 105 |
+
features.append(feats[suffix])
|
| 106 |
+
# For body, just append features (if you want to use them in the model, retrain with these features)
|
| 107 |
+
for suffix in ['length', 'word_count', 'exclamation_count', 'question_count', 'emoji_count', 'number_count', 'caps_ratio']:
|
| 108 |
+
features.append(body_features[suffix])
|
| 109 |
+
# Scale features (truncate or pad to match model input)
|
| 110 |
+
features = features[:len(feature_names)]
|
| 111 |
+
features_scaled = scaler.transform([features])
|
| 112 |
+
# Make prediction
|
| 113 |
+
model = models[target_metric]
|
| 114 |
+
prediction = model.predict(features_scaled)[0]
|
| 115 |
+
# Convert to percentage and ensure reasonable bounds
|
| 116 |
+
if target_metric == 'open_rate':
|
| 117 |
+
prediction = max(0, min(1, prediction)) * 100
|
| 118 |
+
elif target_metric == 'click_rate':
|
| 119 |
+
prediction = max(0, min(0.5, prediction)) * 100
|
| 120 |
+
else: # unsubscribe_rate
|
| 121 |
+
prediction = max(0, min(0.1, prediction)) * 100
|
| 122 |
+
return prediction
|
| 123 |
+
|
| 124 |
+
def analyze_email_complete(subject, preview_text, campaign_name, body_text, day_of_week, send_time, target_metric):
|
| 125 |
+
# Section features and scores
|
| 126 |
subject_features = extract_text_features(subject)
|
| 127 |
preview_features = extract_text_features(preview_text)
|
| 128 |
body_features = extract_text_features(body_text)
|
|
|
|
|
|
|
| 129 |
subject_score = section_score(subject_features)
|
| 130 |
preview_score = section_score(preview_features)
|
| 131 |
body_score = section_score(body_features)
|
| 132 |
+
# Section suggestions
|
| 133 |
+
subject_sugg = section_suggestion("subject", subject_features)
|
| 134 |
+
preview_sugg = section_suggestion("preview", preview_features)
|
| 135 |
+
body_sugg = section_suggestion("body", body_features)
|
| 136 |
+
# Overall performance score (weighted avg)
|
| 137 |
performance_score = int(round(0.4 * subject_score + 0.3 * preview_score + 0.3 * body_score))
|
| 138 |
+
# Predicted metric
|
| 139 |
+
predicted_value = predict_email_performance(subject, preview_text, campaign_name, body_text, day_of_week, send_time, target_metric)
|
| 140 |
# Sentiment analysis
|
| 141 |
text_for_sentiment = f"{subject}\n{preview_text}\n{body_text}"
|
| 142 |
sentiment_result = sentiment(text_for_sentiment)[0]
|
|
|
|
| 143 |
# Zero-shot classification
|
| 144 |
labels = ["engaging", "promotional", "informative", "urgent", "personal"]
|
| 145 |
classification_result = classifier(text_for_sentiment, labels)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 146 |
# Format output
|
| 147 |
+
metric_label = {
|
| 148 |
+
"open_rate": "Open Rate",
|
| 149 |
+
"click_rate": "Click Rate",
|
| 150 |
+
"unsubscribe_rate": "Unsubscribe Rate"
|
| 151 |
+
}[target_metric]
|
| 152 |
output = f"""
|
| 153 |
## 📊 Performance Score: {performance_score}/100
|
| 154 |
|
| 155 |
+
### 🎯 Predicted {metric_label}: {predicted_value:.2f}%
|
| 156 |
+
|
| 157 |
+
### ✂️ Section Scores & Suggestions
|
| 158 |
+
- **Subject Line:** {subject_score}/100
|
| 159 |
+
_Suggestion: {subject_sugg}_
|
| 160 |
+
- **Preview Text:** {preview_score}/100
|
| 161 |
+
_Suggestion: {preview_sugg}_
|
| 162 |
+
- **Body Text:** {body_score}/100
|
| 163 |
+
_Suggestion: {body_sugg}_
|
| 164 |
|
| 165 |
### 📈 Sentiment Analysis
|
| 166 |
- **Sentiment:** {sentiment_result['label']} (confidence: {sentiment_result['score']:.2f})
|
|
|
|
| 169 |
"""
|
| 170 |
for i, (label, score) in enumerate(zip(classification_result['labels'][:3], classification_result['scores'][:3])):
|
| 171 |
output += f"- **{label.title()}**: {score:.2f}\n"
|
|
|
|
|
|
|
|
|
|
|
|
|
| 172 |
output += f"""
|
| 173 |
### 📋 Email Details
|
| 174 |
- **Subject Length:** {subject_features['length']} characters
|
| 175 |
- **Preview Length:** {preview_features['length']} characters
|
| 176 |
- **Body Word Count:** {body_features['word_count']} words
|
| 177 |
- **Send Time:** {send_time} on {day_of_week}
|
|
|
|
| 178 |
"""
|
| 179 |
return output
|
| 180 |
|
| 181 |
# Available options
|
| 182 |
day_options = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 183 |
|
| 184 |
demo = gr.Interface(
|
| 185 |
fn=analyze_email_complete,
|
|
|
|
| 189 |
gr.Textbox(label="📋 Campaign Name", placeholder="Enter campaign name"),
|
| 190 |
gr.Textbox(label="📝 Email Body", placeholder="Paste your email body here"),
|
| 191 |
gr.Dropdown(choices=day_options, label="📅 Day of Week", value="Thursday"),
|
|
|
|
| 192 |
gr.Textbox(label="⏰ Send Time", placeholder="9:00 AM", value="9:00 AM"),
|
| 193 |
+
gr.Radio(choices=['open_rate', 'click_rate', 'unsubscribe_rate'],
|
|
|
|
| 194 |
label="🎯 Target Metric", value='click_rate')
|
| 195 |
],
|
| 196 |
outputs=gr.Markdown(),
|
| 197 |
title="🚀 Email Performance Predictor - Forks Over Knives",
|
| 198 |
+
description="Predict email performance and get actionable, section-specific recommendations based on your campaign data",
|
| 199 |
examples=[
|
| 200 |
+
["Wrap Up Your Monday with Flavor 🌯🥑", "Ready in minutes—perfect for lunch, dinner, or...",
|
| 201 |
+
"Meatless Monday | Black Bean Avo Wraps", "Try our new wraps! They're delicious and easy to make.", "Monday", "9:00 AM", "click_rate"],
|
| 202 |
+
["NEW Special Issue: Plant-Based Bowls", "Get your first look inside the latest issue...",
|
| 203 |
+
"Plant-Based Bowls Special Issue", "Discover 50+ new recipes in this special issue.", "Saturday", "1:30 AM", "open_rate"]
|
| 204 |
]
|
| 205 |
)
|
| 206 |
|