sushilideaclan01 commited on
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 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
- if not r2_url and image_bytes:
 
 
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 (hooks for copy; visuals from visual_library only)
 
8
  # ============================================================================
9
 
10
  STRATEGIES = {
11
- "accusation_opener": {
12
- "name": "Accusation Opener",
13
- "description": "Direct accusation - overpaying style",
14
  "hooks": [
15
- "OVERPAYING?",
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
- "State-Approved Savings Program",
115
- "DMV-Verified Rate Reduction",
116
- "Senior Drivers: New Benefit Available",
117
- "Good Drivers: Check Eligibility",
 
118
  "Official Notice: Rate Reduction Program",
119
- "State Insurance Commission Announces Savings",
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
- "fear_based": {
162
- "name": "Fear-Based",
163
- "description": "Fear of accidents and worst-case scenarios",
164
  "hooks": [
165
- "One accident could bankrupt you",
166
- "Are you one fender-bender away from financial ruin?",
167
- "Uninsured? One ticket costs $5,000+",
168
- "Driving without coverage is illegal",
169
- "What if you hit someone tomorrow?",
170
- "Medical bills from one crash: $250,000",
171
- "Your family could lose everything",
172
- "Lawsuit from one accident could take your house",
173
- "Can you afford a $50,000 hospital bill?",
174
- "Police are cracking down on uninsured drivers",
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
- "anchoring": {
294
- "name": "Anchoring",
295
- "description": "Compare high value to low cost",
296
  "hooks": [
297
- "Coverage worth $100,000 for just $37/month",
298
- "Compared to losing everything, $1.25/day is nothing",
299
- "Full protection for less than your coffee habit",
300
- "Your car is worth $30K. Protection is $29/month.",
301
- "Accident cost: $50,000. Coverage cost: $42/month.",
302
- "Insurance: $35/month. Lawsuit: $250,000.",
303
- "Skip one lunch a week. Protect everything you own.",
304
- "The cost of not having insurance: everything",
305
- "$1.00/day protects $100,000 in assets",
306
- "Cheaper than your streaming subscription",
307
  ],
308
  "visual_styles": [],
309
  },
310
- "simplicity": {
311
- "name": "Simplicity & Ease",
312
- "description": "How easy it is to get covered",
313
  "hooks": [
314
- "Get covered in 3 minutes",
315
- "One click. Full protection.",
316
- "No paperwork. No hassle.",
317
- "Quote in 60 seconds",
318
- "The easiest insurance you'll ever buy",
319
- "Set it and forget it protection",
320
- "Online in minutes, protected for years",
321
- "Skip the agent. Save time and money.",
322
- "Apply from your phone",
323
- "Instant quote, instant coverage",
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
- "comparison_envy": {
330
- "name": "Comparison & Envy",
331
- "description": "Compare to others better protected",
332
  "hooks": [
333
- "Your coworker pays less and gets more coverage",
334
- "Why are smart drivers switching?",
335
- "They're protected. Why aren't you?",
336
- "Same car. Same record. Half the premium.",
337
- "What do they know that you don't?",
338
- "Your friend just saved $900. Your turn.",
339
- "Your colleague's claim was covered. Would yours be?",
340
- "Everyone's switching. What are you waiting for?",
341
- "Don't be the only unprotected driver",
342
- "Your neighbor's accident was covered. Would yours be?",
343
  ],
344
  "visual_styles": [],
345
  },
346
- "transformation": {
347
- "name": "Transformation & Peace",
348
- "description": "Worry to peace transformation",
349
  "hooks": [
350
- "From worried to worry-free in 5 minutes",
351
- "Drive confidently knowing you're covered",
352
- "Finally, peace of mind on the road",
353
- "Stop worrying. Start driving.",
354
- "Imagine never worrying about accidents again",
355
- "The weight off your shoulders",
356
- "From stressed to blessed",
357
- "Drive your life. We'll protect your car.",
358
- "Worry-free driving starts here",
359
- "Breathe easy. You're protected.",
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
- # table/infographic, before-after, coverage tiers, car brand grid, gift card CTA, savings/urgency.
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
- "documentary-candid", # Documentary photography style
449
- "vintage-authentic", # Vintage/retro aesthetic
450
- "proof-testimonial", # Testimonial/evidence style
451
- "ui-screenshot", # Native app interface style
452
- "official-institutional", # Official/document style
453
- "warm-nostalgic", # Warm, nostalgic tones
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 ad-format creatives: official notification, social post embed, rate table, before/after, coverage tiers, car brand grid, gift card CTA, savings/urgency.
 
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, seniors/rate table, before/after split, coverage tier panels, car brand grid, gift card CTA, savings/urgency. No other layouts (e.g. no in-car dashboard or screen-in-car mockups).
530
- - Include headline, prices/rates, and CTA or buttons as specified in the format. Clean, modern layout; clear typography; high contrast where appropriate.
531
- - People, faces, and cars are OPTIONAL: only when the format explicitly calls for them (e.g. split car in before/after, profile in social post, car logos in grid). Most formats are layout and text only.
532
- - NO fake or made-up brand/company names (no gibberish); use generic labels only (e.g. Compare Providers, See Rates) or omit. NO in-car dashboard mockups, car interior screens, or headshots on displays.
533
- - AVOID: documentary/lifestyle photography; crash or fear imagery; generic stock scenes; adding people or vehicles when the format does not specify them.
534
  """,
535
  "image_niche_guidance_short": """
536
- NICHE: Auto Insurance (ad-format graphics only)
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?.image_url && (
346
- <img
347
- src={result.corrected_image.image_url}
348
- alt="Corrected"
349
- className="w-full h-full object-cover"
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={result.regenerated_image?.image_url || ""}
 
 
 
 
 
 
 
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" ? result?.regenerated_image?.image_url : (result?.original_image_url || ad?.r2_url || ad?.image_url))) ? (
 
 
 
 
 
 
 
 
 
429
  <img
430
- src={(selectedImage === "new" ? result?.regenerated_image?.image_url : (result?.original_image_url || ad?.r2_url || ad?.image_url))!}
 
 
 
 
 
 
 
 
 
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 (niche-specific for auto_insurance ad formats)
340
  auto_insurance_strategy_categories = {
341
- "accusation_opener": ["savings_urgency_style", "gift_card_cta_style"],
342
- "curiosity_gap": ["social_post_style", "coverage_tiers_style"],
343
- "specific_price_anchor": ["coverage_tiers_style", "official_notification_style", "car_brand_grid_style"],
344
- "before_after_proof": ["before_after_style", "savings_urgency_style"],
345
- "quiz_interactive": ["car_brand_grid_style", "coverage_tiers_style", "seniors_table_style"],
346
- "authority_transfer": ["official_notification_style", "social_post_style"],
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, rate/seniors table, before/after split (price boxes + split car if any), coverage tier panels, car brand grid, gift card CTA, or savings/urgency (yellow, CONTACT US). 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 (e.g. OVERPAYING not OVERDRPAYING); no gibberish.
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 8 defined ad formats only."""
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
- {image_brief}
960
 
961
- {"=== AUTO INSURANCE AD GRAPHIC (rate card / comparison / CTA layout) ===" if is_auto_insurance_ad_format else ""}
962
- {"Follow the VISUAL SCENE description exactly. Use ONLY these defined formats: official notification, social post card, rate/seniors table, before/after split, coverage tier panels, car brand grid, gift card CTA, savings/urgency. 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 (no gibberish like Alcata, MiCass, etc.); use generic text only (e.g. 'Compare Providers', 'See Rates') or omit brand names. Render as a clean, modern ad graphic with clear typography and layout." if is_auto_insurance_ad_format else ""}
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 8 defined formats. 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."}
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 2b: Generate motivators from research for strategy making
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 3b: Get motivator per strategy (from strategy if assigned by director, else generate)
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 idx, strategy in enumerate(creative_strategies)
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
- """Service for extensive ad generation."""
67
-
68
  def __init__(self):
69
- """Initialize OpenAI client and configuration."""
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 = "Home Insurance"
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 = "Home Insurance"
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 = "Home Insurance"
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 = "Home Insurance",
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
- motivators_block = ""
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
- {motivators_block}
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 = "Home Insurance",selected_motivator: str | None = None) -> str:
551
- """
552
- Generate image prompt from creative strategy.
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 a CORE MOTIVATOR is provided, it is the PRIMARY driver. Structure the prompt so the image directly conveys that emotional truth—the scene, expressions, props, and composition must all serve that motivator. A generic family-or-house image is not enough; the motivator must differentiate this creative.
615
-
616
- For image model here's structure for the prompt: [The Hook - emotion/motivator] + [The Subject] + [The Context/Setting] + [The Technical Polish]
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 motivator's emotion—describe a scene that makes the motivator's feeling unmistakable. Then add subject, setting, and technical polish. Make sure the prompt follows the NICHE-SPECIFIC REQUIREMENTS above."""
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,selected_motivator: str | None = None) -> CopyWriterOutput:
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
- If a CORE MOTIVATOR is provided, it is the PRIMARY driver. The title should evoke that motivator; the body must speak directly to that emotional truth and address it; the description should complete the thought. The motivator differentiates this ad—do not write generic copy.
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. If a CORE MOTIVATOR is present, center the copy on it—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 insurance copy; the motivator must be unmistakably reflected.
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 = "Home Insurance",
821
- selected_motivator: str | None = None,
822
  ) -> tuple[str, str, str, str]:
823
- """
824
- Process a single creative strategy to generate prompt and copy.
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()