Spaces:
Sleeping
Sleeping
Commit ·
2c9d10c
1
Parent(s): a55114e
Enhanced image handling in CorrectionModal and RegenerationModal components to utilize fallback URLs for improved reliability. Updated auto insurance strategies for better alignment with ad formats and removed unused visual guidance. Added GLP-1 image creative prompts for more effective ad generation.
Browse files- api/routers/correction.py +3 -1
- data/auto_insurance.py +95 -362
- data/glp1.py +200 -1
- data/home_insurance.py +0 -1
- data/visuals.py +0 -25
- frontend/components/generation/CorrectionModal.tsx +18 -7
- frontend/components/generation/RegenerationModal.tsx +52 -3
- services/generator.py +29 -94
- services/third_flow.py +36 -306
api/routers/correction.py
CHANGED
|
@@ -178,7 +178,9 @@ async def regenerate_image(
|
|
| 178 |
except Exception as e:
|
| 179 |
api_logger.warning("R2 upload failed: %s", e)
|
| 180 |
local_path = None
|
| 181 |
-
|
|
|
|
|
|
|
| 182 |
local_path = os.path.join(settings.output_dir, filename)
|
| 183 |
os.makedirs(os.path.dirname(local_path), exist_ok=True)
|
| 184 |
with open(local_path, "wb") as f:
|
|
|
|
| 178 |
except Exception as e:
|
| 179 |
api_logger.warning("R2 upload failed: %s", e)
|
| 180 |
local_path = None
|
| 181 |
+
# Always save locally when we have bytes so /images/{filename} works as fallback
|
| 182 |
+
# (avoids ERR_HTTP2_SERVER_REFUSED_STREAM when R2 presigned URL fails in browser)
|
| 183 |
+
if image_bytes:
|
| 184 |
local_path = os.path.join(settings.output_dir, filename)
|
| 185 |
os.makedirs(os.path.dirname(local_path), exist_ok=True)
|
| 186 |
with open(local_path, "wb") as f:
|
data/auto_insurance.py
CHANGED
|
@@ -4,359 +4,110 @@ Strategies kept for copy (hooks); visuals come from AD_FORMAT_VISUAL_LIBRARY onl
|
|
| 4 |
"""
|
| 5 |
|
| 6 |
# ============================================================================
|
| 7 |
-
# SECTION 1: STRATEGIES (
|
|
|
|
| 8 |
# ============================================================================
|
| 9 |
|
| 10 |
STRATEGIES = {
|
| 11 |
-
"
|
| 12 |
-
"name": "
|
| 13 |
-
"description": "
|
| 14 |
"hooks": [
|
| 15 |
-
"
|
| 16 |
-
"Still Overpaying For Car Insurance?",
|
| 17 |
-
"Wasting $847/year On Auto Insurance?",
|
| 18 |
-
"Are You Being Overcharged?",
|
| 19 |
-
"Paying Too Much? Most Drivers Are.",
|
| 20 |
-
"Your Insurance Company Is Overcharging You",
|
| 21 |
-
"Stop Getting Ripped Off On Car Insurance",
|
| 22 |
-
"You're Probably Paying Double",
|
| 23 |
-
"Why Are You Still Paying Full Price?",
|
| 24 |
-
"Being Overcharged Without Knowing It?",
|
| 25 |
-
"Throwing Money Away Every Month?",
|
| 26 |
-
"Your Premium Is Too High. Here's Proof.",
|
| 27 |
-
],
|
| 28 |
-
"visual_styles": [],
|
| 29 |
-
},
|
| 30 |
-
"curiosity_gap": {
|
| 31 |
-
"name": "Curiosity Gap",
|
| 32 |
-
"description": "Open loop - THIS / Instead demands click",
|
| 33 |
-
"hooks": [
|
| 34 |
-
"Drivers Are Ditching Their Auto Insurance & Doing This Instead",
|
| 35 |
-
"Thousands of drivers are dropping their insurance after THIS",
|
| 36 |
-
"Drivers Over 50 Are Switching To THIS",
|
| 37 |
-
"What Smart Drivers Know About Car Insurance",
|
| 38 |
-
"The Secret Insurance Companies Don't Want You To Know",
|
| 39 |
-
"Everyone's Switching. Here's Why.",
|
| 40 |
-
"This Is Why Your Neighbors Pay Less",
|
| 41 |
-
"What 2,437 Drivers In Your Area Just Discovered",
|
| 42 |
-
"The Loophole Drivers Are Using",
|
| 43 |
-
"They Found Something Better. Have You?",
|
| 44 |
-
"After Seeing THIS, You'll Never Overpay Again",
|
| 45 |
-
"One Simple Change Saves Thousands",
|
| 46 |
-
],
|
| 47 |
-
"visual_styles": [],
|
| 48 |
-
},
|
| 49 |
-
"specific_price_anchor": {
|
| 50 |
-
"name": "Specific Price Anchor",
|
| 51 |
-
"description": "Oddly specific prices create believability",
|
| 52 |
-
"hooks": [
|
| 53 |
-
"Car Insurance for as low as $29/month",
|
| 54 |
-
"Auto Insurance with coverage as low as $29/month",
|
| 55 |
-
"$67.33/month For Full Coverage",
|
| 56 |
-
"Pay Just $37/month Instead of $150+",
|
| 57 |
-
"Locked In At $42/month",
|
| 58 |
-
"Full Protection For $1.25/day",
|
| 59 |
-
"Coverage From $29/month",
|
| 60 |
-
"$57/month Beats Your Current Rate",
|
| 61 |
-
"Most Drivers Qualify For $39/month",
|
| 62 |
-
"Switch And Pay Just $63/month",
|
| 63 |
-
"Rates Starting At $27.50/month",
|
| 64 |
-
],
|
| 65 |
-
"visual_styles": [],
|
| 66 |
-
},
|
| 67 |
-
"before_after_proof": {
|
| 68 |
-
"name": "Before/After Proof",
|
| 69 |
-
"description": "Savings numbers with visual proof",
|
| 70 |
-
"hooks": [
|
| 71 |
-
"WAS: $1,842 → NOW: $647",
|
| 72 |
-
"How I Dropped My Premium By $1,195/year",
|
| 73 |
-
"Paid $2,200. Now Pay $756.",
|
| 74 |
-
"From $167/month To $57/month",
|
| 75 |
-
"Cut My Bill In Half. Here's How.",
|
| 76 |
-
"Saved $1,147 In 5 Minutes",
|
| 77 |
-
"Before: $2,400/year After: $780/year",
|
| 78 |
-
"My Friend Showed Me How To Save $900",
|
| 79 |
-
"Real Savings: $1,356 Less Per Year",
|
| 80 |
-
"Went From Overpaying To Saving Big",
|
| 81 |
-
"The 5-Minute Switch That Saved Me $1,200",
|
| 82 |
-
],
|
| 83 |
-
"visual_styles": [],
|
| 84 |
-
},
|
| 85 |
-
"quiz_interactive": {
|
| 86 |
-
"name": "Quiz/Interactive",
|
| 87 |
-
"description": "Quiz format drives engagement",
|
| 88 |
-
"hooks": [
|
| 89 |
-
"What Year Is Your Car?",
|
| 90 |
-
"Tap Your Age To Calculate Your New Rate",
|
| 91 |
-
"Answer 3 Questions. See Your Rate.",
|
| 92 |
-
"How Many Miles Do You Drive?",
|
| 93 |
-
"Check Your Eligibility In 60 Seconds",
|
| 94 |
-
"Take The 30-Second Quiz",
|
| 95 |
-
"Select Your Age Bracket",
|
| 96 |
-
"When Did You Last Shop For Insurance?",
|
| 97 |
-
"Find Your New Rate - 2 Questions",
|
| 98 |
-
"See If You Qualify For Lower Rates",
|
| 99 |
-
"Quick Quiz: How Much Can You Save?",
|
| 100 |
-
"Select Your State To See Rates",
|
| 101 |
-
],
|
| 102 |
-
"visual_styles": [],
|
| 103 |
-
},
|
| 104 |
-
"authority_transfer": {
|
| 105 |
-
"name": "Authority Transfer",
|
| 106 |
-
"description": "Transfer trust from government/institutions",
|
| 107 |
-
"hooks": [
|
| 108 |
-
"State Farm & GEICO Competitors Bring Savings!",
|
| 109 |
-
"Sponsored by Major Insurance Providers",
|
| 110 |
-
"Government Program For Safe Drivers",
|
| 111 |
-
"New State Program Cuts Insurance Costs",
|
| 112 |
-
"Federal Assistance For Drivers Over 50",
|
| 113 |
"Official: Safe Drivers Qualify For Reduced Rates",
|
| 114 |
-
"
|
| 115 |
-
"
|
| 116 |
-
"
|
| 117 |
-
"
|
|
|
|
| 118 |
"Official Notice: Rate Reduction Program",
|
| 119 |
-
"
|
| 120 |
-
|
| 121 |
-
"visual_styles": [],
|
| 122 |
-
},
|
| 123 |
-
"identity_targeting": {
|
| 124 |
-
"name": "Identity Targeting",
|
| 125 |
-
"description": "Demographic callout for self-selection",
|
| 126 |
-
"hooks": [
|
| 127 |
-
"Drivers Won't Have To Pay More Than $39 A Month",
|
| 128 |
-
"Drivers Over 50: Check Your Eligibility",
|
| 129 |
-
"Safe drivers over the age of 50...",
|
| 130 |
-
"If You're 50+ And Drive, Read This",
|
| 131 |
-
"Attention: Drivers Born Before 1975",
|
| 132 |
-
"For Drivers 55 And Older",
|
| 133 |
-
"65+ Drivers: New Rate Available",
|
| 134 |
-
"Baby Boomers: Insurance Relief Is Here",
|
| 135 |
-
"Retired Drivers: Special Program",
|
| 136 |
-
"If You Drive And You're Over 50...",
|
| 137 |
-
"Senior Citizen Auto Insurance Rates",
|
| 138 |
-
"Safe Drivers Turning 65 This Year",
|
| 139 |
-
],
|
| 140 |
-
"visual_styles": [],
|
| 141 |
-
},
|
| 142 |
-
"insider_secret": {
|
| 143 |
-
"name": "Insider Secret",
|
| 144 |
-
"description": "Exclusivity and hidden knowledge",
|
| 145 |
-
"hooks": [
|
| 146 |
-
"The Easiest Way To Cut Car Insurance Bills",
|
| 147 |
-
"What Insurance Companies Don't Want You To Know",
|
| 148 |
-
"The Loophole That Saves Thousands",
|
| 149 |
-
"Former Agent Reveals Industry Secret",
|
| 150 |
-
"The Trick Your Insurance Company Hides",
|
| 151 |
-
"Why Insiders Pay 40% Less",
|
| 152 |
-
"The One Thing That Cuts Your Premium In Half",
|
| 153 |
-
"What They Don't Tell You About Auto Insurance",
|
| 154 |
-
"Insurance Industry Insider Speaks Out",
|
| 155 |
-
"The Secret Smart Drivers Use",
|
| 156 |
-
"Hidden Discount Most People Miss",
|
| 157 |
-
"The Backdoor To Lower Rates",
|
| 158 |
],
|
| 159 |
"visual_styles": [],
|
| 160 |
},
|
| 161 |
-
"
|
| 162 |
-
"name": "
|
| 163 |
-
"description": "
|
| 164 |
"hooks": [
|
| 165 |
-
"
|
| 166 |
-
"
|
| 167 |
-
"
|
| 168 |
-
"
|
| 169 |
-
"
|
| 170 |
-
"
|
| 171 |
-
"
|
| 172 |
-
"
|
| 173 |
-
"
|
| 174 |
-
"
|
| 175 |
-
"The average car accident costs $57,000",
|
| 176 |
-
"Are you gambling with your family's future?",
|
| 177 |
-
],
|
| 178 |
-
"visual_styles": [],
|
| 179 |
-
},
|
| 180 |
-
"urgency_scarcity": {
|
| 181 |
-
"name": "Urgency & Scarcity",
|
| 182 |
-
"description": "Time pressure and limited availability",
|
| 183 |
-
"hooks": [
|
| 184 |
-
"Rates increasing in 48 hours",
|
| 185 |
-
"Last chance for 2024 pricing",
|
| 186 |
-
"Only 23 spots left at this rate",
|
| 187 |
-
"Offer expires midnight",
|
| 188 |
-
"Insurers are RAISING rates in your area",
|
| 189 |
-
"Lock in before it's too late",
|
| 190 |
-
"Price hike coming January 1st",
|
| 191 |
-
"Limited-time discount ending soon",
|
| 192 |
-
"Your quote expires in 24 hours",
|
| 193 |
-
"Enrollment window closing",
|
| 194 |
-
"Act now or pay 30% more next month",
|
| 195 |
-
"Final warning: rates going up",
|
| 196 |
-
],
|
| 197 |
-
"visual_styles": [],
|
| 198 |
-
},
|
| 199 |
-
"social_proof_fomo": {
|
| 200 |
-
"name": "Social Proof & FOMO",
|
| 201 |
-
"description": "Others are doing it, fear of missing out",
|
| 202 |
-
"hooks": [
|
| 203 |
-
"2,437 drivers in your area switched THIS WEEK",
|
| 204 |
-
"Your neighbors are saving. Are you?",
|
| 205 |
-
"Join 3.7 million smart drivers",
|
| 206 |
-
"Why is everyone switching?",
|
| 207 |
-
"The #1 choice for drivers in 2024",
|
| 208 |
-
"9 out of 10 drivers recommend this",
|
| 209 |
-
"Everyone in your neighborhood is saving. Except you?",
|
| 210 |
-
"Don't be the last one overpaying",
|
| 211 |
-
"Over 1 million claims paid this year",
|
| 212 |
-
"Rated #1 by Consumer Reports",
|
| 213 |
-
"The insurance your neighbors trust",
|
| 214 |
-
"Smart drivers are making the switch",
|
| 215 |
-
],
|
| 216 |
-
"visual_styles": [],
|
| 217 |
-
},
|
| 218 |
-
"guilt_shame": {
|
| 219 |
-
"name": "Guilt & Shame",
|
| 220 |
-
"description": "Family responsibility trigger",
|
| 221 |
-
"hooks": [
|
| 222 |
-
"Can you look your family in the eye without coverage?",
|
| 223 |
-
"Your kids are counting on you",
|
| 224 |
-
"Don't let them down",
|
| 225 |
-
"Responsible drivers don't gamble with their family's future",
|
| 226 |
-
"What will you tell your kids after an accident?",
|
| 227 |
-
"A real parent protects their family",
|
| 228 |
-
"Your spouse trusts you to keep them safe",
|
| 229 |
-
"Failure to protect is a choice",
|
| 230 |
-
"They're depending on you. Don't fail them.",
|
| 231 |
-
"Would your family forgive you?",
|
| 232 |
-
"Every mile without coverage is a risk to your family",
|
| 233 |
-
"What kind of driver are you?",
|
| 234 |
-
],
|
| 235 |
-
"visual_styles": [],
|
| 236 |
-
},
|
| 237 |
-
"greed_savings": {
|
| 238 |
-
"name": "Greed & Savings",
|
| 239 |
-
"description": "Desire to save money",
|
| 240 |
-
"hooks": [
|
| 241 |
-
"You're overpaying by $1,147/year",
|
| 242 |
-
"Stop throwing money away",
|
| 243 |
-
"Get $500 back instantly",
|
| 244 |
-
"Why pay more for less?",
|
| 245 |
-
"Save up to 45% on your premium",
|
| 246 |
-
"Free quote reveals your savings",
|
| 247 |
-
"Most drivers can save $900+",
|
| 248 |
-
"You're leaving money on the table",
|
| 249 |
-
"Switch and save in 5 minutes",
|
| 250 |
-
"Same coverage. Half the price.",
|
| 251 |
-
"Get more coverage for less money",
|
| 252 |
-
"Stop wasting money on overpriced insurance",
|
| 253 |
-
],
|
| 254 |
-
"visual_styles": [],
|
| 255 |
-
},
|
| 256 |
-
"authority_trust": {
|
| 257 |
-
"name": "Authority & Trust",
|
| 258 |
-
"description": "Expert credibility and trust",
|
| 259 |
-
"hooks": [
|
| 260 |
-
"Former agent reveals the truth",
|
| 261 |
-
"Industry insider secret exposed",
|
| 262 |
-
"A+ rated, 75 years trusted",
|
| 263 |
-
"Backed by major insurers",
|
| 264 |
-
"Exposed: The coverage gap trap",
|
| 265 |
-
"Insurance agent confessions",
|
| 266 |
-
"The dirty secret of cheap policies",
|
| 267 |
-
"What your agent isn't telling you",
|
| 268 |
-
"BBB accredited with zero complaints",
|
| 269 |
-
"Trusted by millions of drivers",
|
| 270 |
-
"Licensed in all 50 states",
|
| 271 |
-
],
|
| 272 |
-
"visual_styles": [],
|
| 273 |
-
},
|
| 274 |
-
"loss_aversion": {
|
| 275 |
-
"name": "Loss Aversion",
|
| 276 |
-
"description": "Emphasize what they stand to lose",
|
| 277 |
-
"hooks": [
|
| 278 |
-
"Everything you've worked for - GONE after one crash",
|
| 279 |
-
"Average accident costs $57,000 in damages",
|
| 280 |
-
"You can't get back what's already lost",
|
| 281 |
-
"Your savings. Your car. Your future. Gone.",
|
| 282 |
-
"One lawsuit could take everything",
|
| 283 |
-
"Imagine losing it all tomorrow",
|
| 284 |
-
"The average accident destroys years of savings",
|
| 285 |
-
"Your life's work, wiped out in seconds",
|
| 286 |
-
"What would you do if sued for $250,000?",
|
| 287 |
-
"Some things can never be replaced",
|
| 288 |
-
"Everything you own could be at risk",
|
| 289 |
-
"Your assets, your peace of mind, your security",
|
| 290 |
],
|
| 291 |
"visual_styles": [],
|
| 292 |
},
|
| 293 |
-
"
|
| 294 |
-
"name": "
|
| 295 |
-
"description": "
|
| 296 |
"hooks": [
|
| 297 |
-
"
|
| 298 |
-
"
|
| 299 |
-
"
|
| 300 |
-
"
|
| 301 |
-
"
|
| 302 |
-
"
|
| 303 |
-
"
|
| 304 |
-
"
|
| 305 |
-
"
|
| 306 |
-
"
|
| 307 |
],
|
| 308 |
"visual_styles": [],
|
| 309 |
},
|
| 310 |
-
"
|
| 311 |
-
"name": "
|
| 312 |
-
"description": "
|
| 313 |
"hooks": [
|
| 314 |
-
"
|
| 315 |
-
"
|
| 316 |
-
"
|
| 317 |
-
"
|
| 318 |
-
"
|
| 319 |
-
"
|
| 320 |
-
"
|
| 321 |
-
"
|
| 322 |
-
"
|
| 323 |
-
"
|
| 324 |
-
"The lazy driver's insurance solution",
|
| 325 |
-
"Why is getting insurance still this hard? (It isn't anymore)",
|
| 326 |
],
|
| 327 |
"visual_styles": [],
|
| 328 |
},
|
| 329 |
-
"
|
| 330 |
-
"name": "
|
| 331 |
-
"description": "
|
| 332 |
"hooks": [
|
| 333 |
-
"
|
| 334 |
-
"
|
| 335 |
-
"
|
| 336 |
-
"
|
| 337 |
-
"
|
| 338 |
-
"
|
| 339 |
-
"
|
| 340 |
-
"
|
| 341 |
-
"
|
| 342 |
-
"
|
| 343 |
],
|
| 344 |
"visual_styles": [],
|
| 345 |
},
|
| 346 |
-
"
|
| 347 |
-
"name": "
|
| 348 |
-
"description": "
|
| 349 |
"hooks": [
|
| 350 |
-
"
|
| 351 |
-
"
|
| 352 |
-
"
|
| 353 |
-
"
|
| 354 |
-
"
|
| 355 |
-
"
|
| 356 |
-
"
|
| 357 |
-
"
|
| 358 |
-
"
|
| 359 |
-
"
|
| 360 |
],
|
| 361 |
"visual_styles": [],
|
| 362 |
},
|
|
@@ -365,7 +116,7 @@ STRATEGIES = {
|
|
| 365 |
# ============================================================================
|
| 366 |
# SECTION 2: AD FORMAT VISUALS (reference-style ad graphics)
|
| 367 |
# High-converting auto insurance ad layouts: official notification, social post,
|
| 368 |
-
#
|
| 369 |
# ============================================================================
|
| 370 |
|
| 371 |
OFFICIAL_NOTIFICATION_FORMAT = [
|
|
@@ -380,18 +131,6 @@ SOCIAL_POST_EMBED_FORMAT = [
|
|
| 380 |
"Twitter-style ad: profile circle, @handle, timestamp, tweet copy, large embedded card with seal/logo, program name, body text with key phrases bolded, CTA or link, likes and retweets count at bottom",
|
| 381 |
]
|
| 382 |
|
| 383 |
-
SENIORS_TABLE_FORMAT = [
|
| 384 |
-
"seniors cost table: title 'CAR INSURANCE FOR SENIORS COST IN 2024' in blue uppercase, two-column table with 'Age' column (55-69 and 70+ in bold purple on light purple), right column listing coverage types (Liability, Comprehensive, Collision, Usage-Based, Roadside) with yellow 'See Prices' button per row, light purple/lavender table background, clean grid, professional infographic",
|
| 385 |
-
"age-group rate table: bold title, left column age brackets (55-69, 70+), right column coverage options each with rounded yellow See Prices button, purple and blue color scheme, horizontal dividers, modern table layout for auto insurance",
|
| 386 |
-
"senior drivers rate card: headline about senior car insurance cost, table rows for age groups and coverage types, prominent See Prices buttons, lavender background, sans-serif, structured like a comparison or rate finder",
|
| 387 |
-
]
|
| 388 |
-
|
| 389 |
-
BEFORE_AFTER_SPLIT_FORMAT = [
|
| 390 |
-
"before-after savings ad: brand name at top (e.g. AmericanCar Quote), 'Auto Insurance' headline, two semi-transparent boxes side by side labeled Before $186/mo and After $39/mo with same coverage 50/100/50, split car graphic below (one half plain silver, other half dynamic blue/black geometric design), blurred cityscape background, 'Calculate Payment' button at bottom with arrow, terms line",
|
| 391 |
-
"transformation ad: before/after monthly price comparison boxes, sports car image split vertically (before side plain, after side styled), urban skyline blur, single CTA button, clean modern design, blue accent color",
|
| 392 |
-
"savings comparison graphic: Before and After price boxes with crossed-out high price and new low price, car as visual metaphor (half plain half premium look), Calculate Payment or Get Quote CTA, professional ad layout",
|
| 393 |
-
]
|
| 394 |
-
|
| 395 |
COVERAGE_TIERS_PANELS_FORMAT = [
|
| 396 |
"three coverage panels: logo and slogan at top (e.g. Wal-Mart of Auto Insurance), 'GET YOUR QUOTES IN 60 SEC' with flags or accent, three horizontal white rounded panels each with wave/teal bottom shape: left 'Liability Only' From $X/mo, center 'Liability+Collision' From $X/mo, right 'Full Coverage' From $X/mo, below 'Tap Your Age Group' with age buttons 20s 30s 40s 50s 60s 70s+, clean white background, dashed border frame",
|
| 397 |
"coverage tier cards: brand headline, three equal panels with coverage type and 'From $X/mo' in bold, light blue or teal wave design at bottom of each card, age selector row of small bordered buttons, minimal professional layout",
|
|
@@ -420,8 +159,6 @@ SAVINGS_URGENCY_FORMAT = [
|
|
| 420 |
AD_FORMAT_VISUAL_LIBRARY = {
|
| 421 |
"official_notification_style": OFFICIAL_NOTIFICATION_FORMAT,
|
| 422 |
"social_post_style": SOCIAL_POST_EMBED_FORMAT,
|
| 423 |
-
"seniors_table_style": SENIORS_TABLE_FORMAT,
|
| 424 |
-
"before_after_style": BEFORE_AFTER_SPLIT_FORMAT,
|
| 425 |
"coverage_tiers_style": COVERAGE_TIERS_PANELS_FORMAT,
|
| 426 |
"car_brand_grid_style": CAR_BRAND_GRID_FORMAT,
|
| 427 |
"gift_card_cta_style": GIFT_CARD_CTA_FORMAT,
|
|
@@ -444,15 +181,14 @@ CREATIVE_DIRECTIONS = [
|
|
| 444 |
"urgent", # Time-sensitive urgency
|
| 445 |
]
|
| 446 |
|
|
|
|
| 447 |
VISUAL_MOODS = [
|
| 448 |
-
"
|
| 449 |
-
"
|
| 450 |
-
"
|
| 451 |
-
"
|
| 452 |
-
"
|
| 453 |
-
"
|
| 454 |
-
"raw-unpolished", # Raw, unpolished UGC feel
|
| 455 |
-
"news-expose", # News/editorial style
|
| 456 |
]
|
| 457 |
|
| 458 |
COPY_TEMPLATES = [
|
|
@@ -506,10 +242,10 @@ def get_niche_data():
|
|
| 506 |
"copy_templates": COPY_TEMPLATES,
|
| 507 |
"niche_guidance": """
|
| 508 |
NICHE-SPECIFIC REQUIREMENTS (AUTO INSURANCE):
|
| 509 |
-
- Use
|
|
|
|
| 510 |
- Copy supports headlines and CTAs that match these formats (rates, eligibility, phone number, age/car selectors).
|
| 511 |
- Messaging: urgent but trustworthy; clear value (savings, low monthly rate, no credit check, fast quote).
|
| 512 |
-
- AVOID: long-form storytelling; generic lifestyle shots; extreme fear or crash imagery.
|
| 513 |
""",
|
| 514 |
"price_config": {
|
| 515 |
"guidance": "Consider using oddly specific prices (e.g., $29.00 or $67.33 instead of $30 or $70) if the ad format calls for it. Typical range: $29-$150/month. Only include if it enhances believability and fits the ad format.",
|
|
@@ -525,18 +261,15 @@ NICHE-SPECIFIC REQUIREMENTS (AUTO INSURANCE):
|
|
| 525 |
"labels": {"before": "$/year", "after": "$/year", "difference": "$", "metric": "savings per year"},
|
| 526 |
},
|
| 527 |
"image_guidance": """
|
| 528 |
-
NICHE REQUIREMENTS (AUTO INSURANCE):
|
| 529 |
-
- Use ONLY these ad formats: official notification, social post card,
|
| 530 |
-
-
|
| 531 |
-
-
|
| 532 |
-
-
|
| 533 |
-
-
|
| 534 |
""",
|
| 535 |
"image_niche_guidance_short": """
|
| 536 |
-
NICHE: Auto Insurance
|
| 537 |
-
- Rate cards, comparison layouts, CTAs, coverage tiers, car brand grid
|
| 538 |
-
- Headline and price/rate text as part of design
|
| 539 |
-
- People, faces, and cars only when the format specifies them""",
|
| 540 |
"prompt_sanitization_replacements": [
|
| 541 |
(r"\belderly\b", "person"),
|
| 542 |
(r"\bsenior(s)?\b", "driver"),
|
|
|
|
| 4 |
"""
|
| 5 |
|
| 6 |
# ============================================================================
|
| 7 |
+
# SECTION 1: STRATEGIES (aligned with the 6 ad format visuals)
|
| 8 |
+
# Each strategy maps 1:1 to an ad format; hooks and copy fit that format.
|
| 9 |
# ============================================================================
|
| 10 |
|
| 11 |
STRATEGIES = {
|
| 12 |
+
"official_notification": {
|
| 13 |
+
"name": "Official Notification",
|
| 14 |
+
"description": "Government/official-style ad: seal, eligibility, no credit check, rate tier buttons (Liability, Liability+Collision, Full Coverage).",
|
| 15 |
"hooks": [
|
| 16 |
+
"You've Been Approved For Lower Rates",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
"Official: Safe Drivers Qualify For Reduced Rates",
|
| 18 |
+
"No Credit Check Required - Choose Your Rate Below",
|
| 19 |
+
"State-Approved Savings Program For Drivers",
|
| 20 |
+
"Government Program Cuts Insurance Costs",
|
| 21 |
+
"DMV-Verified Rate Reduction Available",
|
| 22 |
+
"Eligibility Confirmed - Select Your Coverage",
|
| 23 |
"Official Notice: Rate Reduction Program",
|
| 24 |
+
"Approved For Liability From $29/mo",
|
| 25 |
+
"Choose Your Rate: Liability, Liability+Collision, or Full Coverage",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
],
|
| 27 |
"visual_styles": [],
|
| 28 |
},
|
| 29 |
+
"social_post": {
|
| 30 |
+
"name": "Social Post",
|
| 31 |
+
"description": "Social/viral post format: tweet or feed post with embedded card, engagement bar, subsidized/low rate headline.",
|
| 32 |
"hooks": [
|
| 33 |
+
"2,437 Drivers In Your Area Just Discovered This",
|
| 34 |
+
"Your Neighbors Are Saving. Are You?",
|
| 35 |
+
"Why Is Everyone Switching?",
|
| 36 |
+
"The #1 Choice For Drivers In 2024",
|
| 37 |
+
"Everyone's Switching. Here's Why.",
|
| 38 |
+
"Subsidized Auto Insurance As Low As $19/mo",
|
| 39 |
+
"DUI-Free Drivers Qualify - See Your Rate",
|
| 40 |
+
"What Smart Drivers Know About Car Insurance",
|
| 41 |
+
"Join 3.7 Million Smart Drivers",
|
| 42 |
+
"Rated #1 - See What The Buzz Is About",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
],
|
| 44 |
"visual_styles": [],
|
| 45 |
},
|
| 46 |
+
"coverage_tiers": {
|
| 47 |
+
"name": "Coverage Tiers",
|
| 48 |
+
"description": "Three-panel layout: Liability Only, Liability+Collision, Full Coverage with From $X/mo; Tap Your Age Group buttons.",
|
| 49 |
"hooks": [
|
| 50 |
+
"Liability Only From $29/mo - Full Coverage From $57/mo",
|
| 51 |
+
"Get Your Quotes In 60 Seconds",
|
| 52 |
+
"Tap Your Age Group: 20s 30s 40s 50s 60s 70s+",
|
| 53 |
+
"Three Options. One Minute.",
|
| 54 |
+
"Liability, Liability+Collision, or Full Coverage",
|
| 55 |
+
"From $27/mo - Select Your Coverage Tier",
|
| 56 |
+
"Choose Liability Only, Combo, or Full Protection",
|
| 57 |
+
"See Your Rate By Age - No Commitment",
|
| 58 |
+
"Compare All Three Tiers Side By Side",
|
| 59 |
+
"Full Coverage From $42/mo - Tap Your Age",
|
| 60 |
],
|
| 61 |
"visual_styles": [],
|
| 62 |
},
|
| 63 |
+
"car_brand_grid": {
|
| 64 |
+
"name": "Car Brand Grid",
|
| 65 |
+
"description": "Car brand selector: liability price headline, TAP THE BRAND OF YOUR CAR, grid of manufacturer logos (Porsche, Toyota, etc.), OTHER option.",
|
| 66 |
"hooks": [
|
| 67 |
+
"Liability From $18/Month - Tap The Brand Of Your Car",
|
| 68 |
+
"What Year Is Your Car? See Your Rate.",
|
| 69 |
+
"Select Your Car Make - Get Your Quote",
|
| 70 |
+
"Tap Your Brand: Toyota, Honda, Ford, Tesla...",
|
| 71 |
+
"Liability Coverage $X/mo - Choose Your Make",
|
| 72 |
+
"20+ Brands - Find Your Rate In Seconds",
|
| 73 |
+
"Tap The Brand Of Your Car To See Prices",
|
| 74 |
+
"From $29/mo - Select Your Vehicle Make",
|
| 75 |
+
"Car Brand Selector - Instant Quote",
|
| 76 |
+
"Tap Your Car. Get Your Rate.",
|
|
|
|
|
|
|
| 77 |
],
|
| 78 |
"visual_styles": [],
|
| 79 |
},
|
| 80 |
+
"gift_card_cta": {
|
| 81 |
+
"name": "Gift Card CTA",
|
| 82 |
+
"description": "Quote + gift card offer: Get A Quote Get A Gift Card, First 25 Callers Only, call today, phone number, local agency.",
|
| 83 |
"hooks": [
|
| 84 |
+
"Get A Quote, Get A Gift Card!",
|
| 85 |
+
"First 25 Callers Only - No Purchase Necessary",
|
| 86 |
+
"Call Today - Get Your Gift Card",
|
| 87 |
+
"Quote + Gift Card - Super Fast & Friendly",
|
| 88 |
+
"Potential To Save Hundreds - Call Now",
|
| 89 |
+
"Local Agency - Get Quote, Get Reward",
|
| 90 |
+
"Free Quote + Gift Card Offer",
|
| 91 |
+
"Call Today For Your Quote And Gift Card",
|
| 92 |
+
"First 25 Callers Get Gift Card",
|
| 93 |
+
"Get A Quote. Get A Gift Card. It's That Simple.",
|
| 94 |
],
|
| 95 |
"visual_styles": [],
|
| 96 |
},
|
| 97 |
+
"savings_urgency": {
|
| 98 |
+
"name": "Savings & Urgency",
|
| 99 |
+
"description": "Yellow/urgent layout: overpaying stat, switch and save, price, CONTACT US button, STOP OVERPAYING graphic.",
|
| 100 |
"hooks": [
|
| 101 |
+
"73% Of Drivers Are Paying Too Much - Are You?",
|
| 102 |
+
"Stop Overpaying - Switch And Save Today",
|
| 103 |
+
"Full Coverage From $59/mo - Contact Us",
|
| 104 |
+
"Insurers Are Raising Rates - Lock In Now",
|
| 105 |
+
"Last Chance For 2024 Pricing",
|
| 106 |
+
"Rates Increasing In 48 Hours - Act Now",
|
| 107 |
+
"You're Overpaying. Here's Proof.",
|
| 108 |
+
"Switch And Start Saving On Auto Insurance",
|
| 109 |
+
"Stop Overpaying - Get Your Quote",
|
| 110 |
+
"Contact Us - See How Much You Can Save",
|
| 111 |
],
|
| 112 |
"visual_styles": [],
|
| 113 |
},
|
|
|
|
| 116 |
# ============================================================================
|
| 117 |
# SECTION 2: AD FORMAT VISUALS (reference-style ad graphics)
|
| 118 |
# High-converting auto insurance ad layouts: official notification, social post,
|
| 119 |
+
# coverage tiers, car brand grid, gift card CTA, savings/urgency.
|
| 120 |
# ============================================================================
|
| 121 |
|
| 122 |
OFFICIAL_NOTIFICATION_FORMAT = [
|
|
|
|
| 131 |
"Twitter-style ad: profile circle, @handle, timestamp, tweet copy, large embedded card with seal/logo, program name, body text with key phrases bolded, CTA or link, likes and retweets count at bottom",
|
| 132 |
]
|
| 133 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 134 |
COVERAGE_TIERS_PANELS_FORMAT = [
|
| 135 |
"three coverage panels: logo and slogan at top (e.g. Wal-Mart of Auto Insurance), 'GET YOUR QUOTES IN 60 SEC' with flags or accent, three horizontal white rounded panels each with wave/teal bottom shape: left 'Liability Only' From $X/mo, center 'Liability+Collision' From $X/mo, right 'Full Coverage' From $X/mo, below 'Tap Your Age Group' with age buttons 20s 30s 40s 50s 60s 70s+, clean white background, dashed border frame",
|
| 136 |
"coverage tier cards: brand headline, three equal panels with coverage type and 'From $X/mo' in bold, light blue or teal wave design at bottom of each card, age selector row of small bordered buttons, minimal professional layout",
|
|
|
|
| 159 |
AD_FORMAT_VISUAL_LIBRARY = {
|
| 160 |
"official_notification_style": OFFICIAL_NOTIFICATION_FORMAT,
|
| 161 |
"social_post_style": SOCIAL_POST_EMBED_FORMAT,
|
|
|
|
|
|
|
| 162 |
"coverage_tiers_style": COVERAGE_TIERS_PANELS_FORMAT,
|
| 163 |
"car_brand_grid_style": CAR_BRAND_GRID_FORMAT,
|
| 164 |
"gift_card_cta_style": GIFT_CARD_CTA_FORMAT,
|
|
|
|
| 181 |
"urgent", # Time-sensitive urgency
|
| 182 |
]
|
| 183 |
|
| 184 |
+
# For auto insurance we use only ad-format graphics; moods are constrained to layout/design (no documentary/UGC).
|
| 185 |
VISUAL_MOODS = [
|
| 186 |
+
"official-institutional", # Official/notification style
|
| 187 |
+
"clean-modern", # Clean ad graphic layout
|
| 188 |
+
"high-contrast", # Bold typography, clear CTA
|
| 189 |
+
"professional-layout", # Rate cards, panels, grids
|
| 190 |
+
"direct-response", # Gift card / savings urgency
|
| 191 |
+
"ui-screenshot", # Social post / embedded card
|
|
|
|
|
|
|
| 192 |
]
|
| 193 |
|
| 194 |
COPY_TEMPLATES = [
|
|
|
|
| 242 |
"copy_templates": COPY_TEMPLATES,
|
| 243 |
"niche_guidance": """
|
| 244 |
NICHE-SPECIFIC REQUIREMENTS (AUTO INSURANCE):
|
| 245 |
+
- Use ONLY these 6 creative types: (1) official notification, (2) social post embed, (3) coverage tiers, (4) car brand grid, (5) gift card CTA, (6) savings/urgency. No other creative types.
|
| 246 |
+
- Do NOT use: documentary, lifestyle, UGC, testimonial, before/after, rate tables, or any style outside the 6 above.
|
| 247 |
- Copy supports headlines and CTAs that match these formats (rates, eligibility, phone number, age/car selectors).
|
| 248 |
- Messaging: urgent but trustworthy; clear value (savings, low monthly rate, no credit check, fast quote).
|
|
|
|
| 249 |
""",
|
| 250 |
"price_config": {
|
| 251 |
"guidance": "Consider using oddly specific prices (e.g., $29.00 or $67.33 instead of $30 or $70) if the ad format calls for it. Typical range: $29-$150/month. Only include if it enhances believability and fits the ad format.",
|
|
|
|
| 261 |
"labels": {"before": "$/year", "after": "$/year", "difference": "$", "metric": "savings per year"},
|
| 262 |
},
|
| 263 |
"image_guidance": """
|
| 264 |
+
NICHE REQUIREMENTS (AUTO INSURANCE) - THESE 6 CREATIVE TYPES ONLY:
|
| 265 |
+
- Use ONLY these 6 ad formats: (1) official notification, (2) social post card, (3) coverage tier panels, (4) car brand grid, (5) gift card CTA, (6) savings/urgency. No other creative types or layouts.
|
| 266 |
+
- Do NOT use: documentary, lifestyle, UGC, testimonial, before/after, rate/seniors tables, in-car dashboard, screen-in-car mockups, or any format outside the 6 above.
|
| 267 |
+
- Include headline, prices/rates, and CTA or buttons as specified in the format. Clean, modern layout; clear typography; high contrast.
|
| 268 |
+
- People, faces, and cars are OPTIONAL: only when the format explicitly calls for them (e.g. profile in social post, car logos in grid). Most formats are layout and text only.
|
| 269 |
+
- NO fake or made-up brand/company names; use generic labels only (e.g. Compare Providers, See Rates) or omit.
|
| 270 |
""",
|
| 271 |
"image_niche_guidance_short": """
|
| 272 |
+
NICHE: Auto Insurance - ONLY these 6 creative types: official notification, social post, coverage tiers, car brand grid, gift card CTA, savings/urgency. No other types. Headline and price/rate text as part of design. People, faces, and cars only when the format specifies them.""",
|
|
|
|
|
|
|
|
|
|
| 273 |
"prompt_sanitization_replacements": [
|
| 274 |
(r"\belderly\b", "person"),
|
| 275 |
(r"\bsenior(s)?\b", "driver"),
|
data/glp1.py
CHANGED
|
@@ -1,6 +1,5 @@
|
|
| 1 |
"""
|
| 2 |
GLP-1 (Weight Loss) - Complete Psychological Arsenal
|
| 3 |
-
All strategies: shame, transformation, FOMO, authority, simplicity, etc.
|
| 4 |
Updated with winning ad patterns from high-converting creative analysis.
|
| 5 |
"""
|
| 6 |
|
|
@@ -638,6 +637,205 @@ COPY_TEMPLATES = [
|
|
| 638 |
},
|
| 639 |
]
|
| 640 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 641 |
# ============================================================================
|
| 642 |
# SECTION 3: VISUAL LIBRARY - Category Entry Points & Psychological Moments
|
| 643 |
# Based on DirectMeds Marketing Brief: 7 Ws, CEPs, Life-Force 8, Stage of Awareness
|
|
@@ -1038,5 +1236,6 @@ NICHE: GLP-1 / Weight Loss
|
|
| 1038 |
"product_aware": PRODUCT_AWARE_VISUALS,
|
| 1039 |
"most_aware": MOST_AWARE_VISUALS,
|
| 1040 |
},
|
|
|
|
| 1041 |
}
|
| 1042 |
|
|
|
|
| 1 |
"""
|
| 2 |
GLP-1 (Weight Loss) - Complete Psychological Arsenal
|
|
|
|
| 3 |
Updated with winning ad patterns from high-converting creative analysis.
|
| 4 |
"""
|
| 5 |
|
|
|
|
| 637 |
},
|
| 638 |
]
|
| 639 |
|
| 640 |
+
# ============================================================================
|
| 641 |
+
# GLP-1 IMAGE CREATIVE PROMPTS (from Creative ad images spreadsheet)
|
| 642 |
+
# Full image-prompt descriptions for high-converting GLP-1 ad creatives.
|
| 643 |
+
# Use these to drive VISUAL SCENE in image generation for authentic affiliate styles.
|
| 644 |
+
# ============================================================================
|
| 645 |
+
|
| 646 |
+
GLP_IMAGE_CREATIVE_PROMPTS = [
|
| 647 |
+
{
|
| 648 |
+
"image_prompt": "A clean, premium medical affiliate ad with a soft white-to-gray gradient background, two sleek injectable pens crossing in a minimal product-render style, modern black typography stating weight loss with one weekly shot, and small disclaimer text; polished, clinical, high-trust aesthetic with no people, no hype, and a compliant pharmaceutical look.",
|
| 649 |
+
"angle": "Scientific credibility + simplicity, emphasizing measurable results and once-weekly convenience.",
|
| 650 |
+
"concept": "Clinically Proven Long-Term Weight Loss",
|
| 651 |
+
},
|
| 652 |
+
{
|
| 653 |
+
"image_prompt": "A bold, high-contrast affiliate ad showing syringes injecting a red apple against a warm peach background, large aggressive typography highlighting Semaglutide and weight loss, prominent claims like fast results and FDA approved, feature callouts, and a strong direct CTA; sensational, high-CTR, aggressive affiliate style with dramatic visuals and urgent messaging.",
|
| 654 |
+
"angle": "Speed + certainty + authority, emphasizing quick results, appetite suppression, and FDA approval.",
|
| 655 |
+
"concept": "Rapid Medical Weight Loss Transformation",
|
| 656 |
+
},
|
| 657 |
+
{
|
| 658 |
+
"image_prompt": "A clean, minimal affiliate ad styled like a smartphone notes app, with a white background and modern sans-serif text reading weight loss isn't one size fits all, softer supporting copy about prescription options and a clear CTA to take a short quiz; neutral, trustworthy, compliant medical tone with no people, no transformations, and an informational, conversational feel.",
|
| 659 |
+
"angle": "Personalization + eligibility, emphasizing that prescription weight loss may be appropriate for some and positioning the quiz as a low-friction way to find out.",
|
| 660 |
+
"concept": "Personalized Medical Weight Loss Eligibility",
|
| 661 |
+
},
|
| 662 |
+
{
|
| 663 |
+
"image_prompt": "A clean, minimalist medical affiliate ad with a solid blue clinical background, bold white centered text about starting the year with Semaglutide weight loss treatment, a small celebratory star accent near the headline, and subtle healthcare branding at the bottom; professional, trustworthy, compliant tone with no people, no before/after imagery, and no sensational elements.",
|
| 664 |
+
"angle": "Medical authority + fresh start, emphasizing Semaglutide as a doctor-led, credible treatment without hype.",
|
| 665 |
+
"concept": "New Year Medical Weight Loss Reset",
|
| 666 |
+
},
|
| 667 |
+
{
|
| 668 |
+
"image_prompt": "A high-visibility affiliate ad with a celebratory New Year theme, gold confetti accents, bold promotional typography, and prominent pricing badges, featuring a Semaglutide vial and injection pen, benefit bullet points, and a strong call-to-action with phone number; sales-driven, urgent, offer-centric design optimized for native or pay-per-call traffic.",
|
| 669 |
+
"angle": "Price incentive + urgency + authority, highlighting a low entry price, limited-time deal, and included medical guidance.",
|
| 670 |
+
"concept": "New Year Promotional Medical Weight Loss Offer",
|
| 671 |
+
},
|
| 672 |
+
{
|
| 673 |
+
"image_prompt": "A UGC-style affiliate ad designed to look like an iPhone Notes app screenshot, with a white background and bold conversational text starting with I never thought I would qualify, casual emojis for relatability, references to a short eligibility quiz, and soft performance claims supported by fine-print disclaimers; authentic, social-proof-driven, compliant, and optimized for social and native traffic.",
|
| 674 |
+
"angle": "Personal discovery + eligibility surprise, using a first-person realization and a quick quiz to reduce skepticism.",
|
| 675 |
+
"concept": "Unexpected Eligibility for Medical Weight Loss",
|
| 676 |
+
},
|
| 677 |
+
{
|
| 678 |
+
"image_prompt": "A UGC-style affiliate ad designed to look like an iPhone Notes app checklist, featuring bold text Weight Loss Program for Men, casual emojis for relatability, a simple step-by-step list (quiz, GLP-1 access, guarantee), and a clean product shot of a GLP-1 vial with modern green labeling; approachable, masculine, trustworthy, and optimized for social and native traffic without aggressive claims.",
|
| 679 |
+
"angle": "Gender-specific relevance + ease + assurance, emphasizing that men can quickly check eligibility and feel confident with a money-back guarantee.",
|
| 680 |
+
"concept": "Male-Focused Medical Weight Loss Program",
|
| 681 |
+
},
|
| 682 |
+
{
|
| 683 |
+
"image_prompt": "A bold, dark-background affiliate ad with high-contrast white typography about guys who don't lose weight getting their money back, playful fitness and syringe emojis in a simple equation, prominent brand placement, and five-star verified reviews at the bottom; confident, masculine, reassurance-driven design optimized for social and native traffic with a strong trust signal.",
|
| 684 |
+
"angle": "Reversal of risk + trust + male specificity, emphasizing a money-back guarantee and verified reviews.",
|
| 685 |
+
"concept": "Risk-Free Male Medical Weight Loss",
|
| 686 |
+
},
|
| 687 |
+
{
|
| 688 |
+
"image_prompt": "A lifestyle-focused affiliate ad showing a fit woman running on a sunny beach alongside a large smartphone call-screen mockup, overlaid with a clean GLP-1 offer card mentioning home delivery and telehealth for a monthly price; aspirational, modern, and approachable design blending fitness imagery with digital healthcare convenience.",
|
| 689 |
+
"angle": "Lifestyle aspiration + convenience + telehealth access, framing weight loss as a timely, effortless decision delivered to your door.",
|
| 690 |
+
"concept": "Summer-Ready Medical Weight Loss Convenience",
|
| 691 |
+
},
|
| 692 |
+
{
|
| 693 |
+
"image_prompt": "A premium affiliate ad with a dark, modern background featuring multiple branded GLP-1 vials and a syringe in a clean product-shot layout, overlaid with a social-style testimonial card from a verified user claiming significant weight loss; sleek, masculine, trust-focused design that blends pharmaceutical credibility with social proof.",
|
| 694 |
+
"angle": "Testimonial authority + male relevance, using a verified personal success story to build trust and aspiration.",
|
| 695 |
+
"concept": "Social-Proof–Driven Men's Medical Weight Loss",
|
| 696 |
+
},
|
| 697 |
+
{
|
| 698 |
+
"image_prompt": "A clean, modern affiliate ad with a soft light-blue background featuring a single GLP-1 Semaglutide vial, bold headline text reading A better you. Now at a better price, and simple checklist benefits (no hidden fees, easy approval, 3-day shipping, no insurance needed); polished, trustworthy, medical-grade design optimized for display and social traffic.",
|
| 699 |
+
"angle": "Price transparency + ease of access, emphasizing no hidden fees, fast approval, quick shipping, and no insurance to reduce friction.",
|
| 700 |
+
"concept": "Affordable, Accessible Medical Weight Loss",
|
| 701 |
+
},
|
| 702 |
+
{
|
| 703 |
+
"image_prompt": "A lifestyle-driven affiliate ad featuring a confident, larger-built man in a modern home setting alongside a Notes-style overlay listing clear benefits (GLP-1 access, average weight loss percentage, men-focused design, guarantee), branded subtly; aspirational yet relatable, masculine, and trust-focused for social and native traffic.",
|
| 704 |
+
"angle": "Authority + relevance + risk reversal, combining measurable average results, fast medication access, and a money-back guarantee.",
|
| 705 |
+
"concept": "Men-Specific, Proven Medical Weight Loss",
|
| 706 |
+
},
|
| 707 |
+
{
|
| 708 |
+
"image_prompt": "A clean, minimalist affiliate ad on a white background featuring a GLP-Activate supplement bottle centered below bold blue text reading a large waitlist number, designed to emphasize popularity and limited availability; simple, clinical, supplement-style aesthetic optimized for native and display traffic with a strong scarcity cue.",
|
| 709 |
+
"angle": "Scarcity + social proof, leveraging a large waitlist number to signal demand and credibility.",
|
| 710 |
+
"concept": "High-Demand Alternative GLP-1 Support",
|
| 711 |
+
},
|
| 712 |
+
{
|
| 713 |
+
"image_prompt": "A clean, review-style affiliate ad with a blue gradient background, two Semaglutide vials angled on the left, and a clear headline reading Our Experience with the Semaglutide Weight Loss Program, designed to feel editorial, neutral, and informative rather than sales-driven, optimized for native and display traffic.",
|
| 714 |
+
"angle": "Authentic experience + credibility, framing the program through personal usage and outcomes to build trust.",
|
| 715 |
+
"concept": "First-Hand Review of a Semaglutide Weight Loss Program",
|
| 716 |
+
},
|
| 717 |
+
{
|
| 718 |
+
"image_prompt": "A clean, educational affiliate ad featuring a close-up, artistic image of hands balancing oranges above a person's head, paired with modern typography reading Dietary Changes to Complement GLP-1 Therapy, set on a calm blue background; informative, supportive, and medical-adjacent design optimized for compliant native and display traffic.",
|
| 719 |
+
"angle": "Education + optimization, emphasizing that simple dietary changes can enhance GLP-1 therapy results.",
|
| 720 |
+
"concept": "Holistic GLP-1 Weight Loss Support",
|
| 721 |
+
},
|
| 722 |
+
{
|
| 723 |
+
"image_prompt": "A bold affiliate ad with a dark green background featuring a centered GLP-1+ supplement bottle, strong headline text about people switching from Ozempic to this, and surrounding icon-based benefit callouts (natural GLP-1 boost, affordable, sustainable weight loss, no side effects, no insurance needed); high-contrast, attention-grabbing, comparison-driven design optimized for native and social traffic.",
|
| 724 |
+
"angle": "Replacement + cost savings + safety, framing the product as a cheaper, natural alternative to Ozempic-style medications.",
|
| 725 |
+
"concept": "Natural Alternative to Prescription Weight Loss",
|
| 726 |
+
},
|
| 727 |
+
{
|
| 728 |
+
"image_prompt": "A premium lifestyle affiliate ad featuring a close-up of a hand holding a Sodium Butyrate or GLP-1 support supplement bottle on a clean surface, soft natural lighting, and minimal icon callouts highlighting benefits like blood sugar balance, metabolic support, and overall wellness, paired with the headline Boost Your GLP-1, Elevate Your Wellness; calm, credible, supplement-style design optimized for native and content-driven traffic.",
|
| 729 |
+
"angle": "Holistic health + natural optimization, emphasizing microbiome support and gentle GLP-1 boosting without prescription drugs.",
|
| 730 |
+
"concept": "Gut-Driven GLP-1 Wellness Support",
|
| 731 |
+
},
|
| 732 |
+
{
|
| 733 |
+
"image_prompt": "A clean, pharmaceutical-style affiliate ad with a cool blue gradient background, a floating Semaglutide vial labeled with brand, large bold text reading GLP-1 Compounded Semaglutide, and supporting callouts like same active ingredient, visible results, and monthly price; modern, medical, value-driven design optimized for display and native traffic.",
|
| 734 |
+
"angle": "Price comparison + equivalence + savings, emphasizing same active ingredient as Ozempic/Wegovy and a low monthly cost.",
|
| 735 |
+
"concept": "Affordable Access to Prescription-Grade GLP-1 Weight Loss",
|
| 736 |
+
},
|
| 737 |
+
{
|
| 738 |
+
"image_prompt": "A clean split-screen affiliate ad with a soft blue medical background, showing branded Ozempic/Wegovy pens on one side labeled high cost and compounded Semaglutide vials on the other labeled low cost with a bold savings badge, headline reading Affordable compounded GLP-1 injections, and a clinical, trustworthy aesthetic optimized for native and display traffic.",
|
| 739 |
+
"angle": "Extreme savings + equivalence, directly contrasting expensive brand pens with affordable compounded injections.",
|
| 740 |
+
"concept": "Cost-Comparison GLP-1 Access",
|
| 741 |
+
},
|
| 742 |
+
{
|
| 743 |
+
"image_prompt": "A minimalist, premium affiliate ad set in a warm, modern kitchen with soft focus, featuring a clean GLP-1 therapy box on a countertop, subtle brand typography, and calming headline text like For those who've tried everything and There's no reason to feel helpless, using neutral tones, shallow depth of field, and an upscale wellness aesthetic optimized for native and brand-safe placements.",
|
| 744 |
+
"angle": "Empathy + reassurance, speaking directly to frustration and hopelessness while softly restoring control and optimism.",
|
| 745 |
+
"concept": "Emotional Reset for Weight Loss Burnout",
|
| 746 |
+
},
|
| 747 |
+
{
|
| 748 |
+
"image_prompt": "Clean, modern lifestyle scene showing a minimal GLP-1 vial or supplement bottle on a neutral surface, soft natural lighting, muted medical-wellness color palette (blues, whites, greens), subtle social-proof elements (phone screen, notes app UI, or testimonial card), no before/after bodies, no dramatic claims, calm and trustworthy aesthetic.",
|
| 749 |
+
"angle": "Stop overpaying + you're not alone — a relatable, peer-to-peer discovery story that reframes GLP-1 as a modern, practical solution.",
|
| 750 |
+
"concept": "Affordable, accessible GLP-1 weight-management support",
|
| 751 |
+
},
|
| 752 |
+
{
|
| 753 |
+
"image_prompt": "Clean beige-to-warm gradient background with a single minimalist semaglutide vial floating at a slight angle, soft shadow beneath, elegant editorial lighting, large question-style headline typography (How do GLP-1s work?), modern wellness brand aesthetic, premium but approachable healthcare look, no people, no before/after visuals, no aggressive sales elements.",
|
| 754 |
+
"angle": "Understand before you start — reducing fear and confusion by explaining how GLP-1s work in a calm, trustworthy way.",
|
| 755 |
+
"concept": "Simple education-first introduction to GLP-1s",
|
| 756 |
+
},
|
| 757 |
+
{
|
| 758 |
+
"image_prompt": "Minimal studio layout featuring three recognizable GLP-1 injector pens aligned horizontally on a soft green gradient background, clean medical lighting, sharp focus, modern sans-serif typography, price-forward headline above, subtle membership note below, calm and credible healthcare aesthetic with no people, no body transformations, and no dramatic effects.",
|
| 759 |
+
"angle": "Premium meds, practical pricing — reframing GLP-1s as realistically attainable through smarter access.",
|
| 760 |
+
"concept": "Access to brand-name GLP-1 medications made affordable",
|
| 761 |
+
},
|
| 762 |
+
{
|
| 763 |
+
"image_prompt": "Minimal off-white background with a bold vertical list of GLP-1 brand names in black typography, red distressed DENIED stamps diagonally over the first three names, one final option stamped in green APPROVED, strong contrast, urgent editorial ad style, flat graphic design, no people, high readability, healthcare-meets-fintech aesthetic.",
|
| 764 |
+
"angle": "Denied everywhere else — tapping into frustration and relief by contrasting rejection with fast approval and simplicity.",
|
| 765 |
+
"concept": "Access unlocked when the system says no",
|
| 766 |
+
},
|
| 767 |
+
{
|
| 768 |
+
"image_prompt": "Clean medical-news layout with bold headline typography, white background, subtle black dividers, a smiling everyday woman holding a GLP-1 injection pen in a casual home setting, natural light, authentic smartphone-style photo, credible editorial aesthetic, minimal branding, looks like a trusted health publication feature.",
|
| 769 |
+
"angle": "It's everywhere, and it works — leveraging news-style credibility and social proof to reduce skepticism.",
|
| 770 |
+
"concept": "Authority-backed weight loss breakthrough",
|
| 771 |
+
},
|
| 772 |
+
{
|
| 773 |
+
"image_prompt": "Minimalist studio product shot of a branded semaglutide vial floating at a slight angle, warm neutral gradient background (beige/gold), soft diffused lighting, subtle shadow for depth, modern sans-serif typography accents, premium DTC healthcare aesthetic, clean and uncluttered composition, editorial commercial style.",
|
| 774 |
+
"angle": "Trusted brand, simplified access — familiar DTC brand credibility + clean pricing to make GLP-1 feel approachable and mainstream.",
|
| 775 |
+
"concept": "Premium, modern medical weight-loss solution",
|
| 776 |
+
},
|
| 777 |
+
{
|
| 778 |
+
"image_prompt": "Clean, airy 3D render of white oral tablets floating mid-air, soft blue gradient background with subtle depth blocks, gentle motion blur for lightness, bright clinical lighting, minimal shadows, modern healthcare aesthetic, calm and accessible tone, premium DTC medical branding style.",
|
| 779 |
+
"angle": "No needles, no friction — same modern GLP-1 promise delivered in an easier, more approachable oral form.",
|
| 780 |
+
"concept": "Injection-free GLP-1 weight loss as a simple, everyday pill",
|
| 781 |
+
},
|
| 782 |
+
{
|
| 783 |
+
"image_prompt": "A serene, lifestyle-meets-medical visual showing a semaglutide vial and syringe resting peacefully, as if part of a weekly routine. Cool blue tones, balanced composition, lots of negative space for headline text. The scene conveys effortlessness and long-term transformation—quiet confidence, simplicity, and consistency. High-end pharmaceutical photography style, ultra-realistic, clean, trustworthy, modern, ad-ready.",
|
| 784 |
+
"angle": "Minimal effort, maximum change — meaningful weight loss doesn't require daily struggle, just one consistent weekly step.",
|
| 785 |
+
"concept": "One simple weekly injection that compounds into long-term transformation",
|
| 786 |
+
},
|
| 787 |
+
{
|
| 788 |
+
"image_prompt": "Premium 3D render of a single compounded semaglutide vial centered against a soft gradient background transitioning from mint green to warm peach, centered composition with ample negative space, subtle shadow beneath the vial, clean sans-serif typography accents implied, calm clinical lighting, modern DTC healthcare aesthetic, trustworthy and polished tone, mobile-optimized vertical framing.",
|
| 789 |
+
"angle": "Premium treatment, temporary price — urgency-led access to doctor-prescribed GLP-1 with transparent monthly pricing.",
|
| 790 |
+
"concept": "Compounded Semaglutide as a limited-time, high-value medical alternative",
|
| 791 |
+
},
|
| 792 |
+
{
|
| 793 |
+
"image_prompt": "iMessage-style chat interface on a dark mode smartphone screen, blue and gray message bubbles showing a casual conversation, a smiling woman's selfie embedded mid-thread with warm natural indoor lighting, modern apartment background slightly blurred, clean UI elements, realistic typography and spacing, subtle glow on the phone screen, lifestyle DTC healthcare aesthetic, authentic, friendly, and conversational tone.",
|
| 794 |
+
"angle": "If it worked for her, it'll work for me — leverages authenticity and word-of-mouth credibility to reduce skepticism.",
|
| 795 |
+
"concept": "Peer-to-peer social proof that frames GLP-1 as a trusted recommendation from a close friend",
|
| 796 |
+
},
|
| 797 |
+
{
|
| 798 |
+
"image_prompt": "Split-screen comparison layout with a cool blue left panel and warm blush right panel, clean vertical divider labeled VS, left side showing a single minimal GLP-1 vial with checkmark icons and benefit bullets, right side showing branded injector pens with red X icons and cost warnings, large bold price figures anchored at the bottom of each column, soft gradient backgrounds, flat-lay product renders, modern sans-serif typography, clinical yet consumer-friendly DTC healthcare design.",
|
| 799 |
+
"angle": "Same science, none of the markup — patients pay more for identical active ingredients; compounded option removes friction and cost.",
|
| 800 |
+
"concept": "Side-by-side cost and access comparison",
|
| 801 |
+
},
|
| 802 |
+
{
|
| 803 |
+
"image_prompt": "Warm, editorial lifestyle photograph of a confident plus-size woman mid-movement, natural pose with soft muscle definition, neutral studio background in beige/stone tones, diffused natural lighting, gentle shadows, authentic skin texture, calm and empowering mood. Subtle text overlay space at center or lower third, modern DTC wellness branding, inclusive and human-first aesthetic, minimal props, premium but relatable health and wellness campaign style.",
|
| 804 |
+
"angle": "Real bodies, real progress — shifts the narrative away from perfection and before/after tropes, emphasizing confidence and gradual transformation.",
|
| 805 |
+
"concept": "Body-positive, lifestyle-led GLP-1 weight loss",
|
| 806 |
+
},
|
| 807 |
+
{
|
| 808 |
+
"image_prompt": "Dark, high-contrast 3D product scene with multiple GLP-1 vials arranged in a strong, grounded formation on a matte surface, syringe placed diagonally in the foreground, deep charcoal or black background, subtle spotlighting with crisp highlights, masculine color palette (greens, greys, metallics), premium medical realism, sharp focus, minimal props, modern DTC men's health branding aesthetic, confident and authoritative tone.",
|
| 809 |
+
"angle": "Built for men who want outcomes, not gimmicks — emphasizes measurable results, clinical credibility, and confidence.",
|
| 810 |
+
"concept": "Male-specific GLP-1 weight loss as a serious, results-driven medical solution",
|
| 811 |
+
},
|
| 812 |
+
{
|
| 813 |
+
"image_prompt": "Bright, clean 3D product lineup of multiple amber GLP-1 vials arranged evenly in a row, bold sky-blue background for trust and clarity, high-contrast white and black typography space above, soft studio lighting with subtle reflections on glass, minimal shadows, modern DTC pharma aesthetic, approachable and affordable tone, crisp focus, simple composition that highlights quantity and value.",
|
| 814 |
+
"angle": "Same science, smaller commitment — reframes GLP-1s from an expensive decision into an easy first step with impulse-friendly pricing.",
|
| 815 |
+
"concept": "Affordable access to GLP-1 weight loss as a low-barrier entry point",
|
| 816 |
+
},
|
| 817 |
+
{
|
| 818 |
+
"image_prompt": "Warm, intimate lifestyle close-up of a hand gently holding a small glass GLP-1 vial between fingers, soft natural skin tones in the background, shallow depth of field, muted sage and olive color palette, diffused natural lighting, minimal clinical cues, premium wellness aesthetic, calm and empowering mood, modern DTC health brand style, tactile and human-centered composition.",
|
| 819 |
+
"angle": "Transformation you can feel, confidence you can see — reframes weight loss from numbers to emotional payoff and self-assurance.",
|
| 820 |
+
"concept": "GLP-1 weight loss as a personal confidence ritual",
|
| 821 |
+
},
|
| 822 |
+
{
|
| 823 |
+
"image_prompt": "A clean, modern healthcare ad showing a human hand holding a small glass vial labeled GLP-1, set against a calm blue medical gradient background. The vial looks clinical and premium, with minimal branding and clear liquid inside. On one side, simple rounded text boxes highlight benefits like no insurance, no waiting rooms, mimics natural GLP-1 hormone, reduces appetite naturally, and supports long-term weight management. The overall visual style is DTC healthcare, trustworthy, compliant, and minimal, similar to modern telehealth brands.",
|
| 824 |
+
"angle": "Brand-equivalent + convenience — same active ingredient as Ozempic while removing friction through home delivery and no clinic visits.",
|
| 825 |
+
"concept": "Accessible medical weight loss made simple",
|
| 826 |
+
},
|
| 827 |
+
{
|
| 828 |
+
"image_prompt": "A premium DTC healthcare advertisement on a white background featuring a sealed medical pouch with blue accents and a small prescription-style glass vial labeled Semaglutide beside it. Dynamic water splash elements surround the packaging to convey freshness, purity, and safe handling. Bold headline text reads 25,000+ Satisfied Customers. The overall style is minimal, clinical, and premium, aligned with modern DTC healthcare and pharmaceutical branding.",
|
| 829 |
+
"angle": "Social proof + trust + personalization — leveraging a large customer base to establish credibility and individualized treatment.",
|
| 830 |
+
"concept": "Trusted, personalized healthcare at scale",
|
| 831 |
+
},
|
| 832 |
+
{
|
| 833 |
+
"image_prompt": "A clean, comparison-style healthcare advertisement on a white background with a bold seasonal or lifestyle headline. The center features a side-by-side comparison: on the left, branded packaging and a semaglutide vial; on the right, a generic competitor vial labeled Competitors, separated by a circular VS marker. The bottom uses structured comparison bars across Pricing, Supply, Quality, and Products, showing advantages versus competitor drawbacks. Clean, structured, compliant design optimized for clarity and fast scanning.",
|
| 834 |
+
"angle": "Seasonal urgency or lifestyle outcome + comparison + reliability — framing the brand as the smarter, more reliable choice.",
|
| 835 |
+
"concept": "Choose the right provider before it matters",
|
| 836 |
+
},
|
| 837 |
+
]
|
| 838 |
+
|
| 839 |
# ============================================================================
|
| 840 |
# SECTION 3: VISUAL LIBRARY - Category Entry Points & Psychological Moments
|
| 841 |
# Based on DirectMeds Marketing Brief: 7 Ws, CEPs, Life-Force 8, Stage of Awareness
|
|
|
|
| 1236 |
"product_aware": PRODUCT_AWARE_VISUALS,
|
| 1237 |
"most_aware": MOST_AWARE_VISUALS,
|
| 1238 |
},
|
| 1239 |
+
"image_creative_prompts": GLP_IMAGE_CREATIVE_PROMPTS,
|
| 1240 |
}
|
| 1241 |
|
data/home_insurance.py
CHANGED
|
@@ -1,6 +1,5 @@
|
|
| 1 |
"""
|
| 2 |
Home Insurance - Complete Psychological Arsenal
|
| 3 |
-
All strategies: fear, shame, urgency, greed, authority, loss aversion, etc.
|
| 4 |
Updated with winning ad patterns from high-converting creative analysis.
|
| 5 |
"""
|
| 6 |
|
|
|
|
| 1 |
"""
|
| 2 |
Home Insurance - Complete Psychological Arsenal
|
|
|
|
| 3 |
Updated with winning ad patterns from high-converting creative analysis.
|
| 4 |
"""
|
| 5 |
|
data/visuals.py
CHANGED
|
@@ -96,28 +96,6 @@ NEGATIVE_PROMPTS: List[str] = [
|
|
| 96 |
"no distorted faces", "no extra limbs", "no AI artifacts", "no blurry main subjects", "no over-processed HDR look",
|
| 97 |
]
|
| 98 |
|
| 99 |
-
NICHE_VISUAL_GUIDANCE: Dict[str, Dict[str, Any]] = {
|
| 100 |
-
"home_insurance": {
|
| 101 |
-
"subjects": ["family in front of home", "house exterior", "homeowner looking confident", "couple reviewing papers"],
|
| 102 |
-
"props": ["insurance documents", "house keys", "tablet showing coverage", "family photos"],
|
| 103 |
-
"avoid": ["disasters", "fire or floods", "stressed expressions", "dark settings"],
|
| 104 |
-
"color_preference": "trust",
|
| 105 |
-
},
|
| 106 |
-
"glp1": {
|
| 107 |
-
"subjects": ["confident person smiling (age 30-50)", "active lifestyle scenes with adults", "healthy meal preparation", "doctor consultation", "person in their 30s-40s looking confident"],
|
| 108 |
-
"props": ["fitness equipment", "healthy food", "comfortable clothing"],
|
| 109 |
-
"avoid": ["before/after weight comparisons", "measuring tapes", "scales prominently", "needle close-ups", "elderly people over 65", "senior citizens", "very old looking people", "gray-haired elderly groups"],
|
| 110 |
-
"age_guidance": "Show people aged 30-50 primarily. Avoid defaulting to elderly/senior citizens. Target audience is middle-aged adults, not seniors.",
|
| 111 |
-
"color_preference": "health",
|
| 112 |
-
},
|
| 113 |
-
"auto_insurance": {
|
| 114 |
-
"subjects": ["driver with car", "family near vehicle", "car owner looking confident", "person reviewing insurance documents"],
|
| 115 |
-
"props": ["insurance papers", "car keys", "vehicle", "smartphone with app", "documents"],
|
| 116 |
-
"avoid": ["car accidents", "crashes", "damaged vehicles prominently", "stressed expressions"],
|
| 117 |
-
"color_preference": "trust",
|
| 118 |
-
},
|
| 119 |
-
}
|
| 120 |
-
|
| 121 |
def get_color_palette(trigger: str) -> Dict[str, str]:
|
| 122 |
return COLOR_PALETTES.get(trigger, COLOR_PALETTES["trust"])
|
| 123 |
|
|
@@ -136,9 +114,6 @@ def get_random_composition() -> str:
|
|
| 136 |
def get_random_mood() -> str:
|
| 137 |
return random.choice(VISUAL_MOODS)
|
| 138 |
|
| 139 |
-
def get_niche_visual_guidance(niche: str) -> Optional[Dict[str, Any]]:
|
| 140 |
-
return NICHE_VISUAL_GUIDANCE.get(niche.lower().replace(" ", "_").replace("-", "_"))
|
| 141 |
-
|
| 142 |
def build_negative_prompt() -> str:
|
| 143 |
return ", ".join(NEGATIVE_PROMPTS)
|
| 144 |
|
|
|
|
| 96 |
"no distorted faces", "no extra limbs", "no AI artifacts", "no blurry main subjects", "no over-processed HDR look",
|
| 97 |
]
|
| 98 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 99 |
def get_color_palette(trigger: str) -> Dict[str, str]:
|
| 100 |
return COLOR_PALETTES.get(trigger, COLOR_PALETTES["trust"])
|
| 101 |
|
|
|
|
| 114 |
def get_random_mood() -> str:
|
| 115 |
return random.choice(VISUAL_MOODS)
|
| 116 |
|
|
|
|
|
|
|
|
|
|
| 117 |
def build_negative_prompt() -> str:
|
| 118 |
return ", ".join(NEGATIVE_PROMPTS)
|
| 119 |
|
frontend/components/generation/CorrectionModal.tsx
CHANGED
|
@@ -4,6 +4,7 @@ import React, { useState, useEffect } from "react";
|
|
| 4 |
import { X, Wand2, Image as ImageIcon, CheckCircle2, AlertCircle, Loader2, Sparkles, Download } from "lucide-react";
|
| 5 |
import { correctImage } from "@/lib/api/endpoints";
|
| 6 |
import type { ImageCorrectResponse, AdCreativeDB } from "@/types/api";
|
|
|
|
| 7 |
import { ProgressBar } from "@/components/ui/ProgressBar";
|
| 8 |
import { Button } from "@/components/ui/Button";
|
| 9 |
import { Input } from "@/components/ui/Input";
|
|
@@ -37,6 +38,11 @@ export const CorrectionModal: React.FC<CorrectionModalProps> = ({
|
|
| 37 |
const [userInstructions, setUserInstructions] = useState("");
|
| 38 |
const [useAutoAnalyze, setUseAutoAnalyze] = useState(false);
|
| 39 |
const [keepCorrected, setKeepCorrected] = useState(true);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
|
| 41 |
useEffect(() => {
|
| 42 |
if (isOpen) {
|
|
@@ -342,13 +348,18 @@ export const CorrectionModal: React.FC<CorrectionModalProps> = ({
|
|
| 342 |
</div>
|
| 343 |
</div>
|
| 344 |
<div className="aspect-square relative rounded-xl overflow-hidden border border-gray-200 cursor-pointer" onClick={() => setKeepCorrected(true)}>
|
| 345 |
-
{result.corrected_image
|
| 346 |
-
|
| 347 |
-
|
| 348 |
-
|
| 349 |
-
|
| 350 |
-
|
| 351 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 352 |
<div className="absolute top-2 right-2 px-2 py-1 bg-green-500 text-white text-xs font-bold rounded-md shadow-sm">
|
| 353 |
NEW
|
| 354 |
</div>
|
|
|
|
| 4 |
import { X, Wand2, Image as ImageIcon, CheckCircle2, AlertCircle, Loader2, Sparkles, Download } from "lucide-react";
|
| 5 |
import { correctImage } from "@/lib/api/endpoints";
|
| 6 |
import type { ImageCorrectResponse, AdCreativeDB } from "@/types/api";
|
| 7 |
+
import { getImageUrlFallback } from "@/lib/utils/formatters";
|
| 8 |
import { ProgressBar } from "@/components/ui/ProgressBar";
|
| 9 |
import { Button } from "@/components/ui/Button";
|
| 10 |
import { Input } from "@/components/ui/Input";
|
|
|
|
| 38 |
const [userInstructions, setUserInstructions] = useState("");
|
| 39 |
const [useAutoAnalyze, setUseAutoAnalyze] = useState(false);
|
| 40 |
const [keepCorrected, setKeepCorrected] = useState(true);
|
| 41 |
+
const [correctedImgUseFallback, setCorrectedImgUseFallback] = useState(false);
|
| 42 |
+
|
| 43 |
+
useEffect(() => {
|
| 44 |
+
if (result) setCorrectedImgUseFallback(false);
|
| 45 |
+
}, [result]);
|
| 46 |
|
| 47 |
useEffect(() => {
|
| 48 |
if (isOpen) {
|
|
|
|
| 348 |
</div>
|
| 349 |
</div>
|
| 350 |
<div className="aspect-square relative rounded-xl overflow-hidden border border-gray-200 cursor-pointer" onClick={() => setKeepCorrected(true)}>
|
| 351 |
+
{result.corrected_image && (result.corrected_image.image_url || result.corrected_image.filename) && (() => {
|
| 352 |
+
const { primary, fallback } = getImageUrlFallback(result.corrected_image?.image_url, result.corrected_image?.filename, result.corrected_image?.r2_url);
|
| 353 |
+
const src = (correctedImgUseFallback && fallback ? fallback : primary ?? fallback) ?? "";
|
| 354 |
+
return src ? (
|
| 355 |
+
<img
|
| 356 |
+
src={src}
|
| 357 |
+
alt="Corrected"
|
| 358 |
+
className="w-full h-full object-cover"
|
| 359 |
+
onError={() => { if (fallback) setCorrectedImgUseFallback(true); }}
|
| 360 |
+
/>
|
| 361 |
+
) : null;
|
| 362 |
+
})()}
|
| 363 |
<div className="absolute top-2 right-2 px-2 py-1 bg-green-500 text-white text-xs font-bold rounded-md shadow-sm">
|
| 364 |
NEW
|
| 365 |
</div>
|
frontend/components/generation/RegenerationModal.tsx
CHANGED
|
@@ -4,6 +4,7 @@ import React, { useState, useEffect } from "react";
|
|
| 4 |
import { X, RefreshCw, CheckCircle2, AlertCircle, Loader2, Sparkles, ChevronDown, Check, ArrowLeft, Image as ImageIcon } from "lucide-react";
|
| 5 |
import { regenerateImage, getImageModels, confirmImageSelection } from "@/lib/api/endpoints";
|
| 6 |
import type { ImageRegenerateResponse, AdCreativeDB, ImageModel } from "@/types/api";
|
|
|
|
| 7 |
import { ProgressBar } from "@/components/ui/ProgressBar";
|
| 8 |
import { Button } from "@/components/ui/Button";
|
| 9 |
import { Card, CardContent } from "@/components/ui/Card";
|
|
@@ -35,6 +36,11 @@ export const RegenerationModal: React.FC<RegenerationModalProps> = ({
|
|
| 35 |
const [loadingModels, setLoadingModels] = useState(false);
|
| 36 |
const [selectedImage, setSelectedImage] = useState<"original" | "new" | null>(null);
|
| 37 |
const [savingSelection, setSavingSelection] = useState(false);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
|
| 39 |
useEffect(() => {
|
| 40 |
if (isOpen) {
|
|
@@ -359,9 +365,24 @@ export const RegenerationModal: React.FC<RegenerationModalProps> = ({
|
|
| 359 |
</div>
|
| 360 |
)}
|
| 361 |
<img
|
| 362 |
-
src={
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 363 |
alt="Regenerated"
|
| 364 |
className="w-full aspect-square object-cover"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 365 |
/>
|
| 366 |
<div className="p-3 bg-purple-50">
|
| 367 |
<p className="text-xs text-gray-500">Model</p>
|
|
@@ -425,11 +446,39 @@ export const RegenerationModal: React.FC<RegenerationModalProps> = ({
|
|
| 425 |
{/* Show the selected image */}
|
| 426 |
<div className="flex justify-center">
|
| 427 |
<div className="max-w-md">
|
| 428 |
-
{((selectedImage === "new"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 429 |
<img
|
| 430 |
-
src={(selectedImage === "new"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 431 |
alt="Selected"
|
| 432 |
className="w-full rounded-xl border border-gray-200"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 433 |
/>
|
| 434 |
) : (
|
| 435 |
<div className="w-full aspect-square bg-gray-50 flex items-center justify-center text-gray-400 rounded-xl border border-gray-200">
|
|
|
|
| 4 |
import { X, RefreshCw, CheckCircle2, AlertCircle, Loader2, Sparkles, ChevronDown, Check, ArrowLeft, Image as ImageIcon } from "lucide-react";
|
| 5 |
import { regenerateImage, getImageModels, confirmImageSelection } from "@/lib/api/endpoints";
|
| 6 |
import type { ImageRegenerateResponse, AdCreativeDB, ImageModel } from "@/types/api";
|
| 7 |
+
import { getImageUrlFallback } from "@/lib/utils/formatters";
|
| 8 |
import { ProgressBar } from "@/components/ui/ProgressBar";
|
| 9 |
import { Button } from "@/components/ui/Button";
|
| 10 |
import { Card, CardContent } from "@/components/ui/Card";
|
|
|
|
| 36 |
const [loadingModels, setLoadingModels] = useState(false);
|
| 37 |
const [selectedImage, setSelectedImage] = useState<"original" | "new" | null>(null);
|
| 38 |
const [savingSelection, setSavingSelection] = useState(false);
|
| 39 |
+
const [regenImgUseFallback, setRegenImgUseFallback] = useState(false);
|
| 40 |
+
|
| 41 |
+
useEffect(() => {
|
| 42 |
+
if (result) setRegenImgUseFallback(false);
|
| 43 |
+
}, [result]);
|
| 44 |
|
| 45 |
useEffect(() => {
|
| 46 |
if (isOpen) {
|
|
|
|
| 365 |
</div>
|
| 366 |
)}
|
| 367 |
<img
|
| 368 |
+
src={(() => {
|
| 369 |
+
const { primary, fallback } = getImageUrlFallback(
|
| 370 |
+
result.regenerated_image?.image_url,
|
| 371 |
+
result.regenerated_image?.filename,
|
| 372 |
+
result.regenerated_image?.r2_url
|
| 373 |
+
);
|
| 374 |
+
return (regenImgUseFallback && fallback ? fallback : primary ?? fallback) ?? "";
|
| 375 |
+
})()}
|
| 376 |
alt="Regenerated"
|
| 377 |
className="w-full aspect-square object-cover"
|
| 378 |
+
onError={() => {
|
| 379 |
+
const { fallback } = getImageUrlFallback(
|
| 380 |
+
result.regenerated_image?.image_url,
|
| 381 |
+
result.regenerated_image?.filename,
|
| 382 |
+
result.regenerated_image?.r2_url
|
| 383 |
+
);
|
| 384 |
+
if (fallback) setRegenImgUseFallback(true);
|
| 385 |
+
}}
|
| 386 |
/>
|
| 387 |
<div className="p-3 bg-purple-50">
|
| 388 |
<p className="text-xs text-gray-500">Model</p>
|
|
|
|
| 446 |
{/* Show the selected image */}
|
| 447 |
<div className="flex justify-center">
|
| 448 |
<div className="max-w-md">
|
| 449 |
+
{((selectedImage === "new"
|
| 450 |
+
? (() => {
|
| 451 |
+
const { primary, fallback } = getImageUrlFallback(
|
| 452 |
+
result?.regenerated_image?.image_url,
|
| 453 |
+
result?.regenerated_image?.filename,
|
| 454 |
+
result?.regenerated_image?.r2_url
|
| 455 |
+
);
|
| 456 |
+
return (regenImgUseFallback && fallback ? fallback : primary ?? fallback) ?? null;
|
| 457 |
+
})()
|
| 458 |
+
: (result?.original_image_url || ad?.r2_url || ad?.image_url))) ? (
|
| 459 |
<img
|
| 460 |
+
src={(selectedImage === "new"
|
| 461 |
+
? (() => {
|
| 462 |
+
const { primary, fallback } = getImageUrlFallback(
|
| 463 |
+
result?.regenerated_image?.image_url,
|
| 464 |
+
result?.regenerated_image?.filename,
|
| 465 |
+
result?.regenerated_image?.r2_url
|
| 466 |
+
);
|
| 467 |
+
return (regenImgUseFallback && fallback ? fallback : primary ?? fallback) ?? "";
|
| 468 |
+
})()
|
| 469 |
+
: (result?.original_image_url || ad?.r2_url || ad?.image_url))!}
|
| 470 |
alt="Selected"
|
| 471 |
className="w-full rounded-xl border border-gray-200"
|
| 472 |
+
onError={() => {
|
| 473 |
+
if (selectedImage === "new") {
|
| 474 |
+
const { fallback } = getImageUrlFallback(
|
| 475 |
+
result?.regenerated_image?.image_url,
|
| 476 |
+
result?.regenerated_image?.filename,
|
| 477 |
+
result?.regenerated_image?.r2_url
|
| 478 |
+
);
|
| 479 |
+
if (fallback) setRegenImgUseFallback(true);
|
| 480 |
+
}
|
| 481 |
+
}}
|
| 482 |
/>
|
| 483 |
) : (
|
| 484 |
<div className="w-full aspect-square bg-gray-50 flex items-center justify-center text-gray-400 rounded-xl border border-gray-200">
|
services/generator.py
CHANGED
|
@@ -26,7 +26,6 @@ from data.hooks import get_power_words, get_random_cta as get_hook_cta, get_rand
|
|
| 26 |
from data.triggers import get_random_trigger, get_trigger_combination, get_triggers_for_niche
|
| 27 |
from data.visuals import (
|
| 28 |
get_color_palette,
|
| 29 |
-
get_niche_visual_guidance,
|
| 30 |
get_random_camera_angle,
|
| 31 |
get_random_composition,
|
| 32 |
get_random_lighting,
|
|
@@ -336,27 +335,14 @@ class AdGenerator:
|
|
| 336 |
# Fallback to strategy visuals
|
| 337 |
return []
|
| 338 |
|
| 339 |
-
# Map strategies to visual categories (
|
| 340 |
auto_insurance_strategy_categories = {
|
| 341 |
-
"
|
| 342 |
-
"
|
| 343 |
-
"
|
| 344 |
-
"
|
| 345 |
-
"
|
| 346 |
-
"
|
| 347 |
-
"identity_targeting": ["seniors_table_style", "coverage_tiers_style", "gift_card_cta_style"],
|
| 348 |
-
"insider_secret": ["social_post_style", "coverage_tiers_style"],
|
| 349 |
-
"fear_based": ["before_after_style", "savings_urgency_style"],
|
| 350 |
-
"urgency_scarcity": ["savings_urgency_style", "gift_card_cta_style"],
|
| 351 |
-
"social_proof_fomo": ["social_post_style", "gift_card_cta_style"],
|
| 352 |
-
"guilt_shame": ["before_after_style", "gift_card_cta_style"],
|
| 353 |
-
"greed_savings": ["savings_urgency_style", "before_after_style", "gift_card_cta_style"],
|
| 354 |
-
"authority_trust": ["official_notification_style", "gift_card_cta_style"],
|
| 355 |
-
"loss_aversion": ["before_after_style", "savings_urgency_style"],
|
| 356 |
-
"anchoring": ["coverage_tiers_style", "official_notification_style"],
|
| 357 |
-
"simplicity": ["car_brand_grid_style", "coverage_tiers_style"],
|
| 358 |
-
"comparison_envy": ["before_after_style", "coverage_tiers_style"],
|
| 359 |
-
"transformation": ["before_after_style", "gift_card_cta_style"],
|
| 360 |
}
|
| 361 |
default_strategy_to_category = {
|
| 362 |
"accusation_opener": ["problem_risk", "disaster_fear"],
|
|
@@ -655,7 +641,7 @@ Create a SCROLL-STOPPING Facebook ad for {niche.replace("_", " ").upper()} using
|
|
| 655 |
- CRITICAL: Use ONLY {framework_data.get('name', framework)} format - DO NOT mix with other formats
|
| 656 |
- {f"If chat-style framework (e.g. iMessage, WhatsApp): Include 2-4 readable, coherent messages related to {niche.replace('_', ' ').title()}. Use the headline or a variation as one message." if 'chat_style' in framework_data.get('tags', []) else ""}
|
| 657 |
- {f"If document-style framework (e.g. memo, email): Include readable, properly formatted text related to {niche.replace('_', ' ').title()}." if 'document_style' in framework_data.get('tags', []) else ""}
|
| 658 |
-
- FOR AUTO INSURANCE: Describe ONLY one of these ad-format layouts: official notification (seal, rate buttons), social post card,
|
| 659 |
- FOR HOME INSURANCE: Show person with document, savings proof, home setting. People 30-60, relatable homeowners.
|
| 660 |
- FOR GLP-1: REQUIRED - every image brief MUST describe either (1) a GLP-1 medication bottle or pen visible in the scene (e.g. Ozempic, Wegovy, Mounjaro, Zepbound pen or box), OR (2) the text "GLP-1" or a medication name (e.g. Ozempic, Wegovy) visible on a label, screen, document, or surface. Use VARIETY in visual types: quiz interfaces, doctor/medical settings, person on scale, mirror reflections, lifestyle moments, confidence scenes, testimonial portraits, celebrity references, or before/after (only when strategy calls for it). People aged 30-50, not elderly.
|
| 661 |
|
|
@@ -703,7 +689,6 @@ Generate the ad copy now for {niche.replace("_", " ").upper()}. Make it look lik
|
|
| 703 |
lighting: str,
|
| 704 |
composition: str,
|
| 705 |
visual_style_data: Optional[Dict[str, Any]] = None,
|
| 706 |
-
niche_visual_guidance_data: Optional[Dict[str, Any]] = None,
|
| 707 |
trending_context: Optional[str] = None,
|
| 708 |
) -> str:
|
| 709 |
"""
|
|
@@ -716,6 +701,14 @@ Generate the ad copy now for {niche.replace("_", " ").upper()}. Make it look lik
|
|
| 716 |
image_brief = ad_copy.get("image_brief", "")
|
| 717 |
headline = ad_copy.get("headline", "")
|
| 718 |
psychological_angle = ad_copy.get("psychological_angle", "")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 719 |
# Same framework used for copy is used for visual (no separate container)
|
| 720 |
framework_key = ad_copy.get("framework_key") or ad_copy.get("container_key", "")
|
| 721 |
framework_name = ad_copy.get("framework") or ad_copy.get("container_used", "Standard Ad")
|
|
@@ -774,7 +767,7 @@ Generate the ad copy now for {niche.replace("_", " ").upper()}. Make it look lik
|
|
| 774 |
niche_image_guidance = (niche_data.get("image_guidance", "") + """
|
| 775 |
|
| 776 |
PEOPLE, FACES, AND CARS ARE OPTIONAL. Only include them when the VISUAL SCENE description explicitly mentions them. Most ad formats are typography, layout, and buttons only.
|
| 777 |
-
NO fake or made-up brand/company names (no gibberish); use generic text only or omit. NO in-car dashboard mockups or screens inside car interiors; stick to the
|
| 778 |
)
|
| 779 |
elif niche == "glp1":
|
| 780 |
niche_data = self._get_niche_data(niche)
|
|
@@ -782,14 +775,6 @@ NO fake or made-up brand/company names (no gibberish); use generic text only or
|
|
| 782 |
|
| 783 |
CRITICAL - GLP-1 PRODUCT VISIBILITY: The image MUST show at least one of: (1) A GLP-1 medication bottle or injectable pen (e.g. Ozempic, Wegovy, Mounjaro, Zepbound) in the scene, OR (2) The text "GLP-1" or a medication name (Ozempic, Wegovy, Mounjaro, etc.) visible on a label, screen, document, or surface. Do not generate a GLP-1 ad image without the product or name visible."""
|
| 784 |
)
|
| 785 |
-
elif niche_visual_guidance_data and isinstance(niche_visual_guidance_data, dict):
|
| 786 |
-
niche_image_guidance = f"""
|
| 787 |
-
NICHE REQUIREMENTS ({niche.replace("_", " ").title()}):
|
| 788 |
-
SUBJECTS: {', '.join(niche_visual_guidance_data.get('subjects', []))}
|
| 789 |
-
PROPS: {', '.join(niche_visual_guidance_data.get('props', []))}
|
| 790 |
-
AVOID: {', '.join(niche_visual_guidance_data.get('avoid', []))}
|
| 791 |
-
COLOR PREFERENCE: {niche_visual_guidance_data.get('color_preference', 'balanced')}
|
| 792 |
-
"""
|
| 793 |
else:
|
| 794 |
niche_data = self._get_niche_data(niche)
|
| 795 |
niche_image_guidance = niche_data.get("image_guidance", "")
|
|
@@ -956,10 +941,10 @@ DOCUMENTS (if present):
|
|
| 956 |
{text_overlay_section}
|
| 957 |
|
| 958 |
=== VISUAL SCENE ===
|
| 959 |
-
{
|
| 960 |
|
| 961 |
-
{"=== AUTO INSURANCE AD GRAPHIC (
|
| 962 |
-
{"Follow the VISUAL SCENE description exactly. Use ONLY these
|
| 963 |
|
| 964 |
{people_faces_section}
|
| 965 |
|
|
@@ -1023,7 +1008,7 @@ Apply these effects IF they fit the natural aesthetic (not required):
|
|
| 1023 |
- Text should appear ONCE only, not multiple times in the image
|
| 1024 |
|
| 1025 |
=== OUTPUT ===
|
| 1026 |
-
{"Create a scroll-stopping auto insurance ad graphic. Follow the VISUAL SCENE layout exactly: headline, rates/prices, and CTA or buttons as specified. Use only the
|
| 1027 |
|
| 1028 |
CRITICAL REQUIREMENTS:
|
| 1029 |
{"- Use only the defined ad format from VISUAL SCENE. No dashboard/screen-in-car mockups. No made-up brand names. Borders and buttons only where the format specifies." if is_auto_insurance_ad_format else "- NO decorative borders, frames, or boxes"}
|
|
@@ -1295,10 +1280,7 @@ CRITICAL REQUIREMENTS:
|
|
| 1295 |
|
| 1296 |
# Get power words for copy enhancement
|
| 1297 |
power_words = get_power_words(category=None, count=5)
|
| 1298 |
-
|
| 1299 |
-
# Get niche visual guidance
|
| 1300 |
-
niche_visual_guidance_data = get_niche_visual_guidance(niche)
|
| 1301 |
-
|
| 1302 |
# Get random angle × concept combination (like matrix generation)
|
| 1303 |
combination = matrix_service.generate_single_combination(niche)
|
| 1304 |
angle = combination["angle"]
|
|
@@ -1381,7 +1363,6 @@ CRITICAL REQUIREMENTS:
|
|
| 1381 |
lighting=lighting,
|
| 1382 |
composition=composition,
|
| 1383 |
visual_style_data=visual_style_data,
|
| 1384 |
-
niche_visual_guidance_data=niche_visual_guidance_data,
|
| 1385 |
trending_context=trending_context if use_trending else None,
|
| 1386 |
)
|
| 1387 |
|
|
@@ -1969,19 +1950,7 @@ CONCEPT: {concept.get('name', 'Custom Concept')}
|
|
| 1969 |
)
|
| 1970 |
)
|
| 1971 |
|
| 1972 |
-
# Step
|
| 1973 |
-
print("💡 Step 2b: Generating motivators from research for strategy making...")
|
| 1974 |
-
motivators_from_research = await third_flow_service.generate_motivators_for_research(
|
| 1975 |
-
researcher_output=researcher_output,
|
| 1976 |
-
niche=niche_display,
|
| 1977 |
-
target_audience=target_audience,
|
| 1978 |
-
offer=offer,
|
| 1979 |
-
count_per_item=4,
|
| 1980 |
-
)
|
| 1981 |
-
if motivators_from_research:
|
| 1982 |
-
print(f" Generated {len(motivators_from_research)} motivators for creative director")
|
| 1983 |
-
|
| 1984 |
-
# Step 3: Creative Director (with motivators for strategy making)
|
| 1985 |
print(f"🎨 Step 3: Creating {num_strategies} creative strategy/strategies...")
|
| 1986 |
print(f"📋 Parameters: num_strategies={num_strategies}, num_images={num_images}")
|
| 1987 |
creative_strategies = await asyncio.to_thread(
|
|
@@ -1993,59 +1962,25 @@ CONCEPT: {concept.get('name', 'Custom Concept')}
|
|
| 1993 |
offer=offer,
|
| 1994 |
niche=niche_display,
|
| 1995 |
n=num_strategies,
|
| 1996 |
-
motivators=motivators_from_research if motivators_from_research else None,
|
| 1997 |
)
|
| 1998 |
-
|
| 1999 |
if not creative_strategies:
|
| 2000 |
raise ValueError("Creative director returned no strategies")
|
| 2001 |
-
|
| 2002 |
# Limit to requested number of strategies (in case LLM returns more)
|
| 2003 |
creative_strategies = creative_strategies[:num_strategies]
|
| 2004 |
print(f"📊 Using {len(creative_strategies)} strategy/strategies (requested: {num_strategies})")
|
| 2005 |
-
|
| 2006 |
-
# Step
|
| 2007 |
-
print(f"💡 Step 3b: Resolving motivators for each strategy...")
|
| 2008 |
-
strategies_needing_motivators = [
|
| 2009 |
-
(idx, s) for idx, s in enumerate(creative_strategies)
|
| 2010 |
-
if not s.motivators or len(s.motivators) == 0
|
| 2011 |
-
]
|
| 2012 |
-
generated_motivators: dict[int, str | None] = {}
|
| 2013 |
-
if strategies_needing_motivators:
|
| 2014 |
-
gen_tasks = [
|
| 2015 |
-
third_flow_service.generate_motivators_for_strategy(
|
| 2016 |
-
strategy=s, niche=niche_display,
|
| 2017 |
-
target_audience=target_audience, offer=offer, count=6,
|
| 2018 |
-
)
|
| 2019 |
-
for _, s in strategies_needing_motivators
|
| 2020 |
-
]
|
| 2021 |
-
gen_results = await asyncio.gather(*gen_tasks)
|
| 2022 |
-
for (idx, _), motivators in zip(strategies_needing_motivators, gen_results):
|
| 2023 |
-
generated_motivators[idx] = motivators[0] if motivators else None
|
| 2024 |
-
motivators_per_strategy = []
|
| 2025 |
-
for idx, strategy in enumerate(creative_strategies):
|
| 2026 |
-
if strategy.motivators and len(strategy.motivators) > 0:
|
| 2027 |
-
m = strategy.motivators[0]
|
| 2028 |
-
motivators_per_strategy.append(m)
|
| 2029 |
-
if m:
|
| 2030 |
-
print(f" Strategy {idx + 1} (from director): \"{m[:60]}...\"" if len(m) > 60 else f" Strategy {idx + 1} (from director): \"{m}\"")
|
| 2031 |
-
else:
|
| 2032 |
-
sel = generated_motivators.get(idx)
|
| 2033 |
-
motivators_per_strategy.append(sel)
|
| 2034 |
-
if sel:
|
| 2035 |
-
print(f" Strategy {idx + 1} (generated): \"{sel[:60]}...\"" if len(sel) > 60 else f" Strategy {idx + 1} (generated): \"{sel}\"")
|
| 2036 |
-
|
| 2037 |
-
# Step 4: Process strategies in parallel (designer + copywriter) - Optimized: use asyncio.to_thread instead of ThreadPoolExecutor
|
| 2038 |
print(f"⚡ Step 4: Processing {len(creative_strategies)} strategies in parallel...")
|
| 2039 |
-
|
| 2040 |
-
# Process strategies concurrently using asyncio (non-blocking)
|
| 2041 |
strategy_tasks = [
|
| 2042 |
asyncio.to_thread(
|
| 2043 |
third_flow_service.process_strategy,
|
| 2044 |
strategy,
|
| 2045 |
niche=niche_display,
|
| 2046 |
-
selected_motivator=motivators_per_strategy[idx] if idx < len(motivators_per_strategy) else None,
|
| 2047 |
)
|
| 2048 |
-
for
|
| 2049 |
]
|
| 2050 |
strategy_results = await asyncio.gather(*strategy_tasks)
|
| 2051 |
|
|
|
|
| 26 |
from data.triggers import get_random_trigger, get_trigger_combination, get_triggers_for_niche
|
| 27 |
from data.visuals import (
|
| 28 |
get_color_palette,
|
|
|
|
| 29 |
get_random_camera_angle,
|
| 30 |
get_random_composition,
|
| 31 |
get_random_lighting,
|
|
|
|
| 335 |
# Fallback to strategy visuals
|
| 336 |
return []
|
| 337 |
|
| 338 |
+
# Map strategies to visual categories (auto_insurance: 1:1 with the 6 ad formats)
|
| 339 |
auto_insurance_strategy_categories = {
|
| 340 |
+
"official_notification": ["official_notification_style"],
|
| 341 |
+
"social_post": ["social_post_style"],
|
| 342 |
+
"coverage_tiers": ["coverage_tiers_style"],
|
| 343 |
+
"car_brand_grid": ["car_brand_grid_style"],
|
| 344 |
+
"gift_card_cta": ["gift_card_cta_style"],
|
| 345 |
+
"savings_urgency": ["savings_urgency_style"],
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 346 |
}
|
| 347 |
default_strategy_to_category = {
|
| 348 |
"accusation_opener": ["problem_risk", "disaster_fear"],
|
|
|
|
| 641 |
- CRITICAL: Use ONLY {framework_data.get('name', framework)} format - DO NOT mix with other formats
|
| 642 |
- {f"If chat-style framework (e.g. iMessage, WhatsApp): Include 2-4 readable, coherent messages related to {niche.replace('_', ' ').title()}. Use the headline or a variation as one message." if 'chat_style' in framework_data.get('tags', []) else ""}
|
| 643 |
- {f"If document-style framework (e.g. memo, email): Include readable, properly formatted text related to {niche.replace('_', ' ').title()}." if 'document_style' in framework_data.get('tags', []) else ""}
|
| 644 |
+
- FOR AUTO INSURANCE: Describe ONLY one of these 6 ad-format layouts: (1) official notification (seal, rate buttons), (2) social post card, (3) coverage tier panels, (4) car brand grid, (5) gift card CTA, (6) savings/urgency (yellow, CONTACT US). No other creative types. Do NOT describe testimonial portraits, couples, speech bubbles, quote bubbles, or people holding documents. Do NOT describe elderly or senior people. Typography, layout, prices, and buttons only. All text in the image must be readable and correctly spelled; no gibberish.
|
| 645 |
- FOR HOME INSURANCE: Show person with document, savings proof, home setting. People 30-60, relatable homeowners.
|
| 646 |
- FOR GLP-1: REQUIRED - every image brief MUST describe either (1) a GLP-1 medication bottle or pen visible in the scene (e.g. Ozempic, Wegovy, Mounjaro, Zepbound pen or box), OR (2) the text "GLP-1" or a medication name (e.g. Ozempic, Wegovy) visible on a label, screen, document, or surface. Use VARIETY in visual types: quiz interfaces, doctor/medical settings, person on scale, mirror reflections, lifestyle moments, confidence scenes, testimonial portraits, celebrity references, or before/after (only when strategy calls for it). People aged 30-50, not elderly.
|
| 647 |
|
|
|
|
| 689 |
lighting: str,
|
| 690 |
composition: str,
|
| 691 |
visual_style_data: Optional[Dict[str, Any]] = None,
|
|
|
|
| 692 |
trending_context: Optional[str] = None,
|
| 693 |
) -> str:
|
| 694 |
"""
|
|
|
|
| 701 |
image_brief = ad_copy.get("image_brief", "")
|
| 702 |
headline = ad_copy.get("headline", "")
|
| 703 |
psychological_angle = ad_copy.get("psychological_angle", "")
|
| 704 |
+
# GLP-1: optionally use spreadsheet-style image creative prompts (Creative ad images sheet)
|
| 705 |
+
visual_scene_description = image_brief
|
| 706 |
+
if niche == "glp1":
|
| 707 |
+
niche_data_img = self._get_niche_data(niche)
|
| 708 |
+
creative_prompts = niche_data_img.get("image_creative_prompts") or []
|
| 709 |
+
if creative_prompts and random.random() < 0.5:
|
| 710 |
+
chosen = random.choice(creative_prompts)
|
| 711 |
+
visual_scene_description = chosen.get("image_prompt", image_brief)
|
| 712 |
# Same framework used for copy is used for visual (no separate container)
|
| 713 |
framework_key = ad_copy.get("framework_key") or ad_copy.get("container_key", "")
|
| 714 |
framework_name = ad_copy.get("framework") or ad_copy.get("container_used", "Standard Ad")
|
|
|
|
| 767 |
niche_image_guidance = (niche_data.get("image_guidance", "") + """
|
| 768 |
|
| 769 |
PEOPLE, FACES, AND CARS ARE OPTIONAL. Only include them when the VISUAL SCENE description explicitly mentions them. Most ad formats are typography, layout, and buttons only.
|
| 770 |
+
NO fake or made-up brand/company names (no gibberish); use generic text only or omit. NO in-car dashboard mockups or screens inside car interiors; stick to the 6 defined ad formats only (official notification, social post, coverage tiers, car brand grid, gift card CTA, savings/urgency)."""
|
| 771 |
)
|
| 772 |
elif niche == "glp1":
|
| 773 |
niche_data = self._get_niche_data(niche)
|
|
|
|
| 775 |
|
| 776 |
CRITICAL - GLP-1 PRODUCT VISIBILITY: The image MUST show at least one of: (1) A GLP-1 medication bottle or injectable pen (e.g. Ozempic, Wegovy, Mounjaro, Zepbound) in the scene, OR (2) The text "GLP-1" or a medication name (Ozempic, Wegovy, Mounjaro, etc.) visible on a label, screen, document, or surface. Do not generate a GLP-1 ad image without the product or name visible."""
|
| 777 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 778 |
else:
|
| 779 |
niche_data = self._get_niche_data(niche)
|
| 780 |
niche_image_guidance = niche_data.get("image_guidance", "")
|
|
|
|
| 941 |
{text_overlay_section}
|
| 942 |
|
| 943 |
=== VISUAL SCENE ===
|
| 944 |
+
{visual_scene_description}
|
| 945 |
|
| 946 |
+
{"=== AUTO INSURANCE AD GRAPHIC (ONLY these 6 creative types) ===" if is_auto_insurance_ad_format else ""}
|
| 947 |
+
{"Follow the VISUAL SCENE description exactly. Use ONLY these 6 formats: (1) official notification, (2) social post card, (3) coverage tier panels, (4) car brand grid, (5) gift card CTA, (6) savings/urgency. No other creative types. Include headline, prices, rates, and CTA or button text as specified. Do NOT add people, faces, or cars unless the VISUAL SCENE explicitly asks for them. Do NOT create in-car dashboard mockups, screens inside car interiors, or headshots on displays. Do NOT include fake or made-up brand/company names; use generic text only (e.g. Compare Providers, See Rates) or omit. Render as a clean, modern ad graphic with clear typography and layout." if is_auto_insurance_ad_format else ""}
|
| 948 |
|
| 949 |
{people_faces_section}
|
| 950 |
|
|
|
|
| 1008 |
- Text should appear ONCE only, not multiple times in the image
|
| 1009 |
|
| 1010 |
=== OUTPUT ===
|
| 1011 |
+
{"Create a scroll-stopping auto insurance ad graphic. Follow the VISUAL SCENE layout exactly: headline, rates/prices, and CTA or buttons as specified. Use only the 6 defined formats (official notification, social post, coverage tiers, car brand grid, gift card CTA, savings/urgency). No other creative types. No fake brand names; no in-car dashboard or screen mockups; no headshots on displays. Clean typography and layout only." if is_auto_insurance_ad_format else f"Create a scroll-stopping image that feels authentic and organic. {'Include the headline text ONCE as specified above - do NOT duplicate it.' if include_text_overlay else 'Focus on the visual scene without text.'} The image should feel like real content - NOT like a designed advertisement."}
|
| 1012 |
|
| 1013 |
CRITICAL REQUIREMENTS:
|
| 1014 |
{"- Use only the defined ad format from VISUAL SCENE. No dashboard/screen-in-car mockups. No made-up brand names. Borders and buttons only where the format specifies." if is_auto_insurance_ad_format else "- NO decorative borders, frames, or boxes"}
|
|
|
|
| 1280 |
|
| 1281 |
# Get power words for copy enhancement
|
| 1282 |
power_words = get_power_words(category=None, count=5)
|
| 1283 |
+
|
|
|
|
|
|
|
|
|
|
| 1284 |
# Get random angle × concept combination (like matrix generation)
|
| 1285 |
combination = matrix_service.generate_single_combination(niche)
|
| 1286 |
angle = combination["angle"]
|
|
|
|
| 1363 |
lighting=lighting,
|
| 1364 |
composition=composition,
|
| 1365 |
visual_style_data=visual_style_data,
|
|
|
|
| 1366 |
trending_context=trending_context if use_trending else None,
|
| 1367 |
)
|
| 1368 |
|
|
|
|
| 1950 |
)
|
| 1951 |
)
|
| 1952 |
|
| 1953 |
+
# Step 3: Creative Director
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1954 |
print(f"🎨 Step 3: Creating {num_strategies} creative strategy/strategies...")
|
| 1955 |
print(f"📋 Parameters: num_strategies={num_strategies}, num_images={num_images}")
|
| 1956 |
creative_strategies = await asyncio.to_thread(
|
|
|
|
| 1962 |
offer=offer,
|
| 1963 |
niche=niche_display,
|
| 1964 |
n=num_strategies,
|
|
|
|
| 1965 |
)
|
| 1966 |
+
|
| 1967 |
if not creative_strategies:
|
| 1968 |
raise ValueError("Creative director returned no strategies")
|
| 1969 |
+
|
| 1970 |
# Limit to requested number of strategies (in case LLM returns more)
|
| 1971 |
creative_strategies = creative_strategies[:num_strategies]
|
| 1972 |
print(f"📊 Using {len(creative_strategies)} strategy/strategies (requested: {num_strategies})")
|
| 1973 |
+
|
| 1974 |
+
# Step 4: Process strategies in parallel (designer + copywriter)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1975 |
print(f"⚡ Step 4: Processing {len(creative_strategies)} strategies in parallel...")
|
| 1976 |
+
|
|
|
|
| 1977 |
strategy_tasks = [
|
| 1978 |
asyncio.to_thread(
|
| 1979 |
third_flow_service.process_strategy,
|
| 1980 |
strategy,
|
| 1981 |
niche=niche_display,
|
|
|
|
| 1982 |
)
|
| 1983 |
+
for strategy in creative_strategies
|
| 1984 |
]
|
| 1985 |
strategy_results = await asyncio.gather(*strategy_tasks)
|
| 1986 |
|
services/third_flow.py
CHANGED
|
@@ -1,8 +1,4 @@
|
|
| 1 |
-
"""
|
| 2 |
-
Extensive Ad Generation Service
|
| 3 |
-
Implements the researcher → creative director → designer → copywriter flow
|
| 4 |
-
Uses OpenAI GPT models with file search for knowledge retrieval
|
| 5 |
-
"""
|
| 6 |
|
| 7 |
import os
|
| 8 |
import sys
|
|
@@ -13,7 +9,6 @@ from pydantic import BaseModel
|
|
| 13 |
# Add parent directory to path for imports
|
| 14 |
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
| 15 |
|
| 16 |
-
from services.motivator import generate_motivators
|
| 17 |
from openai import OpenAI
|
| 18 |
from config import settings
|
| 19 |
|
|
@@ -45,7 +40,6 @@ class CreativeStrategies(BaseModel):
|
|
| 45 |
titleIdeas: str
|
| 46 |
captionIdeas: str
|
| 47 |
bodyIdeas: str
|
| 48 |
-
motivators: list[str] | None = None
|
| 49 |
|
| 50 |
|
| 51 |
class CreativeStrategiesOutput(BaseModel):
|
|
@@ -63,10 +57,10 @@ class CopyWriterOutput(BaseModel):
|
|
| 63 |
|
| 64 |
|
| 65 |
class ThirdFlowService:
|
| 66 |
-
"""
|
| 67 |
-
|
| 68 |
def __init__(self):
|
| 69 |
-
"""
|
| 70 |
self.client = OpenAI(api_key=settings.openai_api_key)
|
| 71 |
self.search_vector_store_id = "vs_691afcc4f8688191b01487b4a8439607"
|
| 72 |
self.ads_vector_store_id = "vs_69609db487048191a1e6b7ba0997ee39"
|
|
@@ -76,19 +70,9 @@ class ThirdFlowService:
|
|
| 76 |
self,
|
| 77 |
target_audience: str,
|
| 78 |
offer: str,
|
| 79 |
-
niche: str = "
|
| 80 |
) -> List[ImageAdEssentials]:
|
| 81 |
-
"""
|
| 82 |
-
Research psychology triggers, angles, and concepts.
|
| 83 |
-
|
| 84 |
-
Args:
|
| 85 |
-
target_audience: Target audience description
|
| 86 |
-
offer: Offer to run
|
| 87 |
-
niche: Niche category
|
| 88 |
-
|
| 89 |
-
Returns:
|
| 90 |
-
List of ImageAdEssentials with psychology triggers, angles, and concepts
|
| 91 |
-
"""
|
| 92 |
messages = [
|
| 93 |
{
|
| 94 |
"role": "system",
|
|
@@ -141,105 +125,13 @@ class ThirdFlowService:
|
|
| 141 |
print(f"Error in researcher: {e}")
|
| 142 |
return []
|
| 143 |
|
| 144 |
-
async def generate_motivators_for_research(
|
| 145 |
-
self,
|
| 146 |
-
researcher_output: List[ImageAdEssentials],
|
| 147 |
-
niche: str,
|
| 148 |
-
target_audience: str | None,
|
| 149 |
-
offer: str | None,
|
| 150 |
-
count_per_item: int = 4,
|
| 151 |
-
) -> list[str]:
|
| 152 |
-
"""
|
| 153 |
-
Generate emotional motivators from researcher output for use in strategy making.
|
| 154 |
-
Produces motivators for each research item (psychology trigger + angles + concepts).
|
| 155 |
-
"""
|
| 156 |
-
if not researcher_output:
|
| 157 |
-
return []
|
| 158 |
-
niche_key = niche.lower().replace(" ", "_").replace("-", "_")
|
| 159 |
-
all_motivators: list[str] = []
|
| 160 |
-
seen: set[str] = set()
|
| 161 |
-
for item in researcher_output:
|
| 162 |
-
trigger = item.phsychologyTriggers or ""
|
| 163 |
-
angles = item.angles or []
|
| 164 |
-
concepts = item.concepts or []
|
| 165 |
-
if not angles:
|
| 166 |
-
angles = [trigger or "benefit"]
|
| 167 |
-
if not concepts:
|
| 168 |
-
concepts = ["authentic story"]
|
| 169 |
-
angle_ctx = {
|
| 170 |
-
"name": angles[0],
|
| 171 |
-
"trigger": trigger,
|
| 172 |
-
"example": angles[0] if len(angles) > 0 else "",
|
| 173 |
-
}
|
| 174 |
-
concept_ctx = {
|
| 175 |
-
"name": concepts[0],
|
| 176 |
-
"structure": concepts[0],
|
| 177 |
-
"visual": ", ".join(concepts[:2]) if concepts else "authentic",
|
| 178 |
-
}
|
| 179 |
-
motivators = await generate_motivators(
|
| 180 |
-
niche=niche_key,
|
| 181 |
-
angle=angle_ctx,
|
| 182 |
-
concept=concept_ctx,
|
| 183 |
-
target_audience=target_audience,
|
| 184 |
-
offer=offer,
|
| 185 |
-
count=count_per_item,
|
| 186 |
-
)
|
| 187 |
-
for m in motivators:
|
| 188 |
-
if m and m.strip() and m.strip().lower() not in seen:
|
| 189 |
-
seen.add(m.strip().lower())
|
| 190 |
-
all_motivators.append(m.strip())
|
| 191 |
-
return all_motivators[:24] # Cap total for prompt size
|
| 192 |
-
|
| 193 |
-
async def generate_motivators_for_strategy(
|
| 194 |
-
self,
|
| 195 |
-
strategy: CreativeStrategies,
|
| 196 |
-
niche: str,
|
| 197 |
-
target_audience: str | None,
|
| 198 |
-
offer: str | None,
|
| 199 |
-
count: int = 6,
|
| 200 |
-
) -> list[str]:
|
| 201 |
-
angle_ctx = {
|
| 202 |
-
"name": strategy.angle,
|
| 203 |
-
"trigger": strategy.phsychologyTrigger,
|
| 204 |
-
"example": strategy.titleIdeas,
|
| 205 |
-
}
|
| 206 |
-
print(f"Generating angle {angle_ctx}")
|
| 207 |
-
|
| 208 |
-
concept_ctx = {
|
| 209 |
-
"name": strategy.concept,
|
| 210 |
-
"structure": strategy.visualDirection,
|
| 211 |
-
"visual": strategy.visualDirection,
|
| 212 |
-
}
|
| 213 |
-
print(f"Generating concept {concept_ctx}")
|
| 214 |
-
|
| 215 |
-
motivators = await generate_motivators(
|
| 216 |
-
niche=niche.lower().replace(" ", "_"),
|
| 217 |
-
angle=angle_ctx,
|
| 218 |
-
concept=concept_ctx,
|
| 219 |
-
target_audience=target_audience,
|
| 220 |
-
offer=offer,
|
| 221 |
-
count=count,
|
| 222 |
-
)
|
| 223 |
-
print(f"Generated motivators: {motivators}")
|
| 224 |
-
|
| 225 |
-
return motivators
|
| 226 |
-
|
| 227 |
def retrieve_search(
|
| 228 |
self,
|
| 229 |
target_audience: str,
|
| 230 |
offer: str,
|
| 231 |
-
niche: str = "
|
| 232 |
) -> str:
|
| 233 |
-
"""
|
| 234 |
-
Retrieve marketing book knowledge using file search with vector stores.
|
| 235 |
-
Args:
|
| 236 |
-
target_audience: Target audience description
|
| 237 |
-
offer: Offer to run
|
| 238 |
-
niche: Niche category
|
| 239 |
-
|
| 240 |
-
Returns:
|
| 241 |
-
Retrieved knowledge text from vector store
|
| 242 |
-
"""
|
| 243 |
try:
|
| 244 |
# Method 1: Try using responses.create
|
| 245 |
if hasattr(self.client, 'responses') and hasattr(self.client.responses, 'create'):
|
|
@@ -335,19 +227,9 @@ class ThirdFlowService:
|
|
| 335 |
self,
|
| 336 |
target_audience: str,
|
| 337 |
offer: str,
|
| 338 |
-
niche: str = "
|
| 339 |
) -> str:
|
| 340 |
-
"""
|
| 341 |
-
Retrieve old ads knowledge using file search with vector stores.
|
| 342 |
-
|
| 343 |
-
Args:
|
| 344 |
-
target_audience: Target audience description
|
| 345 |
-
offer: Offer to run
|
| 346 |
-
niche: Niche category
|
| 347 |
-
|
| 348 |
-
Returns:
|
| 349 |
-
Retrieved ads knowledge text from vector store
|
| 350 |
-
"""
|
| 351 |
try:
|
| 352 |
# Method 1: Try using responses.create
|
| 353 |
if hasattr(self.client, 'responses') and hasattr(self.client.responses, 'create'):
|
|
@@ -446,26 +328,10 @@ class ThirdFlowService:
|
|
| 446 |
ads_knowledge: str,
|
| 447 |
target_audience: str,
|
| 448 |
offer: str,
|
| 449 |
-
niche: str = "
|
| 450 |
n: int = 5,
|
| 451 |
-
motivators: List[str] | None = None,
|
| 452 |
) -> List[CreativeStrategies]:
|
| 453 |
-
"""
|
| 454 |
-
Create creative strategies based on research.
|
| 455 |
-
|
| 456 |
-
Args:
|
| 457 |
-
researcher_output: Output from researcher function
|
| 458 |
-
book_knowledge: Knowledge from marketing books
|
| 459 |
-
ads_knowledge: Knowledge from old ads
|
| 460 |
-
target_audience: Target audience description
|
| 461 |
-
offer: Offer to run
|
| 462 |
-
niche: Niche category
|
| 463 |
-
n: Number of strategies to generate
|
| 464 |
-
motivators: Emotional motivators (customer's internal voice) to weave into strategies
|
| 465 |
-
|
| 466 |
-
Returns:
|
| 467 |
-
List of CreativeStrategies
|
| 468 |
-
"""
|
| 469 |
# Convert researcher_output to string for prompt
|
| 470 |
researcher_str = "\n".join([
|
| 471 |
f"Psychology Triggers: {item.phsychologyTriggers}\n"
|
|
@@ -473,19 +339,7 @@ class ThirdFlowService:
|
|
| 473 |
f"Concepts: {', '.join(item.concepts)}"
|
| 474 |
for item in researcher_output
|
| 475 |
])
|
| 476 |
-
|
| 477 |
-
if motivators:
|
| 478 |
-
motivators_str = "\n".join(f"- {m}" for m in motivators[:20])
|
| 479 |
-
motivators_block = f"""
|
| 480 |
-
EMOTIONAL MOTIVATORS (customer's internal voice - these MUST drive each strategy):
|
| 481 |
-
{motivators_str}
|
| 482 |
-
|
| 483 |
-
CRITICAL: Each strategy MUST be built around ONE motivator. The motivator is the emotional core—everything else (visual direction, title ideas, body ideas, text overlay) must flow from it.
|
| 484 |
-
- visualDirection: Describe a scene that VISUALLY EXPRESSES the motivator (e.g., fear of loss → family protecting documents; trust → community/social proof; unpreparedness → urgency/planning)
|
| 485 |
-
- titleIdeas: Headlines that echo or evoke the motivator's emotional truth
|
| 486 |
-
- text (if any): Overlay text that hints at the motivator without being on-the-nose
|
| 487 |
-
Assign the chosen motivator to the motivators field. The motivator is non-negotiable—it defines the entire creative."""
|
| 488 |
-
|
| 489 |
messages = [
|
| 490 |
{
|
| 491 |
"role": "system",
|
|
@@ -522,9 +376,8 @@ Assign the chosen motivator to the motivators field. The motivator is non-negoti
|
|
| 522 |
Niche: {niche}
|
| 523 |
Offer to run: {offer}
|
| 524 |
Target Audience: {target_audience}
|
| 525 |
-
|
| 526 |
-
|
| 527 |
-
Provide the different creative strategies based on the given input. For each strategy, include in the motivators field the motivator(s) that best fit that strategy."""
|
| 528 |
}
|
| 529 |
]
|
| 530 |
}
|
|
@@ -547,48 +400,10 @@ Assign the chosen motivator to the motivators field. The motivator is non-negoti
|
|
| 547 |
print(f"Error in creative_director: {e}")
|
| 548 |
return []
|
| 549 |
|
| 550 |
-
def creative_designer(self, creative_strategy: CreativeStrategies, niche: str = "
|
| 551 |
-
"""
|
| 552 |
-
|
| 553 |
-
|
| 554 |
-
Args:
|
| 555 |
-
creative_strategy: Creative strategy object
|
| 556 |
-
niche: Niche category (Home Insurance or GLP-1)
|
| 557 |
-
|
| 558 |
-
Returns:
|
| 559 |
-
Image generation prompt
|
| 560 |
-
"""
|
| 561 |
-
# Import niche visual guidance
|
| 562 |
-
from data.visuals import get_niche_visual_guidance
|
| 563 |
-
|
| 564 |
-
# Get niche-specific visual guidance
|
| 565 |
-
niche_lower = niche.lower().replace(" ", "_").replace("-", "_")
|
| 566 |
-
niche_guidance_data = get_niche_visual_guidance(niche_lower)
|
| 567 |
-
|
| 568 |
-
# Build niche-specific guidance text
|
| 569 |
-
if niche_guidance_data:
|
| 570 |
-
niche_guidance = f"""
|
| 571 |
-
NICHE-SPECIFIC REQUIREMENTS ({niche}):
|
| 572 |
-
SUBJECTS TO INCLUDE: {', '.join(niche_guidance_data.get('subjects', []))}
|
| 573 |
-
PROPS TO INCLUDE: {', '.join(niche_guidance_data.get('props', []))}
|
| 574 |
-
AVOID: {', '.join(niche_guidance_data.get('avoid', []))}
|
| 575 |
-
COLOR PREFERENCE: {niche_guidance_data.get('color_preference', 'balanced')}
|
| 576 |
-
|
| 577 |
-
CRITICAL: The image MUST be appropriate for {niche} niche.
|
| 578 |
-
"""
|
| 579 |
-
else:
|
| 580 |
-
niche_guidance = f"""
|
| 581 |
-
NICHE-SPECIFIC REQUIREMENTS ({niche}):
|
| 582 |
-
CRITICAL: The image MUST be appropriate for {niche} niche.
|
| 583 |
-
"""
|
| 584 |
-
|
| 585 |
-
motivator_block = ""
|
| 586 |
-
if selected_motivator:
|
| 587 |
-
motivator_block = f"""
|
| 588 |
-
PRIMARY DRIVER - CORE MOTIVATOR (the image MUST visually express this emotion):
|
| 589 |
-
"{selected_motivator}"
|
| 590 |
-
|
| 591 |
-
The motivator is the MAIN creative driver. The image must make a viewer FEEL this emotional truth through composition, expressions, setting, and props. Every element in the image should support conveying this motivator. Do NOT produce a generic scene—the scene must be specifically designed to evoke this emotional response."""
|
| 592 |
|
| 593 |
text_overlay = creative_strategy.text.textToBeWrittern if creative_strategy.text and creative_strategy.text.textToBeWrittern not in (None, "None", "NA", "") else "No text overlay"
|
| 594 |
strategy_str = f"""Psychology Trigger: {creative_strategy.phsychologyTrigger}
|
|
@@ -597,7 +412,6 @@ The motivator is the MAIN creative driver. The image must make a viewer FEEL thi
|
|
| 597 |
Text: {text_overlay}
|
| 598 |
CTA: {creative_strategy.cta}
|
| 599 |
Visual Direction: {creative_strategy.visualDirection}
|
| 600 |
-
{motivator_block}
|
| 601 |
"""
|
| 602 |
|
| 603 |
messages = [
|
|
@@ -610,13 +424,14 @@ The motivator is the MAIN creative driver. The image must make a viewer FEEL thi
|
|
| 610 |
Nano Banana image model will be used to generate the images
|
| 611 |
Affiliate marketing is a performance-based model where you promote someone else's product or service and earn a commission for each qualified action (click, lead, or sale).
|
| 612 |
In affiliate marketing 'Low-production, realistic images often outperform studio creatives' runs most.
|
| 613 |
-
|
| 614 |
-
If
|
| 615 |
-
|
| 616 |
-
|
| 617 |
-
|
|
|
|
| 618 |
{niche_guidance}
|
| 619 |
-
|
| 620 |
CRITICAL: If the image includes people or faces, ensure they look like real, original people with:
|
| 621 |
- Photorealistic faces with natural skin texture, visible pores, and realistic skin imperfections
|
| 622 |
- Natural facial asymmetry (no perfectly symmetrical faces)
|
|
@@ -637,7 +452,7 @@ The motivator is the MAIN creative driver. The image must make a viewer FEEL thi
|
|
| 637 |
"type": "text",
|
| 638 |
"text": f"""Following is the creative strategy:
|
| 639 |
{strategy_str}
|
| 640 |
-
Provide the image prompt. The prompt MUST lead with the
|
| 641 |
}
|
| 642 |
]
|
| 643 |
}
|
|
@@ -664,10 +479,7 @@ The motivator is the MAIN creative driver. The image must make a viewer FEEL thi
|
|
| 664 |
return ""
|
| 665 |
|
| 666 |
def _refine_prompt_for_affiliate(self, prompt: str, niche: str) -> str:
|
| 667 |
-
"""
|
| 668 |
-
Refine GPT-generated prompt for affiliate marketing creatives.
|
| 669 |
-
Fixes illogical elements, wrong demographics, and ensures authenticity.
|
| 670 |
-
"""
|
| 671 |
import re
|
| 672 |
|
| 673 |
if not prompt:
|
|
@@ -710,24 +522,8 @@ The motivator is the MAIN creative driver. The image must make a viewer FEEL thi
|
|
| 710 |
|
| 711 |
return prompt.strip()
|
| 712 |
|
| 713 |
-
def copy_writer(self, creative_strategy: CreativeStrategies
|
| 714 |
-
"""
|
| 715 |
-
Generate ad copy from creative strategy.
|
| 716 |
-
|
| 717 |
-
Args:
|
| 718 |
-
creative_strategy: Creative strategy object
|
| 719 |
-
|
| 720 |
-
Returns:
|
| 721 |
-
CopyWriterOutput with title, body, and description
|
| 722 |
-
"""
|
| 723 |
-
|
| 724 |
-
motivator_block = ""
|
| 725 |
-
if selected_motivator:
|
| 726 |
-
motivator_block = f"""
|
| 727 |
-
PRIMARY DRIVER - CORE MOTIVATOR (the copy MUST speak directly to this emotional truth):
|
| 728 |
-
"{selected_motivator}"
|
| 729 |
-
|
| 730 |
-
The motivator is the emotional core of the ad. The title should interrupt with this feeling; the body should acknowledge and address it; the description should complete the thought. The reader must feel you understand their internal voice."""
|
| 731 |
|
| 732 |
strategy_str = f"""Psychology Trigger: {creative_strategy.phsychologyTrigger}
|
| 733 |
Angle: {creative_strategy.angle}
|
|
@@ -736,7 +532,6 @@ The motivator is the emotional core of the ad. The title should interrupt with t
|
|
| 736 |
Title Ideas: {creative_strategy.titleIdeas}
|
| 737 |
Caption Ideas: {creative_strategy.captionIdeas}
|
| 738 |
Body Ideas: {creative_strategy.bodyIdeas}
|
| 739 |
-
{motivator_block}
|
| 740 |
"""
|
| 741 |
|
| 742 |
messages = [
|
|
@@ -749,7 +544,7 @@ The motivator is the emotional core of the ad. The title should interrupt with t
|
|
| 749 |
The ad copy must include the title, body and description related to the strategies.
|
| 750 |
Affiliate marketing is a performance-based model where you promote someone else's product or service and earn a commission for each qualified action (click, lead, or sale).
|
| 751 |
|
| 752 |
-
|
| 753 |
|
| 754 |
Role of the Title: Stop the scroll and trigger emotion.
|
| 755 |
1. The title is not for explaining. It's for interrupting attention.
|
|
@@ -780,7 +575,7 @@ The motivator is the emotional core of the ad. The title should interrupt with t
|
|
| 780 |
"text": f"""Following is the creative strategy:
|
| 781 |
{strategy_str}
|
| 782 |
|
| 783 |
-
Provide the title, body, and description.
|
| 784 |
|
| 785 |
IMPORTANT: The body must be 150-250 words long - write a detailed, compelling narrative that tells a story and builds emotional connection with the reader."""
|
| 786 |
}
|
|
@@ -817,21 +612,11 @@ The motivator is the emotional core of the ad. The title should interrupt with t
|
|
| 817 |
def process_strategy(
|
| 818 |
self,
|
| 819 |
creative_strategy: CreativeStrategies,
|
| 820 |
-
niche: str = "
|
| 821 |
-
selected_motivator: str | None = None,
|
| 822 |
) -> tuple[str, str, str, str]:
|
| 823 |
-
"""
|
| 824 |
-
|
| 825 |
-
|
| 826 |
-
Args:
|
| 827 |
-
creative_strategy: Creative strategy object
|
| 828 |
-
niche: Niche category (Home Insurance or GLP-1)
|
| 829 |
-
|
| 830 |
-
Returns:
|
| 831 |
-
Tuple of (prompt, title, body, description)
|
| 832 |
-
"""
|
| 833 |
-
prompt = self.creative_designer(creative_strategy,niche=niche,selected_motivator=selected_motivator)
|
| 834 |
-
ad_copy = self.copy_writer(creative_strategy,selected_motivator=selected_motivator)
|
| 835 |
|
| 836 |
return (
|
| 837 |
prompt,
|
|
@@ -843,58 +628,3 @@ The motivator is the emotional core of the ad. The title should interrupt with t
|
|
| 843 |
|
| 844 |
# Global service instance
|
| 845 |
third_flow_service = ThirdFlowService()
|
| 846 |
-
|
| 847 |
-
if __name__ == "__main__":
|
| 848 |
-
import asyncio
|
| 849 |
-
|
| 850 |
-
# ---- MOCK STRATEGY FOR TESTING ----
|
| 851 |
-
test_strategy = CreativeStrategies(
|
| 852 |
-
phsychologyTrigger="Fear of unexpected financial loss",
|
| 853 |
-
angle="Your home could cost you thousands tomorrow",
|
| 854 |
-
concept="Before vs After comparison",
|
| 855 |
-
text=Text(
|
| 856 |
-
textToBeWrittern="One small issue can turn into a massive repair bill.",
|
| 857 |
-
color="white",
|
| 858 |
-
placement="top"
|
| 859 |
-
),
|
| 860 |
-
cta="Check your coverage now",
|
| 861 |
-
visualDirection="Split image: damaged home vs peaceful protected home",
|
| 862 |
-
titleIdeas="This mistake costs homeowners $12,000",
|
| 863 |
-
captionIdeas="Most homeowners don’t realize this until it’s too late",
|
| 864 |
-
bodyIdeas="A real homeowner story about unexpected damage",
|
| 865 |
-
)
|
| 866 |
-
|
| 867 |
-
async def run_test():
|
| 868 |
-
print("\n========== TEST: GENERATE MOTIVATORS ==========\n")
|
| 869 |
-
motivators = await third_flow_service.generate_motivators_for_strategy(
|
| 870 |
-
strategy=test_strategy,
|
| 871 |
-
niche="home_insurance",
|
| 872 |
-
target_audience="US homeowners 35–65",
|
| 873 |
-
offer="Free home insurance quote",
|
| 874 |
-
)
|
| 875 |
-
|
| 876 |
-
print(motivators)
|
| 877 |
-
|
| 878 |
-
selected_motivator = motivators[0] if motivators else None
|
| 879 |
-
|
| 880 |
-
print("\n========== TEST: PROCESS STRATEGY ==========\n")
|
| 881 |
-
prompt, title, body, description = third_flow_service.process_strategy(
|
| 882 |
-
creative_strategy=test_strategy,
|
| 883 |
-
niche="Home Insurance",
|
| 884 |
-
selected_motivator=selected_motivator,
|
| 885 |
-
)
|
| 886 |
-
|
| 887 |
-
print("\n--- IMAGE PROMPT ---\n")
|
| 888 |
-
print(prompt)
|
| 889 |
-
|
| 890 |
-
print("\n--- TITLE ---\n")
|
| 891 |
-
print(title)
|
| 892 |
-
|
| 893 |
-
print("\n--- BODY ---\n")
|
| 894 |
-
print(body)
|
| 895 |
-
|
| 896 |
-
print("\n--- DESCRIPTION ---\n")
|
| 897 |
-
print(description)
|
| 898 |
-
|
| 899 |
-
asyncio.run(run_test())
|
| 900 |
-
|
|
|
|
| 1 |
+
"""Extensive ad generation: researcher → creative director → designer → copywriter (OpenAI + file search)."""
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
|
| 3 |
import os
|
| 4 |
import sys
|
|
|
|
| 9 |
# Add parent directory to path for imports
|
| 10 |
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
| 11 |
|
|
|
|
| 12 |
from openai import OpenAI
|
| 13 |
from config import settings
|
| 14 |
|
|
|
|
| 40 |
titleIdeas: str
|
| 41 |
captionIdeas: str
|
| 42 |
bodyIdeas: str
|
|
|
|
| 43 |
|
| 44 |
|
| 45 |
class CreativeStrategiesOutput(BaseModel):
|
|
|
|
| 57 |
|
| 58 |
|
| 59 |
class ThirdFlowService:
|
| 60 |
+
"""Extensive ad generation (researcher → creative director → designer → copywriter)."""
|
| 61 |
+
|
| 62 |
def __init__(self):
|
| 63 |
+
"""Set up OpenAI client and vector store IDs."""
|
| 64 |
self.client = OpenAI(api_key=settings.openai_api_key)
|
| 65 |
self.search_vector_store_id = "vs_691afcc4f8688191b01487b4a8439607"
|
| 66 |
self.ads_vector_store_id = "vs_69609db487048191a1e6b7ba0997ee39"
|
|
|
|
| 70 |
self,
|
| 71 |
target_audience: str,
|
| 72 |
offer: str,
|
| 73 |
+
niche: str = ""
|
| 74 |
) -> List[ImageAdEssentials]:
|
| 75 |
+
"""Return psychology triggers, angles, and concepts for niche/offer/audience."""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 76 |
messages = [
|
| 77 |
{
|
| 78 |
"role": "system",
|
|
|
|
| 125 |
print(f"Error in researcher: {e}")
|
| 126 |
return []
|
| 127 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 128 |
def retrieve_search(
|
| 129 |
self,
|
| 130 |
target_audience: str,
|
| 131 |
offer: str,
|
| 132 |
+
niche: str = ""
|
| 133 |
) -> str:
|
| 134 |
+
"""Retrieve marketing knowledge from vector store (file search)."""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 135 |
try:
|
| 136 |
# Method 1: Try using responses.create
|
| 137 |
if hasattr(self.client, 'responses') and hasattr(self.client.responses, 'create'):
|
|
|
|
| 227 |
self,
|
| 228 |
target_audience: str,
|
| 229 |
offer: str,
|
| 230 |
+
niche: str = ""
|
| 231 |
) -> str:
|
| 232 |
+
"""Retrieve ads knowledge from vector store (file search)."""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 233 |
try:
|
| 234 |
# Method 1: Try using responses.create
|
| 235 |
if hasattr(self.client, 'responses') and hasattr(self.client.responses, 'create'):
|
|
|
|
| 328 |
ads_knowledge: str,
|
| 329 |
target_audience: str,
|
| 330 |
offer: str,
|
| 331 |
+
niche: str = "",
|
| 332 |
n: int = 5,
|
|
|
|
| 333 |
) -> List[CreativeStrategies]:
|
| 334 |
+
"""Create creative strategies from research, book knowledge, and ads knowledge."""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 335 |
# Convert researcher_output to string for prompt
|
| 336 |
researcher_str = "\n".join([
|
| 337 |
f"Psychology Triggers: {item.phsychologyTriggers}\n"
|
|
|
|
| 339 |
f"Concepts: {', '.join(item.concepts)}"
|
| 340 |
for item in researcher_output
|
| 341 |
])
|
| 342 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 343 |
messages = [
|
| 344 |
{
|
| 345 |
"role": "system",
|
|
|
|
| 376 |
Niche: {niche}
|
| 377 |
Offer to run: {offer}
|
| 378 |
Target Audience: {target_audience}
|
| 379 |
+
|
| 380 |
+
Provide the different creative strategies based on the given input."""
|
|
|
|
| 381 |
}
|
| 382 |
]
|
| 383 |
}
|
|
|
|
| 400 |
print(f"Error in creative_director: {e}")
|
| 401 |
return []
|
| 402 |
|
| 403 |
+
def creative_designer(self, creative_strategy: CreativeStrategies, niche: str = "") -> str:
|
| 404 |
+
"""Generate image prompt from a creative strategy."""
|
| 405 |
+
niche_lower = niche.lower().replace(" ", "_").replace("-", "_") if niche else ""
|
| 406 |
+
niche_guidance = f"\nThe image must be appropriate for the {niche} niche." if niche else ""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 407 |
|
| 408 |
text_overlay = creative_strategy.text.textToBeWrittern if creative_strategy.text and creative_strategy.text.textToBeWrittern not in (None, "None", "NA", "") else "No text overlay"
|
| 409 |
strategy_str = f"""Psychology Trigger: {creative_strategy.phsychologyTrigger}
|
|
|
|
| 412 |
Text: {text_overlay}
|
| 413 |
CTA: {creative_strategy.cta}
|
| 414 |
Visual Direction: {creative_strategy.visualDirection}
|
|
|
|
| 415 |
"""
|
| 416 |
|
| 417 |
messages = [
|
|
|
|
| 424 |
Nano Banana image model will be used to generate the images
|
| 425 |
Affiliate marketing is a performance-based model where you promote someone else's product or service and earn a commission for each qualified action (click, lead, or sale).
|
| 426 |
In affiliate marketing 'Low-production, realistic images often outperform studio creatives' runs most.
|
| 427 |
+
|
| 428 |
+
If the image looks like it belongs on a stock website, it has failed.
|
| 429 |
+
|
| 430 |
+
The psychology trigger and angle from the strategy are the emotional driver. Structure the prompt so the image directly conveys that emotional truth—the scene, expressions, props, and composition must all serve it. A generic scene is not enough; the strategy must differentiate this creative.
|
| 431 |
+
|
| 432 |
+
For image model here's structure for the prompt: [The Hook - emotion from psychology trigger/angle] + [The Subject] + [The Context/Setting] + [The Technical Polish]
|
| 433 |
{niche_guidance}
|
| 434 |
+
|
| 435 |
CRITICAL: If the image includes people or faces, ensure they look like real, original people with:
|
| 436 |
- Photorealistic faces with natural skin texture, visible pores, and realistic skin imperfections
|
| 437 |
- Natural facial asymmetry (no perfectly symmetrical faces)
|
|
|
|
| 452 |
"type": "text",
|
| 453 |
"text": f"""Following is the creative strategy:
|
| 454 |
{strategy_str}
|
| 455 |
+
Provide the image prompt. The prompt MUST lead with the strategy's emotional hook (psychology trigger and angle)—describe a scene that makes that feeling unmistakable. Then add subject, setting, and technical polish."""
|
| 456 |
}
|
| 457 |
]
|
| 458 |
}
|
|
|
|
| 479 |
return ""
|
| 480 |
|
| 481 |
def _refine_prompt_for_affiliate(self, prompt: str, niche: str) -> str:
|
| 482 |
+
"""Refine prompt for affiliate creatives: fix stock/corporate wording, ensure authenticity."""
|
|
|
|
|
|
|
|
|
|
| 483 |
import re
|
| 484 |
|
| 485 |
if not prompt:
|
|
|
|
| 522 |
|
| 523 |
return prompt.strip()
|
| 524 |
|
| 525 |
+
def copy_writer(self, creative_strategy: CreativeStrategies) -> CopyWriterOutput:
|
| 526 |
+
"""Generate title, body, and description from creative strategy."""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 527 |
|
| 528 |
strategy_str = f"""Psychology Trigger: {creative_strategy.phsychologyTrigger}
|
| 529 |
Angle: {creative_strategy.angle}
|
|
|
|
| 532 |
Title Ideas: {creative_strategy.titleIdeas}
|
| 533 |
Caption Ideas: {creative_strategy.captionIdeas}
|
| 534 |
Body Ideas: {creative_strategy.bodyIdeas}
|
|
|
|
| 535 |
"""
|
| 536 |
|
| 537 |
messages = [
|
|
|
|
| 544 |
The ad copy must include the title, body and description related to the strategies.
|
| 545 |
Affiliate marketing is a performance-based model where you promote someone else's product or service and earn a commission for each qualified action (click, lead, or sale).
|
| 546 |
|
| 547 |
+
The psychology trigger and angle are the emotional driver. The title should evoke that emotion; the body must speak directly to that emotional truth and address it; the description should complete the thought. Do not write generic copy.
|
| 548 |
|
| 549 |
Role of the Title: Stop the scroll and trigger emotion.
|
| 550 |
1. The title is not for explaining. It's for interrupting attention.
|
|
|
|
| 575 |
"text": f"""Following is the creative strategy:
|
| 576 |
{strategy_str}
|
| 577 |
|
| 578 |
+
Provide the title, body, and description. Center the copy on the strategy's psychology trigger and angle—the title should make someone with that thought pause; the body should speak to that specific emotional truth; the description should extend it. Do not write generic copy.
|
| 579 |
|
| 580 |
IMPORTANT: The body must be 150-250 words long - write a detailed, compelling narrative that tells a story and builds emotional connection with the reader."""
|
| 581 |
}
|
|
|
|
| 612 |
def process_strategy(
|
| 613 |
self,
|
| 614 |
creative_strategy: CreativeStrategies,
|
| 615 |
+
niche: str = "",
|
|
|
|
| 616 |
) -> tuple[str, str, str, str]:
|
| 617 |
+
"""Run designer + copywriter on one strategy; return (prompt, title, body, description)."""
|
| 618 |
+
prompt = self.creative_designer(creative_strategy, niche=niche)
|
| 619 |
+
ad_copy = self.copy_writer(creative_strategy)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 620 |
|
| 621 |
return (
|
| 622 |
prompt,
|
|
|
|
| 628 |
|
| 629 |
# Global service instance
|
| 630 |
third_flow_service = ThirdFlowService()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|