Spaces:
Sleeping
Sleeping
Upload 12 files
Browse files- app.py +228 -0
- projectstructure.txt +17 -0
- requirements.txt +8 -0
- services/__pycache__/zero_shot.cpython-311.pyc +0 -0
- services/copy_optimizer.py +40 -0
- services/cta_analysis.py +54 -0
- services/meta_ads_api.py +29 -0
- services/sentiment.py +35 -0
- services/similarity.py +36 -0
- services/zero_shot.py +49 -0
- utils/scoring.py +43 -0
- utils/trend_analysis.py +24 -0
app.py
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import streamlit as st
|
| 2 |
+
import os
|
| 3 |
+
import warnings
|
| 4 |
+
from warnings import filterwarnings
|
| 5 |
+
|
| 6 |
+
# ---------- Services ----------
|
| 7 |
+
from services.zero_shot import classify_intent
|
| 8 |
+
from services.sentiment import detect_emotion
|
| 9 |
+
from services.similarity import text_quality_score
|
| 10 |
+
from services.cta_analysis import cta_strength
|
| 11 |
+
from services.copy_optimizer import optimize_copy
|
| 12 |
+
from services.meta_ads_api import fetch_live_ads
|
| 13 |
+
|
| 14 |
+
# ---------- Utils ----------
|
| 15 |
+
from utils.scoring import final_score
|
| 16 |
+
from utils.trend_analysis import market_trends
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
# ---------- Page Config ----------
|
| 20 |
+
st.set_page_config(
|
| 21 |
+
page_title="Meta AI Ads Intelligence Tool",
|
| 22 |
+
page_icon="📢",
|
| 23 |
+
layout="wide"
|
| 24 |
+
)
|
| 25 |
+
|
| 26 |
+
st.title("📢 Meta AI Ads Intelligence Tool")
|
| 27 |
+
st.caption("Live Meta Ads • Market Trends • AI Creative Analysis")
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
# ---------- Sidebar ----------
|
| 31 |
+
menu = st.sidebar.radio(
|
| 32 |
+
"Navigation",
|
| 33 |
+
[
|
| 34 |
+
"📊 Overview",
|
| 35 |
+
"🎯 Analyze My Ad",
|
| 36 |
+
"🧠 Live Competitor Ads",
|
| 37 |
+
"⚠️ Ad Fatigue Checker",
|
| 38 |
+
"✍️ Copy Optimizer",
|
| 39 |
+
"ℹ️ About"
|
| 40 |
+
]
|
| 41 |
+
)
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
# =========================================================
|
| 45 |
+
# 📊 OVERVIEW
|
| 46 |
+
# =========================================================
|
| 47 |
+
if menu == "📊 Overview":
|
| 48 |
+
st.subheader("What does this tool do?")
|
| 49 |
+
|
| 50 |
+
st.write(
|
| 51 |
+
"""
|
| 52 |
+
This platform combines **Meta Ads Library live data** with **pretrained AI models**
|
| 53 |
+
to analyze ad creatives, market trends, and competitor messaging — without using
|
| 54 |
+
any historical performance data.
|
| 55 |
+
"""
|
| 56 |
+
)
|
| 57 |
+
|
| 58 |
+
col1, col2, col3 = st.columns(3)
|
| 59 |
+
col1.metric("Live Meta Ads", "Yes")
|
| 60 |
+
col2.metric("Model Training", "Not Required")
|
| 61 |
+
col3.metric("Analysis Type", "Real-Time")
|
| 62 |
+
|
| 63 |
+
st.info("🔒 No ads are stored. All analysis runs on demand.")
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
# =========================================================
|
| 67 |
+
# 🎯 ANALYZE USER AD
|
| 68 |
+
# =========================================================
|
| 69 |
+
elif menu == "🎯 Analyze My Ad":
|
| 70 |
+
st.subheader("Analyze Your Ad Creative")
|
| 71 |
+
|
| 72 |
+
col1, col2 = st.columns(2)
|
| 73 |
+
|
| 74 |
+
with col1:
|
| 75 |
+
caption = st.text_area("Ad Caption / Primary Text", height=150)
|
| 76 |
+
cta = st.selectbox(
|
| 77 |
+
"Call To Action",
|
| 78 |
+
["Buy Now", "Shop Now", "Learn More", "DM Us", "Sign Up", "Check It Out"]
|
| 79 |
+
)
|
| 80 |
+
analyze = st.button("Analyze Ad")
|
| 81 |
+
|
| 82 |
+
with col2:
|
| 83 |
+
if analyze and caption.strip():
|
| 84 |
+
with st.spinner("Running AI analysis..."):
|
| 85 |
+
intent = classify_intent(caption)
|
| 86 |
+
emotion = detect_emotion(caption)
|
| 87 |
+
quality = text_quality_score(caption)
|
| 88 |
+
cta_score = cta_strength(cta)
|
| 89 |
+
|
| 90 |
+
score = final_score(
|
| 91 |
+
intent["score"],
|
| 92 |
+
emotion["score"],
|
| 93 |
+
cta_score,
|
| 94 |
+
quality
|
| 95 |
+
)
|
| 96 |
+
|
| 97 |
+
st.metric("Performance Score", f"{score}/100")
|
| 98 |
+
|
| 99 |
+
if score >= 75:
|
| 100 |
+
st.success("🟢 Low Risk – Ready to Run")
|
| 101 |
+
elif score >= 50:
|
| 102 |
+
st.warning("🟡 Medium Risk – Needs Optimization")
|
| 103 |
+
else:
|
| 104 |
+
st.error("🔴 High Risk – Likely Budget Waste")
|
| 105 |
+
|
| 106 |
+
st.progress(score / 100)
|
| 107 |
+
|
| 108 |
+
st.markdown("### 🔍 AI Insights")
|
| 109 |
+
st.write(f"**Intent:** {intent['label']}")
|
| 110 |
+
st.write(f"**Emotion:** {emotion['emotion']}")
|
| 111 |
+
st.write(f"**CTA Strength:** {round(cta_score * 100)}%")
|
| 112 |
+
st.write(f"**Text Quality:** {round(quality * 100)}%")
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
# =========================================================
|
| 116 |
+
# 🧠 LIVE COMPETITOR ADS (META ADS LIBRARY)
|
| 117 |
+
# =========================================================
|
| 118 |
+
elif menu == "🧠 Live Competitor Ads":
|
| 119 |
+
st.subheader("Live Competitor Ads (Meta Ads Library)")
|
| 120 |
+
|
| 121 |
+
keyword = st.text_input("Search Keyword / Brand / Product")
|
| 122 |
+
country = st.selectbox("Country", ["IN", "US", "UK", "AE"])
|
| 123 |
+
|
| 124 |
+
if st.button("Fetch Live Ads"):
|
| 125 |
+
try:
|
| 126 |
+
with st.spinner("Fetching live ads from Meta Ads Library..."):
|
| 127 |
+
ads = fetch_live_ads(keyword, country)
|
| 128 |
+
|
| 129 |
+
if not ads:
|
| 130 |
+
st.warning("No ads found for this keyword.")
|
| 131 |
+
else:
|
| 132 |
+
st.success(f"Fetched {len(ads)} live ads")
|
| 133 |
+
|
| 134 |
+
# ---------- Market Trends ----------
|
| 135 |
+
trends = market_trends(ads)
|
| 136 |
+
|
| 137 |
+
st.markdown("### 📊 Market Trend Analysis")
|
| 138 |
+
st.write("**Total Live Ads:**", trends["total_ads"])
|
| 139 |
+
st.write("**Trending Keywords:**", ", ".join(trends["top_keywords"]))
|
| 140 |
+
|
| 141 |
+
st.divider()
|
| 142 |
+
|
| 143 |
+
# ---------- Show Ads + AI Analysis ----------
|
| 144 |
+
for ad in ads[:5]:
|
| 145 |
+
st.markdown(f"### 🏷️ {ad['page_name']}")
|
| 146 |
+
st.write(ad["ad_creative_body"])
|
| 147 |
+
|
| 148 |
+
intent = classify_intent(ad["ad_creative_body"])
|
| 149 |
+
emotion = detect_emotion(ad["ad_creative_body"])
|
| 150 |
+
|
| 151 |
+
st.caption(
|
| 152 |
+
f"Intent: {intent['label']} | "
|
| 153 |
+
f"Emotion: {emotion['emotion']}"
|
| 154 |
+
)
|
| 155 |
+
|
| 156 |
+
st.divider()
|
| 157 |
+
|
| 158 |
+
except Exception as e:
|
| 159 |
+
st.error(f"Error fetching ads: {e}")
|
| 160 |
+
|
| 161 |
+
|
| 162 |
+
# =========================================================
|
| 163 |
+
# ⚠️ AD FATIGUE CHECKER
|
| 164 |
+
# =========================================================
|
| 165 |
+
elif menu == "⚠️ Ad Fatigue Checker":
|
| 166 |
+
st.subheader("Ad Fatigue Risk Estimator")
|
| 167 |
+
|
| 168 |
+
caption = st.text_area("Ad Caption", height=120)
|
| 169 |
+
days = st.slider("Planned Run Duration (Days)", 1, 30, 7)
|
| 170 |
+
frequency = st.slider("Estimated Frequency", 1.0, 5.0, 2.0)
|
| 171 |
+
|
| 172 |
+
if st.button("Check Fatigue"):
|
| 173 |
+
fatigue_risk = min((days * frequency) / 30, 1.0)
|
| 174 |
+
|
| 175 |
+
st.metric("Fatigue Risk", f"{round(fatigue_risk * 100)}%")
|
| 176 |
+
st.progress(fatigue_risk)
|
| 177 |
+
|
| 178 |
+
if fatigue_risk > 0.7:
|
| 179 |
+
st.error("High Fatigue Risk – Refresh Creative")
|
| 180 |
+
elif fatigue_risk > 0.4:
|
| 181 |
+
st.warning("Medium Risk – Monitor Performance")
|
| 182 |
+
else:
|
| 183 |
+
st.success("Low Risk – Safe to Run")
|
| 184 |
+
|
| 185 |
+
|
| 186 |
+
# =========================================================
|
| 187 |
+
# ✍️ COPY OPTIMIZER
|
| 188 |
+
# =========================================================
|
| 189 |
+
elif menu == "✍️ Copy Optimizer":
|
| 190 |
+
st.subheader("AI Copy Optimization")
|
| 191 |
+
|
| 192 |
+
caption = st.text_area("Original Caption", height=150)
|
| 193 |
+
|
| 194 |
+
if st.button("Get Suggestions") and caption.strip():
|
| 195 |
+
tips = optimize_copy(caption)
|
| 196 |
+
|
| 197 |
+
if tips:
|
| 198 |
+
st.markdown("### ✨ Optimization Suggestions")
|
| 199 |
+
for tip in tips:
|
| 200 |
+
st.write("•", tip)
|
| 201 |
+
else:
|
| 202 |
+
st.success("Your caption already follows best practices!")
|
| 203 |
+
|
| 204 |
+
|
| 205 |
+
# =========================================================
|
| 206 |
+
# ℹ️ ABOUT
|
| 207 |
+
# =========================================================
|
| 208 |
+
elif menu == "ℹ️ About":
|
| 209 |
+
st.subheader("About This Project")
|
| 210 |
+
|
| 211 |
+
st.write(
|
| 212 |
+
"""
|
| 213 |
+
**Meta AI Ads Intelligence Tool** is a real-time marketing intelligence platform.
|
| 214 |
+
|
| 215 |
+
### Key Capabilities
|
| 216 |
+
- Live Meta Ads Library integration
|
| 217 |
+
- Market trend analysis
|
| 218 |
+
- Zero-shot intent classification
|
| 219 |
+
- Emotion detection
|
| 220 |
+
- No model training or historical data
|
| 221 |
+
|
| 222 |
+
### Tech Stack
|
| 223 |
+
- Python
|
| 224 |
+
- Streamlit
|
| 225 |
+
- HuggingFace Transformers
|
| 226 |
+
- Meta Ads Library API
|
| 227 |
+
"""
|
| 228 |
+
)
|
projectstructure.txt
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
meta_ai_ads_tool/
|
| 2 |
+
│
|
| 3 |
+
├── app.py
|
| 4 |
+
│
|
| 5 |
+
├── services/
|
| 6 |
+
│ ├── meta_ads_api.py 👈 META API KEY USED HERE
|
| 7 |
+
│ ├── zero_shot.py
|
| 8 |
+
│ ├── sentiment.py
|
| 9 |
+
│ ├── similarity.py
|
| 10 |
+
│ ├── cta_analysis.py
|
| 11 |
+
│ └── copy_optimizer.py
|
| 12 |
+
│
|
| 13 |
+
├── utils/
|
| 14 |
+
│ ├── scoring.py
|
| 15 |
+
│ └── trend_analysis.py 👈 MARKET TRENDS
|
| 16 |
+
│
|
| 17 |
+
└── requirements.txt
|
requirements.txt
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
streamlit==1.28.0
|
| 2 |
+
transformers==4.40.0
|
| 3 |
+
sentence-transformers==2.2.2
|
| 4 |
+
torch>=2.0.0
|
| 5 |
+
numpy>=1.24.0
|
| 6 |
+
requests>=2.31.0
|
| 7 |
+
scikit-learn>=1.3.0
|
| 8 |
+
pandas>=2.1.0
|
services/__pycache__/zero_shot.cpython-311.pyc
ADDED
|
Binary file (1.54 kB). View file
|
|
|
services/copy_optimizer.py
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
def optimize_copy(ad_text: str):
|
| 2 |
+
"""
|
| 3 |
+
Provides actionable suggestions to improve ad captions.
|
| 4 |
+
Rule-based, no training required.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
if not ad_text or len(ad_text.strip()) == 0:
|
| 8 |
+
return ["Ad text is empty, please provide text."]
|
| 9 |
+
|
| 10 |
+
suggestions = []
|
| 11 |
+
|
| 12 |
+
# Suggest shortening long captions
|
| 13 |
+
word_count = len(ad_text.split())
|
| 14 |
+
if word_count > 20:
|
| 15 |
+
suggestions.append("Consider shortening the caption (under 20 words).")
|
| 16 |
+
|
| 17 |
+
# Add urgency if missing
|
| 18 |
+
urgency_words = ["now", "today", "limited", "hurry", "exclusive", "offer"]
|
| 19 |
+
if not any(word in ad_text.lower() for word in urgency_words):
|
| 20 |
+
suggestions.append("Add urgency words like 'now', 'limited', 'exclusive'.")
|
| 21 |
+
|
| 22 |
+
# Check for punctuation / excitement
|
| 23 |
+
if "!" not in ad_text:
|
| 24 |
+
suggestions.append("Add punctuation or exclamation marks for excitement.")
|
| 25 |
+
|
| 26 |
+
# Suggest including a CTA if missing
|
| 27 |
+
cta_words = ["buy", "shop", "order", "download", "register", "sign up"]
|
| 28 |
+
if not any(word in ad_text.lower() for word in cta_words):
|
| 29 |
+
suggestions.append("Include a clear call-to-action (CTA) in your caption.")
|
| 30 |
+
|
| 31 |
+
# Suggest adding emotional trigger words
|
| 32 |
+
emotional_words = ["amazing", "best", "incredible", "free", "surprise"]
|
| 33 |
+
if not any(word in ad_text.lower() for word in emotional_words):
|
| 34 |
+
suggestions.append("Consider adding emotional trigger words to attract attention.")
|
| 35 |
+
|
| 36 |
+
# If no suggestions, compliment the copy
|
| 37 |
+
if len(suggestions) == 0:
|
| 38 |
+
suggestions.append("Your caption follows best practices!")
|
| 39 |
+
|
| 40 |
+
return suggestions
|
services/cta_analysis.py
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import re
|
| 2 |
+
|
| 3 |
+
# High-performing CTA keywords (marketing proven)
|
| 4 |
+
STRONG_CTA = [
|
| 5 |
+
"buy now", "shop now", "order now", "get started",
|
| 6 |
+
"sign up", "register", "download now", "limited offer",
|
| 7 |
+
"claim now", "book now", "subscribe", "grab now"
|
| 8 |
+
]
|
| 9 |
+
|
| 10 |
+
MEDIUM_CTA = [
|
| 11 |
+
"learn more", "discover", "find out", "see more",
|
| 12 |
+
"explore", "know more", "view details"
|
| 13 |
+
]
|
| 14 |
+
|
| 15 |
+
WEAK_CTA = [
|
| 16 |
+
"click here", "visit us", "check this",
|
| 17 |
+
"read more", "watch now"
|
| 18 |
+
]
|
| 19 |
+
|
| 20 |
+
def analyze_cta(ad_text: str):
|
| 21 |
+
"""
|
| 22 |
+
Analyze CTA strength inside ad copy
|
| 23 |
+
Returns CTA score and insights
|
| 24 |
+
"""
|
| 25 |
+
|
| 26 |
+
if not ad_text or len(ad_text.strip()) == 0:
|
| 27 |
+
return {"error": "Empty ad text"}
|
| 28 |
+
|
| 29 |
+
text = ad_text.lower()
|
| 30 |
+
|
| 31 |
+
found_strong = [cta for cta in STRONG_CTA if cta in text]
|
| 32 |
+
found_medium = [cta for cta in MEDIUM_CTA if cta in text]
|
| 33 |
+
found_weak = [cta for cta in WEAK_CTA if cta in text]
|
| 34 |
+
|
| 35 |
+
score = 0
|
| 36 |
+
|
| 37 |
+
# Scoring logic
|
| 38 |
+
score += len(found_strong) * 10
|
| 39 |
+
score += len(found_medium) * 5
|
| 40 |
+
score += len(found_weak) * 2
|
| 41 |
+
|
| 42 |
+
# Penalty if no CTA
|
| 43 |
+
if not (found_strong or found_medium or found_weak):
|
| 44 |
+
score -= 10
|
| 45 |
+
|
| 46 |
+
score = max(min(score, 100), 0)
|
| 47 |
+
|
| 48 |
+
return {
|
| 49 |
+
"cta_score": score,
|
| 50 |
+
"found_strong_cta": found_strong,
|
| 51 |
+
"found_medium_cta": found_medium,
|
| 52 |
+
"found_weak_cta": found_weak,
|
| 53 |
+
"has_cta": score > 0
|
| 54 |
+
}
|
services/meta_ads_api.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import requests
|
| 2 |
+
import os
|
| 3 |
+
|
| 4 |
+
META_ADS_TOKEN = os.getenv("EAAVtBlZBaes0BQZAfPSf7ZBO7Yv0WjULDVn3zZAwvgbdvCtU24x9MNG8rxGArSXDg4GFZBB3GBJOs5qblEU6BKCNx9GypIZAcneDRnuZBfkLYEWDZAMnzqblUUVNtPSjul792ZBuorTN36XHgqZCsOlFox4rsyYnw9Kjl2u244lfWsGZC4oCNIEwjslyljVryVXOY5W1LRiZAbnZCrMQ6GLdkkmKqJTkuLKKjrQxnPcnl7xCopI44nF1Moa3wtP1e61TQAoXc1UvdkEvllaJg7ircBzwYpILl")
|
| 5 |
+
BASE_URL = "https://www.facebook.com/ads/library/?active_status=all&ad_type=political_and_issue_ads&country=IN&is_targeted_country=false&media_type=all"
|
| 6 |
+
|
| 7 |
+
def fetch_live_ads(keyword, country="IN", limit=20):
|
| 8 |
+
if not META_ADS_TOKEN:
|
| 9 |
+
raise ValueError("META_ADS_TOKEN not set")
|
| 10 |
+
|
| 11 |
+
params = {
|
| 12 |
+
"search_terms": keyword,
|
| 13 |
+
"ad_reached_countries": country,
|
| 14 |
+
"ad_type": "ALL",
|
| 15 |
+
"fields": (
|
| 16 |
+
"page_name,"
|
| 17 |
+
"ad_creative_body,"
|
| 18 |
+
"ad_creative_link_title,"
|
| 19 |
+
"ad_delivery_start_time"
|
| 20 |
+
),
|
| 21 |
+
"limit": limit,
|
| 22 |
+
"access_token": META_ADS_TOKEN
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
response = requests.get(BASE_URL, params=params)
|
| 26 |
+
response.raise_for_status()
|
| 27 |
+
|
| 28 |
+
ads = response.json().get("data", [])
|
| 29 |
+
return [ad for ad in ads if ad.get("ad_creative_body")]
|
services/sentiment.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from transformers import pipeline
|
| 2 |
+
|
| 3 |
+
# Pretrained emotion detection model
|
| 4 |
+
# No training required
|
| 5 |
+
emotion_classifier = pipeline(
|
| 6 |
+
task="text-classification",
|
| 7 |
+
model="j-hartmann/emotion-english-distilroberta-base",
|
| 8 |
+
return_all_scores=True
|
| 9 |
+
)
|
| 10 |
+
|
| 11 |
+
def analyze_emotion(ad_text: str):
|
| 12 |
+
"""
|
| 13 |
+
Analyze emotional tone of an ad caption
|
| 14 |
+
Returns emotion scores
|
| 15 |
+
"""
|
| 16 |
+
|
| 17 |
+
if not ad_text or len(ad_text.strip()) == 0:
|
| 18 |
+
return {"error": "Empty ad text"}
|
| 19 |
+
|
| 20 |
+
result = emotion_classifier(ad_text)[0]
|
| 21 |
+
|
| 22 |
+
emotions = []
|
| 23 |
+
for item in result:
|
| 24 |
+
emotions.append({
|
| 25 |
+
"emotion": item["label"],
|
| 26 |
+
"confidence": round(item["score"], 3)
|
| 27 |
+
})
|
| 28 |
+
|
| 29 |
+
# Sort by highest confidence
|
| 30 |
+
emotions = sorted(emotions, key=lambda x: x["confidence"], reverse=True)
|
| 31 |
+
|
| 32 |
+
return {
|
| 33 |
+
"ad_text": ad_text,
|
| 34 |
+
"emotions": emotions
|
| 35 |
+
}
|
services/similarity.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from sentence_transformers import SentenceTransformer, util
|
| 2 |
+
|
| 3 |
+
# Pretrained embedding model (NO training needed)
|
| 4 |
+
model = SentenceTransformer("all-MiniLM-L6-v2")
|
| 5 |
+
|
| 6 |
+
def compute_similarity(base_ad: str, competitor_ads: list):
|
| 7 |
+
"""
|
| 8 |
+
Compare one ad caption with multiple competitor ads
|
| 9 |
+
Returns similarity scores
|
| 10 |
+
"""
|
| 11 |
+
|
| 12 |
+
if not base_ad or not competitor_ads:
|
| 13 |
+
return {"error": "Base ad or competitor ads missing"}
|
| 14 |
+
|
| 15 |
+
# Encode base ad
|
| 16 |
+
base_embedding = model.encode(base_ad, convert_to_tensor=True)
|
| 17 |
+
|
| 18 |
+
results = []
|
| 19 |
+
|
| 20 |
+
for ad in competitor_ads:
|
| 21 |
+
competitor_embedding = model.encode(ad, convert_to_tensor=True)
|
| 22 |
+
|
| 23 |
+
score = util.cos_sim(base_embedding, competitor_embedding).item()
|
| 24 |
+
|
| 25 |
+
results.append({
|
| 26 |
+
"competitor_ad": ad,
|
| 27 |
+
"similarity_score": round(score * 100, 2) # percentage
|
| 28 |
+
})
|
| 29 |
+
|
| 30 |
+
# Sort highest similarity first
|
| 31 |
+
results = sorted(results, key=lambda x: x["similarity_score"], reverse=True)
|
| 32 |
+
|
| 33 |
+
return {
|
| 34 |
+
"base_ad": base_ad,
|
| 35 |
+
"comparisons": results
|
| 36 |
+
}
|
services/zero_shot.py
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from transformers import pipeline
|
| 2 |
+
|
| 3 |
+
# Load pretrained Zero-Shot Classification model
|
| 4 |
+
# This model is already trained — NO custom data required
|
| 5 |
+
classifier = pipeline(
|
| 6 |
+
task="zero-shot-classification",
|
| 7 |
+
model="facebook/bart-large-mnli"
|
| 8 |
+
)
|
| 9 |
+
|
| 10 |
+
# Marketing / Ads-specific labels
|
| 11 |
+
DEFAULT_LABELS = [
|
| 12 |
+
"Brand Awareness",
|
| 13 |
+
"Lead Generation",
|
| 14 |
+
"Sales Promotion",
|
| 15 |
+
"Product Launch",
|
| 16 |
+
"Discount Offer",
|
| 17 |
+
"Emotional Appeal",
|
| 18 |
+
"Urgency Driven",
|
| 19 |
+
"Trust Building",
|
| 20 |
+
"Social Proof",
|
| 21 |
+
"Call To Action Focused"
|
| 22 |
+
]
|
| 23 |
+
|
| 24 |
+
def analyze_ad_intent(ad_text: str, labels: list = DEFAULT_LABELS):
|
| 25 |
+
"""
|
| 26 |
+
Analyze ad caption text using Zero-Shot Learning
|
| 27 |
+
Returns intent labels with confidence scores
|
| 28 |
+
"""
|
| 29 |
+
|
| 30 |
+
if not ad_text or len(ad_text.strip()) == 0:
|
| 31 |
+
return {"error": "Empty ad text"}
|
| 32 |
+
|
| 33 |
+
result = classifier(
|
| 34 |
+
sequences=ad_text,
|
| 35 |
+
candidate_labels=labels,
|
| 36 |
+
multi_label=True
|
| 37 |
+
)
|
| 38 |
+
|
| 39 |
+
response = []
|
| 40 |
+
for label, score in zip(result["labels"], result["scores"]):
|
| 41 |
+
response.append({
|
| 42 |
+
"label": label,
|
| 43 |
+
"confidence": round(score, 3)
|
| 44 |
+
})
|
| 45 |
+
|
| 46 |
+
return {
|
| 47 |
+
"ad_text": ad_text,
|
| 48 |
+
"analysis": response
|
| 49 |
+
}
|
utils/scoring.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
def final_score(
|
| 2 |
+
intent_score: float = 0,
|
| 3 |
+
emotion_score: float = 0,
|
| 4 |
+
cta_score: float = 0,
|
| 5 |
+
text_quality_score: float = 0,
|
| 6 |
+
similarity_score: float = None
|
| 7 |
+
) -> int:
|
| 8 |
+
"""
|
| 9 |
+
Compute final Ad Performance Score (0-100)
|
| 10 |
+
Scores are expected between 0 and 1 (or 0-100 if already scaled)
|
| 11 |
+
similarity_score is optional (0-100)
|
| 12 |
+
"""
|
| 13 |
+
|
| 14 |
+
# Scale 0-1 inputs to 0-100
|
| 15 |
+
intent_score = intent_score * 100 if intent_score <= 1 else intent_score
|
| 16 |
+
emotion_score = emotion_score * 100 if emotion_score <= 1 else emotion_score
|
| 17 |
+
cta_score = cta_score * 100 if cta_score <= 1 else cta_score
|
| 18 |
+
text_quality_score = text_quality_score * 100 if text_quality_score <= 1 else text_quality_score
|
| 19 |
+
|
| 20 |
+
# Weighted scoring (adjustable)
|
| 21 |
+
weights = {
|
| 22 |
+
"intent": 0.3,
|
| 23 |
+
"emotion": 0.25,
|
| 24 |
+
"cta": 0.2,
|
| 25 |
+
"text_quality": 0.15,
|
| 26 |
+
"similarity": 0.1
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
total_score = (
|
| 30 |
+
intent_score * weights["intent"] +
|
| 31 |
+
emotion_score * weights["emotion"] +
|
| 32 |
+
cta_score * weights["cta"] +
|
| 33 |
+
text_quality_score * weights["text_quality"]
|
| 34 |
+
)
|
| 35 |
+
|
| 36 |
+
# Include similarity if available
|
| 37 |
+
if similarity_score is not None:
|
| 38 |
+
total_score += similarity_score * weights["similarity"]
|
| 39 |
+
|
| 40 |
+
# Clamp between 0-100
|
| 41 |
+
total_score = max(min(total_score, 100), 0)
|
| 42 |
+
|
| 43 |
+
return round(total_score)
|
utils/trend_analysis.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from collections import Counter
|
| 2 |
+
import re
|
| 3 |
+
|
| 4 |
+
def extract_keywords(texts):
|
| 5 |
+
words = []
|
| 6 |
+
for t in texts:
|
| 7 |
+
clean = re.sub(r"[^a-zA-Z ]", "", t.lower())
|
| 8 |
+
words.extend(clean.split())
|
| 9 |
+
return Counter(words)
|
| 10 |
+
|
| 11 |
+
def market_trends(ads):
|
| 12 |
+
texts = [ad["ad_creative_body"] for ad in ads]
|
| 13 |
+
|
| 14 |
+
keyword_freq = extract_keywords(texts)
|
| 15 |
+
|
| 16 |
+
top_keywords = [
|
| 17 |
+
word for word, count in keyword_freq.items()
|
| 18 |
+
if count > 2 and len(word) > 3
|
| 19 |
+
][:10]
|
| 20 |
+
|
| 21 |
+
return {
|
| 22 |
+
"total_ads": len(ads),
|
| 23 |
+
"top_keywords": top_keywords
|
| 24 |
+
}
|