sushilideaclan01 commited on
Commit
026f283
·
1 Parent(s): 4a56a0b

add new things in the extensive flow

Browse files
api/routers/extensive.py CHANGED
@@ -5,7 +5,14 @@ import uuid
5
  from typing import Dict, Any, Optional
6
  from fastapi import APIRouter, HTTPException, Depends
7
 
8
- from api.schemas import ExtensiveGenerateRequest, ExtensiveJobResponse, BatchResponse
 
 
 
 
 
 
 
9
  from services.generator import ad_generator
10
  from services.auth_dependency import get_current_user
11
 
@@ -23,6 +30,8 @@ async def _run_extensive_job_async(
23
  num_images: int,
24
  image_model: Optional[str],
25
  num_strategies: int,
 
 
26
  ):
27
  """Run extensive generation on the main event loop."""
28
  import logging
@@ -36,6 +45,8 @@ async def _run_extensive_job_async(
36
  image_model=image_model,
37
  num_strategies=num_strategies,
38
  username=username,
 
 
39
  )
40
  _extensive_jobs[job_id]["status"] = "completed"
41
  _extensive_jobs[job_id]["result"] = BatchResponse(count=len(results), ads=results)
@@ -78,6 +89,8 @@ async def generate_extensive(
78
  request.num_images,
79
  request.image_model,
80
  request.num_strategies,
 
 
81
  )
82
  )
83
  return ExtensiveJobResponse(job_id=job_id)
@@ -117,3 +130,52 @@ async def extensive_job_result(
117
  if job["status"] == "failed":
118
  raise HTTPException(status_code=500, detail=job.get("error", "Generation failed"))
119
  return job["result"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
  from typing import Dict, Any, Optional
6
  from fastapi import APIRouter, HTTPException, Depends
7
 
8
+ from api.schemas import (
9
+ ExtensiveGenerateRequest,
10
+ ExtensiveJobResponse,
11
+ BatchResponse,
12
+ InventOnlyRequest,
13
+ InventOnlyResponse,
14
+ InventedEssentialSchema,
15
+ )
16
  from services.generator import ad_generator
17
  from services.auth_dependency import get_current_user
18
 
 
30
  num_images: int,
31
  image_model: Optional[str],
32
  num_strategies: int,
33
+ use_creative_inventor: bool = True,
34
+ trend_context: Optional[str] = None,
35
  ):
36
  """Run extensive generation on the main event loop."""
37
  import logging
 
45
  image_model=image_model,
46
  num_strategies=num_strategies,
47
  username=username,
48
+ use_creative_inventor=use_creative_inventor,
49
+ trend_context=trend_context,
50
  )
51
  _extensive_jobs[job_id]["status"] = "completed"
52
  _extensive_jobs[job_id]["result"] = BatchResponse(count=len(results), ads=results)
 
89
  request.num_images,
90
  request.image_model,
91
  request.num_strategies,
92
+ getattr(request, "use_creative_inventor", True),
93
+ getattr(request, "trend_context", None),
94
  )
95
  )
96
  return ExtensiveJobResponse(job_id=job_id)
 
130
  if job["status"] == "failed":
131
  raise HTTPException(status_code=500, detail=job.get("error", "Generation failed"))
132
  return job["result"]
133
+
134
+
135
+ @router.post("/extensive/invent", response_model=InventOnlyResponse)
136
+ async def invent_only(
137
+ request: InventOnlyRequest,
138
+ username: str = Depends(get_current_user),
139
+ ):
140
+ """
141
+ Invent new ad angles, concepts, visuals, and psychological triggers only (no ad generation).
142
+ Returns structured essentials for review, export, or later use in generation.
143
+ """
144
+ from services.creative_inventor import creative_inventor_service
145
+
146
+ niche_display = request.niche.replace("_", " ").title()
147
+ offer = request.offer or f"Get the best {niche_display} solution"
148
+
149
+ essentials = await asyncio.to_thread(
150
+ creative_inventor_service.invent,
151
+ niche=niche_display,
152
+ offer=offer,
153
+ n=request.n,
154
+ target_audience_hint=request.target_audience,
155
+ trend_context=request.trend_context,
156
+ )
157
+
158
+ schema_list = [
159
+ InventedEssentialSchema(
160
+ psychology_trigger=e.psychology_trigger,
161
+ angles=e.angles,
162
+ concepts=e.concepts,
163
+ visual_directions=e.visual_directions,
164
+ hooks=getattr(e, "hooks", []) or [],
165
+ visual_styles=getattr(e, "visual_styles", []) or [],
166
+ target_audience=getattr(e, "target_audience", "") or "",
167
+ )
168
+ for e in essentials
169
+ ]
170
+
171
+ export_text = None
172
+ if request.export_as_text and essentials:
173
+ export_text = await asyncio.to_thread(
174
+ creative_inventor_service.invent_and_export,
175
+ niche=niche_display,
176
+ offer=offer,
177
+ essentials=essentials,
178
+ target_audience_hint=request.target_audience,
179
+ )
180
+
181
+ return InventOnlyResponse(essentials=schema_list, export_text=export_text)
api/schemas.py CHANGED
@@ -337,6 +337,11 @@ class ExtensiveGenerateRequest(BaseModel):
337
  num_images: int = Field(default=1, ge=1, le=3, description="Number of images per strategy (1-3)")
338
  image_model: Optional[str] = Field(default=None, description="Image generation model to use")
339
  num_strategies: int = Field(default=5, ge=1, le=10, description="Number of creative strategies (1-10)")
 
 
 
 
 
340
 
341
 
342
  class ExtensiveJobResponse(BaseModel):
@@ -345,6 +350,33 @@ class ExtensiveJobResponse(BaseModel):
345
  message: str = "Extensive generation started. Poll /extensive/status/{job_id} for progress."
346
 
347
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
348
  # ----- Creative (upload / analyze / modify) -----
349
  class CreativeAnalysisData(BaseModel):
350
  """Structured analysis of a creative."""
 
337
  num_images: int = Field(default=1, ge=1, le=3, description="Number of images per strategy (1-3)")
338
  image_model: Optional[str] = Field(default=None, description="Image generation model to use")
339
  num_strategies: int = Field(default=5, ge=1, le=10, description="Number of creative strategies (1-10)")
340
+ use_creative_inventor: bool = Field(
341
+ default=True,
342
+ description="If True, invent new angles/concepts/visuals/triggers; if False, use researcher",
343
+ )
344
+ trend_context: Optional[str] = Field(default=None, description="Optional trend or occasion (e.g. New Year, Valentine's)")
345
 
346
 
347
  class ExtensiveJobResponse(BaseModel):
 
350
  message: str = "Extensive generation started. Poll /extensive/status/{job_id} for progress."
351
 
352
 
353
+ class InventOnlyRequest(BaseModel):
354
+ """Request for invent-only (no ad generation)."""
355
+ niche: str = Field(description="Niche (e.g. GLP-1, Home Insurance)")
356
+ target_audience: Optional[str] = Field(default=None, description="Target audience")
357
+ offer: Optional[str] = Field(default=None, description="Offer to run")
358
+ n: int = Field(default=5, ge=1, le=15, description="Number of invented essentials")
359
+ trend_context: Optional[str] = Field(default=None, description="Optional trend or occasion")
360
+ export_as_text: bool = Field(default=False, description="If True, return human-readable text; else JSON")
361
+
362
+
363
+ class InventedEssentialSchema(BaseModel):
364
+ """One invented creative essential (for API response)."""
365
+ psychology_trigger: str
366
+ angles: List[str]
367
+ concepts: List[str]
368
+ visual_directions: List[str]
369
+ hooks: List[str] = []
370
+ visual_styles: List[str] = []
371
+ target_audience: str = "" # Hyper-specific audience for this essential (AI-decided)
372
+
373
+
374
+ class InventOnlyResponse(BaseModel):
375
+ """Response from invent-only endpoint."""
376
+ essentials: List[InventedEssentialSchema]
377
+ export_text: Optional[str] = None
378
+
379
+
380
  # ----- Creative (upload / analyze / modify) -----
381
  class CreativeAnalysisData(BaseModel):
382
  """Structured analysis of a creative."""
data/auto_insurance.py CHANGED
@@ -8,110 +8,110 @@ Strategies kept for copy (hooks); visuals come from AD_FORMAT_VISUAL_LIBRARY onl
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
- },
114
- }
115
 
116
  # ============================================================================
117
  # SECTION 2: AD FORMAT VISUALS (reference-style ad graphics)
@@ -214,15 +214,15 @@ COPY_TEMPLATES = [
214
  # SECTION 4: AGGREGATED DATA
215
  # ============================================================================
216
 
217
- STRATEGY_NAMES = list(STRATEGIES.keys())
218
 
219
- ALL_HOOKS = []
220
- for strategy in STRATEGIES.values():
221
- ALL_HOOKS.extend(strategy["hooks"])
222
 
223
- ALL_VISUAL_STYLES = []
224
- for strategy in STRATEGIES.values():
225
- ALL_VISUAL_STYLES.extend(strategy["visual_styles"])
226
  # Visuals for images come from visual_library (AD_FORMAT_VISUAL_LIBRARY) only
227
 
228
  # ============================================================================
@@ -233,10 +233,10 @@ def get_niche_data():
233
  """Return all auto insurance data for the generator."""
234
  return {
235
  "niche": "auto_insurance",
236
- "strategies": STRATEGIES,
237
- "all_hooks": ALL_HOOKS,
238
- "all_visual_styles": ALL_VISUAL_STYLES,
239
- "strategy_names": STRATEGY_NAMES,
240
  "creative_directions": CREATIVE_DIRECTIONS,
241
  "visual_moods": VISUAL_MOODS,
242
  "copy_templates": COPY_TEMPLATES,
 
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
+ # },
114
+ # }
115
 
116
  # ============================================================================
117
  # SECTION 2: AD FORMAT VISUALS (reference-style ad graphics)
 
214
  # SECTION 4: AGGREGATED DATA
215
  # ============================================================================
216
 
217
+ # STRATEGY_NAMES = list(STRATEGIES.keys())
218
 
219
+ # ALL_HOOKS = []
220
+ # for strategy in STRATEGIES.values():
221
+ # ALL_HOOKS.extend(strategy["hooks"])
222
 
223
+ # ALL_VISUAL_STYLES = []
224
+ # for strategy in STRATEGIES.values():
225
+ # ALL_VISUAL_STYLES.extend(strategy["visual_styles"])
226
  # Visuals for images come from visual_library (AD_FORMAT_VISUAL_LIBRARY) only
227
 
228
  # ============================================================================
 
233
  """Return all auto insurance data for the generator."""
234
  return {
235
  "niche": "auto_insurance",
236
+ # "strategies": STRATEGIES,
237
+ # "all_hooks": ALL_HOOKS,
238
+ # "all_visual_styles": ALL_VISUAL_STYLES,
239
+ # "strategy_names": STRATEGY_NAMES,
240
  "creative_directions": CREATIVE_DIRECTIONS,
241
  "visual_moods": VISUAL_MOODS,
242
  "copy_templates": COPY_TEMPLATES,
data/ecom_verticals.py ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Ecom verticals for creative variety.
3
+ Angles, concepts, and motivators can be adapted to different verticals to increase diversity.
4
+ """
5
+
6
+ from typing import Dict, List, Any, Optional
7
+ import random
8
+
9
+ # Vertical name -> suggested angle keys (must exist in data.angles ANGLES)
10
+ ECOM_VERTICALS: List[Dict[str, Any]] = [
11
+ {"key": "fashion", "name": "Fashion & Apparel", "angle_hint": "trending_now, confidence_boost, people_like_you, viral_popularity"},
12
+ {"key": "beauty", "name": "Beauty & Skincare", "angle_hint": "pride_self_worth, testimonials, confidence_boost, real_stories"},
13
+ {"key": "supplements", "name": "Supplements & Health", "angle_hint": "expert_backed, relief_escape, modern_solution, stress_relief"},
14
+ {"key": "fitness", "name": "Fitness & Wellness", "angle_hint": "shortcut, pain_point, empowerment, hope_optimism"},
15
+ {"key": "electronics", "name": "Electronics & Tech", "angle_hint": "modern_solution, smart_investment, comparison_logic, fast_instant"},
16
+ {"key": "home_goods", "name": "Home & Living", "angle_hint": "peace_of_mind, save_money, lifestyle_match, risk_free"},
17
+ {"key": "pets", "name": "Pet Products", "angle_hint": "emotional_connection, guilt_responsibility, relief_escape, community_trust"},
18
+ {"key": "food_beverage", "name": "Food & Beverage", "angle_hint": "nostalgia, word_of_mouth, simple_easy, mass_adoption"},
19
+ {"key": "jewelry_accessories", "name": "Jewelry & Accessories", "angle_hint": "pride_self_worth, personal_relevance, lifestyle_match, custom_fit"},
20
+ {"key": "subscription", "name": "Subscription & Memberships", "angle_hint": "save_money, limited_time, community_trust, hassle_free"},
21
+ ]
22
+
23
+ VERTICAL_KEYS = [v["key"] for v in ECOM_VERTICALS]
24
+
25
+
26
+ def get_random_vertical() -> Dict[str, Any]:
27
+ """Return a random ecom vertical for variety."""
28
+ return random.choice(ECOM_VERTICALS)
29
+
30
+
31
+ def get_vertical_by_key(key: str) -> Optional[Dict[str, Any]]:
32
+ """Return vertical by key."""
33
+ for v in ECOM_VERTICALS:
34
+ if v.get("key") == key:
35
+ return v
36
+ return None
37
+
38
+
39
+ def get_verticals_for_prompt(count: int = 5) -> str:
40
+ """Return a comma-separated list of vertical names for use in prompts."""
41
+ chosen = random.sample(ECOM_VERTICALS, min(count, len(ECOM_VERTICALS)))
42
+ return ", ".join(v["name"] for v in chosen)
43
+
44
+
45
+ def get_angle_keys_for_vertical(vertical_key: str) -> List[str]:
46
+ """Return list of angle keys suggested for this ecom vertical (for variety)."""
47
+ v = get_vertical_by_key(vertical_key)
48
+ if not v or "angle_hint" not in v:
49
+ return []
50
+ return [k.strip() for k in v["angle_hint"].split(",")]
data/glp1.py CHANGED
@@ -225,6 +225,242 @@ STRATEGIES = {
225
  ],
226
  },
227
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
228
  # ==========================================================================
229
  # ORIGINAL STRATEGIES (UPDATED WITH VINTAGE VISUAL STYLES)
230
  # ==========================================================================
 
225
  ],
226
  },
227
 
228
+ # ==========================================================================
229
+ # COMPETITOR-DERIVED: New angles, concepts, visuals from high-performing ads
230
+ # ==========================================================================
231
+
232
+ "biology_not_willpower": {
233
+ "name": "Biology Not Willpower",
234
+ "description": "Reframe weight loss from moral failing to biology; validate struggle then introduce GLP-1",
235
+ "hooks": [
236
+ "What If Weight Loss Wasn't About Willpower?",
237
+ "It was never about willpower. It's about biology.",
238
+ "Lazy? Undisciplined? Weak? None of these.",
239
+ "GLP-1 targets hormones, not habits.",
240
+ "Stop blaming willpower. Your biology had other plans.",
241
+ "What if your struggle wasn't a character flaw?",
242
+ "It's about biology. Discover the science.",
243
+ "Willpower failed. Biology doesn't have to.",
244
+ ],
245
+ "visual_styles": [
246
+ "crossed-out negative labels: Lazy, Undisciplined, Weak with None of these",
247
+ "black and white emotional portrait, single tear, hopeful smile",
248
+ "bold text overlay on portrait: What if weight loss wasn't about willpower?",
249
+ "minimal text-focused layout, golden/amber accent, DISCOVER THE SCIENCE CTA",
250
+ ],
251
+ },
252
+
253
+ "last_first_day": {
254
+ "name": "Last First Day",
255
+ "description": "Acknowledge repeated false starts; position today as the final, decisive Day 1",
256
+ "hooks": [
257
+ "This Is Your Last First Day.",
258
+ "MAKE TODAY DAY 1.",
259
+ "How many Day 1s have you had? Make this the last.",
260
+ "Stop starting over. Start finishing.",
261
+ "This time, Day 1 sticks.",
262
+ "Your last first day starts now.",
263
+ "One more Day 1—the one that counts.",
264
+ ],
265
+ "visual_styles": [
266
+ "grid of handwritten DAY 1 entries, most crossed out, one circled with checkmark",
267
+ "multi-colored markers, informal tracker aesthetic, red highlight on chosen day",
268
+ "calendar or habit-tracker layout, authentic handwritten feel",
269
+ ],
270
+ },
271
+
272
+ "skeptic_to_believer": {
273
+ "name": "Skeptic to Believer",
274
+ "description": "Name the objection (scam, doubt) then flip with proof; invite to see the science",
275
+ "hooks": [
276
+ "I Thought GLP-1s Were a Scam. I Was Wrong.",
277
+ "GLP-1 IS A SCAM — with SCAM crossed out",
278
+ "Skeptical? So was I. Then I saw the science.",
279
+ "I didn't believe it either. Here's what changed.",
280
+ "See the science. Decide for yourself.",
281
+ "From skeptic to 30 lbs down. Here's why.",
282
+ ],
283
+ "visual_styles": [
284
+ "notebook or paper with GLP-1 IS A SCAM, SCAM heavily crossed out",
285
+ "flat lay: notebook, GLP-1 vials, coffee mug on wood surface",
286
+ "SEE THE SCIENCE CTA button, teal or blue, clean minimal banner",
287
+ ],
288
+ },
289
+
290
+ "nostalgia_clothing_freedom": {
291
+ "name": "Nostalgia & Clothing Freedom",
292
+ "description": "Goal = reclaim ability to wear what you love; nostalgia for past self, not just weight number",
293
+ "hooks": [
294
+ "Remember When You Could Wear Anything?",
295
+ "GLP-1 could help you fit back into them.",
296
+ "FIT INTO WHAT YOU LOVE.",
297
+ "Back into the clothes you've been saving.",
298
+ "That jacket. Those jeans. You again.",
299
+ "Wear anything. Feel like yourself again.",
300
+ ],
301
+ "visual_styles": [
302
+ "blue jeans with white measuring tape draped on waist, light beige fabric background",
303
+ "aspirational wardrobe item (jeans, dress) with measuring tape, warm intimate setting",
304
+ "minimal product shot, orange/peach CTA, warm neutral palette",
305
+ ],
306
+ },
307
+
308
+ "quality_capped_scarcity": {
309
+ "name": "Quality-Capped Scarcity",
310
+ "description": "Scarcity framed as benefit: we cap enrollment for quality care; progress bar + social proof",
311
+ "hooks": [
312
+ "JANUARY ENROLLMENT 87% FULL.",
313
+ "Spots Are Filling Fast.",
314
+ "We cap monthly enrollment for quality care.",
315
+ "10,000 People Joined This Week. Spots Are Filling Fast.",
316
+ "Limited spots. So we can give you real care.",
317
+ "SECURE YOUR SPOT.",
318
+ ],
319
+ "visual_styles": [
320
+ "progress bar showing 87% full, enrollment or spots language",
321
+ "flip-style counter 10,000+ with progress bar, blue grid or cream background",
322
+ "single CTA button SECURE YOUR SPOT in red, clean hierarchy",
323
+ ],
324
+ },
325
+
326
+ "future_regret_two_paths": {
327
+ "name": "Future Regret & Two Paths",
328
+ "description": "One year from now you'll wish you started today; visual fork: better future vs same regret",
329
+ "hooks": [
330
+ "A Year From Now, You'll Wish You Started Today.",
331
+ "50 lbs lighter. Best year ever. Or same weight. Same regret.",
332
+ "CHOOSE YOUR 2027.",
333
+ "Two paths. One choice. Today.",
334
+ "A year from now: thank yourself or regret waiting.",
335
+ ],
336
+ "visual_styles": [
337
+ "calendar 2026/2027 with diverging paths: green upward arrow vs grey flat line",
338
+ "two outcomes text: 50 lbs lighter / same weight same regret",
339
+ "green CTA CHOOSE YOUR 2027, warm background",
340
+ ],
341
+ },
342
+
343
+ "supplement_fatigue": {
344
+ "name": "Supplement Fatigue",
345
+ "description": "Target people who spent on supplements with little result; contrast clutter vs one solution",
346
+ "hooks": [
347
+ "You Spent $3,000 on Supplements That Did Nothing.",
348
+ "SWITCH TO WHAT WORKS.",
349
+ "Done with pills that promised everything and delivered nothing?",
350
+ "Supplements didn't work. This isn't a supplement.",
351
+ "Stop stacking bottles. One thing that actually works.",
352
+ ],
353
+ "visual_styles": [
354
+ "cluttered supplement bottles and pills around central white card with headline",
355
+ "single blue GLP-1 pen on white vs messy bottles; red problem text, blue solution",
356
+ "flat lay: many open bottles, one pen, SWITCH TO WHAT WORKS CTA",
357
+ ],
358
+ },
359
+
360
+ "mainstream_fomo": {
361
+ "name": "Mainstream FOMO",
362
+ "description": "Future-dated world where everyone is on GLP-1; you're the exception; act now",
363
+ "hooks": [
364
+ "It's 2026. Everyone Is On GLP-1s. Except You.",
365
+ "GET STARTED TODAY.",
366
+ "The weight loss revolution is here. Are you in?",
367
+ "GLP-1 went mainstream. Don't be last.",
368
+ "Everyone's talking about it. Everyone's on it. Except you.",
369
+ ],
370
+ "visual_styles": [
371
+ "newspaper or magazine collage: GLP-1 headlines, Weight Loss Revolution, crumpled paper",
372
+ "serif headline clippings, modern sans-serif overlay for main CTA",
373
+ "red GET STARTED TODAY button on newsprint-style background",
374
+ ],
375
+ },
376
+
377
+ "amazon_delivery": {
378
+ "name": "Amazon-Like Delivery",
379
+ "description": "Prescription GLP-1 with same convenience as Amazon; doorstep, next day, medical box",
380
+ "hooks": [
381
+ "Prescription GLP-1s. Delivered Like Amazon.",
382
+ "Board-certified doctor. Ships next day.",
383
+ "ORDER FOR DELIVERY.",
384
+ "Your prescription. Your doorstep. Next day.",
385
+ "Same convenience as Amazon. For your GLP-1.",
386
+ ],
387
+ "visual_styles": [
388
+ "residential doorstep, blue door, small cardboard box with MEDICAL + label",
389
+ "neutral delivery box, discreet, plants and welcome mat; text overlay with headline",
390
+ "green ORDER FOR DELIVERY CTA, trust and convenience mood",
391
+ ],
392
+ },
393
+
394
+ "daily_cost_compare": {
395
+ "name": "Daily Cost Comparison",
396
+ "description": "Lead with $/day; compare visually to coffee, lunch, other meds to anchor value",
397
+ "hooks": [
398
+ "Start For As Low As $5/Day.",
399
+ "START AT $5/DAY.",
400
+ "Less than your daily coffee. Real results.",
401
+ "$5/day. Coffee costs more. So did those supplements.",
402
+ "The math is simple: $5 a day. 30 lbs in months.",
403
+ ],
404
+ "visual_styles": [
405
+ "five-dollar bill, coffee cup, sandwich, pill bottle with prices; GLP-1 pen centered",
406
+ "comparison grid: $5, $6, $9, $45 next to everyday items, yellow CTA",
407
+ "minimal layout, price tags under each object, START AT $5/DAY button",
408
+ ],
409
+ },
410
+
411
+ "calculator_math": {
412
+ "name": "Calculator Math",
413
+ "description": "Make outcome calculable; show equation on calculator; CTA = see the numbers",
414
+ "hooks": [
415
+ "Lose 2-3 lbs/Week with GLP-1 Meds",
416
+ "FDA-approved. Doctor-supervised.",
417
+ "SEE THE NUMBERS.",
418
+ "2.5 lbs/week x 12 weeks = 30 lbs.",
419
+ "Do the math. Then see the numbers.",
420
+ "The equation that actually adds up.",
421
+ ],
422
+ "visual_styles": [
423
+ "calculator screen showing 2.5 x 12 = 30 or similar equation",
424
+ "dark grey calculator on wood surface, teal/green SEE THE NUMBERS CTA",
425
+ "headline above calculator, credibility line below, single bold CTA",
426
+ ],
427
+ },
428
+
429
+ "no_barriers_transparent_price": {
430
+ "name": "No Barriers + Transparent Price",
431
+ "description": "No doctor visits, no insurance, no BS; one all-in monthly price; flat lay with phone and pen",
432
+ "hooks": [
433
+ "No Doctor Visits. No Insurance. No BS.",
434
+ "$147/mo. That's it.",
435
+ "Prescription weight-loss medications, delivered. GET STARTED ONLINE.",
436
+ "No insurance. No office. No runaround.",
437
+ "One price. Everything included. Get started online.",
438
+ ],
439
+ "visual_styles": [
440
+ "flat lay: smartphone showing simple form, GLP-1 pen, coffee cup on white",
441
+ "phone screen with name/email/phone and Continue, no insurance required",
442
+ "bold black headline, blue GET STARTED ONLINE button, minimal and clean",
443
+ ],
444
+ },
445
+
446
+ "scroll_guilt_low_friction": {
447
+ "name": "Scroll Guilt + Low Friction",
448
+ "description": "Free 2-minute assessment; contrast effort with time already spent scrolling",
449
+ "hooks": [
450
+ "Free 2-Minute Assessment.",
451
+ "Answer a few questions. Get matched with a doctor.",
452
+ "You've spent more time scrolling than this takes.",
453
+ "No commitment. No credit card. 2 minutes.",
454
+ "TAKE FREE ASSESSMENT.",
455
+ "2 minutes. That's it. See if you qualify.",
456
+ ],
457
+ "visual_styles": [
458
+ "digital timer or clock icon showing 2:00, minimal white/off-white background",
459
+ "green TAKE FREE ASSESSMENT button, single accent color, clean hierarchy",
460
+ "phone mock-up with quiz/Start screen, low-commitment reassurance text",
461
+ ],
462
+ },
463
+
464
  # ==========================================================================
465
  # ORIGINAL STRATEGIES (UPDATED WITH VINTAGE VISUAL STYLES)
466
  # ==========================================================================
docs/competitor_ad_analysis.md ADDED
@@ -0,0 +1,171 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Competitor Ad Analysis: What They Do Differently
2
+
3
+ Analysis of 20+ competitor GLP-1/weight-loss ads vs. our current creative system. Focus: **new ad angles, concepts, visuals, and psychological triggers** we’re underusing or missing.
4
+
5
+ ---
6
+
7
+ ## 1. What We Already Do (Brief)
8
+
9
+ Our system already uses:
10
+
11
+ - **Accusation openers** (“Still Overweight?”, “Another Failed Diet?”)
12
+ - **Curiosity gap** (“Thousands Are Losing Weight After Discovering THIS”)
13
+ - **Specific numbers** (lbs lost, timeframes, sizes)
14
+ - **Before/after proof**, **quiz/interactive**, **authority**, **identity**, **FOMO**, **urgency/scarcity**
15
+ - **Future pacing** (“Picture yourself at your goal weight”, “6 months from now…”)
16
+ - **Simplicity** (“No willpower needed”, “No diet. No exercise.”)
17
+ - **Loss aversion** (“Every day you wait is another day wasted”)
18
+
19
+ So the gap isn’t “we don’t have the category” — it’s **how** they execute these ideas and which **new combinations and formats** they use.
20
+
21
+ ---
22
+
23
+ ## 2. New Angles & Concepts They Use (We Don’t or Underuse)
24
+
25
+ ### 2.1 Biology vs. Willpower Reframe
26
+
27
+ - **Them:** “What If Weight Loss Wasn’t About Willpower?” → “It was never about willpower. It’s about biology.”
28
+ Sometimes paired with **crossed-out labels**: “Lazy” / “Undisciplined” / “Weak” → “None of these.”
29
+ - **Us:** We have “If Willpower Has Never Been Enough” and “No willpower needed” but don’t own the **paradigm shift** (willpower = wrong frame; biology = right frame) or the **label-striking** visual.
30
+ - **Takeaway:** Add a dedicated **“biology_not_willpower”** angle: reframe the problem, validate struggle, then introduce GLP-1 as the biological lever. Visual: crossed-out negative labels + “None of these.”
31
+
32
+ ### 2.2 Validated Failure / “Last First Day”
33
+
34
+ - **Them:** Grid of many “DAY 1” entries, most crossed out; one circled/checked. Headline: “This Is Your Last First Day.” CTA: “MAKE TODAY DAY 1.”
35
+ - **Us:** We have future pacing and loss aversion but not the **repeated false starts** metaphor or the “last first day” promise.
36
+ - **Takeaway:** New angle **“last_first_day”**: acknowledge many failed Day 1s, then position this one as the final, decisive start. Visual: handwritten-style grid, crossed-out DAY 1s, one highlighted.
37
+
38
+ ### 2.3 Skepticism-to-Belief (Myth-Bust)
39
+
40
+ - **Them:** “I Thought GLP-1s Were a Scam. I Was Wrong.” Visual: “GLP-1 IS A SCAM” with “SCAM” crossed out. CTA: “SEE THE SCIENCE.”
41
+ - **Us:** We don’t explicitly **name and negate** the “scam” objection or use a first-person “I was wrong” narrative.
42
+ - **Takeaway:** New angle **“skeptic_to_believer”**: lead with the objection, then flip it and invite proof (science). Visual: crossed-out “SCAM” (or similar) on notebook/document.
43
+
44
+ ### 2.4 Nostalgia + “Fit Back Into What You Love”
45
+
46
+ - **Them:** “Remember When You Could Wear Anything?” → “GLP-1 could help you fit back into them.” Visual: jeans + measuring tape on fabric. CTA: “FIT INTO WHAT YOU LOVE.”
47
+ - **Us:** Nostalgia exists in the generic angles framework but isn’t applied to GLP-1 as **clothing freedom / past self**.
48
+ - **Takeaway:** New angle **“nostalgia_clothing_freedom”**: goal = reclaim ability to wear what you want, not just “lose weight.” Visual: aspirational jeans/wardrobe + measuring tape, warm and personal.
49
+
50
+ ### 2.5 Justified Scarcity (Quality, Not Manipulation)
51
+
52
+ - **Them:** “We cap monthly enrollment for quality care.” Progress bar “87% FULL” + “10,000+ joined” + “Spots Are Filling Fast.”
53
+ - **Us:** We have urgency/scarcity and countdowns but not **justified** scarcity (we limit to protect quality) or **progress bar + social proof** in one unit.
54
+ - **Takeaway:** New angle **“quality_capped_scarcity”**: scarcity framed as **benefit** (better care). Visual: progress bar (e.g. 87% full) + big social proof number (10,000+) in one layout.
55
+
56
+ ### 2.6 Future Regret (One Year From Now)
57
+
58
+ - **Them:** “A Year From Now, You’ll Wish You Started Today.” Two paths: green upward “50 lbs lighter. Best year ever.” vs. grey flat “Same weight. Same regret.” Calendar 2026/2027. CTA: “CHOOSE YOUR 2027.”
59
+ - **Us:** We have future pacing and loss aversion but not the **explicit one-year regret** or **two-path choice** visual.
60
+ - **Takeaway:** Strengthen **future_pacing** with **regret framing** and **two-path visual**: “A year from now you’ll wish you started today” + diverging paths (positive vs. stagnant).
61
+
62
+ ### 2.7 Scroll Guilt / Opportunity Cost of Time
63
+
64
+ - **Them:** “You’ve spent more time scrolling than this takes.” Under a “Free 2-Minute Assessment” and “Answer a few questions. Get matched with a doctor.”
65
+ - **Us:** We don’t use **contrast with current behavior** (scrolling) to make the ask feel tiny.
66
+ - **Takeaway:** New micro-angle for **low-friction** flows (quiz/assessment): reframe 2 minutes as less than the time they’re already wasting. Copy line: “You’ve spent more time scrolling than this takes.”
67
+
68
+ ### 2.8 Supplements Wasted Money → Switch to What Works
69
+
70
+ - **Them:** “You Spent $3,000 on Supplements That Did Nothing.” Visual: cluttered supplement bottles vs. one clean GLP-1 pen. CTA: “SWITCH TO WHAT WORKS.”
71
+ - **Us:** We don’t target **disillusioned supplement buyers** or use **quantified wasted spend** + visual clutter vs. single solution.
72
+ - **Takeaway:** New angle **“supplement_fatigue”**: speak to people who’ve spent heavily on supplements with little result. Visual: messy supplements vs. one medical device/pen; headline with specific $ (e.g. $3,000).
73
+
74
+ ### 2.9 “Everyone Is On GLP-1s. Except You.” (FOMO + Future Date)
75
+
76
+ - **Them:** “It’s 2026. Everyone Is On GLP-1s. Except You.” Newspaper-style headlines (GLP-1 goes mainstream, weight loss revolution). CTA: “GET STARTED TODAY.”
77
+ - **Us:** We have FOMO (“Everyone is losing weight except you”) but not **future-dated mainstreaming** or **news/editorial** visual treatment.
78
+ - **Takeaway:** Add **“mainstream_fomo”**: near-future world where “everyone” is on GLP-1; you’re the exception. Visual: newspaper/magazine collage with GLP-1 headlines.
79
+
80
+ ### 2.10 Amazon-Like Delivery (Convenience as Category)
81
+
82
+ - **Them:** “Prescription GLP-1s. Delivered Like Amazon.” “Board-certified doctor. Ships next day.” Doorstep + “MEDICAL” box.
83
+ - **Us:** We have convenience and “delivered” but don’t **anchor to Amazon** or **doorstep + medical box** as the hero visual.
84
+ - **Takeaway:** New angle **“amazon_delivery”**: same convenience as Amazon, but for prescription GLP-1. Visual: doorstep, neutral “MEDICAL” box, no product branding on box.
85
+
86
+ ### 2.11 Daily Cost Anchoring ($/Day vs. Coffee, Lunch)
87
+
88
+ - **Them:** “Start For As Low As $5/Day.” Comparisons: $5 bill, $9 sandwich, $6 coffee, $45 pill bottle. GLP-1 pen. CTA: “START AT $5/DAY.”
89
+ - **Us:** We use price and value but not **per-day framing** or **visual comparison to daily purchases** (coffee, lunch, other meds).
90
+ - **Takeaway:** New angle **“daily_cost_compare”**: lead with $/day and show it next to coffee, lunch, other meds. Visual: items + prices in a simple comparison layout.
91
+
92
+ ### 2.12 Calculator / “See the Numbers” (Math as Proof)
93
+
94
+ - **Them:** “Lose 2-3 lbs/Week with GLP-1 Meds” + “FDA-approved. Doctor-supervised.” Calculator screen: “2.5lbs/week x 12 weeks = 30 lbs.” CTA: “SEE THE NUMBERS.”
95
+ - **Us:** We have specific numbers and authority but not **calculator as visual** or **“see the numbers”** as a proof-oriented CTA.
96
+ - **Takeaway:** New angle **“calculator_math”**: make the outcome **calculable** and show it on a calculator. CTA: “SEE THE NUMBERS” (evidence, not just “learn more”).
97
+
98
+ ### 2.13 No Doctor / No Insurance / No BS (Barrier Smash)
99
+
100
+ - **Them:** “No Doctor Visits. No Insurance. No BS.” “$147/mo. That’s it.” Phone + GLP-1 pen + coffee. “GET STARTED ONLINE.”
101
+ - **Us:** We have simplicity and convenience but not this **triple negative** (no visits, no insurance, no BS) or **single all-in price** as hero.
102
+ - **Takeaway:** New angle **“no_barriers_transparent_price”**: explicitly remove the three biggest barriers; one clear monthly price. Visual: flat lay with phone, pen, simple UI.
103
+
104
+ ### 2.14 Free 2-Minute Assessment (Hyper-Specific Low Friction)
105
+
106
+ - **Them:** “Free 2-Minute Assessment.” “Answer a few questions. Get matched with a doctor.” “No commitment. No credit card.” Timer icon “2:00.” CTA: “TAKE FREE ASSESSMENT.”
107
+ - **Us:** We have quiz/interactive and “See If You Qualify” but not **exact time** (2 minutes) or **timer visual** to anchor effort.
108
+ - **Takeaway:** Refine **quiz_interactive**: add “2-Minute” (or similar) to headlines and **timer/clock graphic** (e.g. “2:00”) in visuals for assessment ads.
109
+
110
+ ---
111
+
112
+ ## 3. Visual Concepts We’re Underusing
113
+
114
+ | Concept | Them | Us |
115
+ |--------|-----|----|
116
+ | **Progress bar** | 87% full, “Spots filling fast,” “Enrollment capped” | Countdown, “limited spots” text only |
117
+ | **Flip counter** | “10,000+” in train-departure / flip style | Plain “10,000+” or “thousands” |
118
+ | **Crossed-out text** | “SCAM,” “Lazy,” “Undisciplined,” “Weak” | Not in our visual vocabulary |
119
+ | **Grid of failures** | Many “DAY 1” crossed out, one checked | Not used |
120
+ | **Two-path fork** | Green path up vs. grey path flat, 2026/2027 | Not used |
121
+ | **Clutter vs. one** | Many supplement bottles vs. one pen | Not used |
122
+ | **Calculator** | On-screen equation (2.5×12=30) | Not used |
123
+ | **Doorstep + MEDICAL box** | Neutral box, “MEDICAL +” | Not used |
124
+ | **Everyday price comparison** | $5, $6, $9, $45 next to objects | Not used |
125
+ | **Newspaper/magazine collage** | “2026”, “Revolution,” multiple headlines | Not used |
126
+ | **B&W emotional portrait** | Tear + slight smile, “What if not willpower?” | We use candid/real but not this specific treatment |
127
+ | **Jeans + measuring tape** | Aspirational fit, “fit back into what you love” | We have “old jeans” but not this exact composition |
128
+
129
+ **Action:** Add these as **named visual_styles** in the relevant strategies (e.g. `progress_bar_87_full`, `crossed_out_labels`, `two_path_fork`, `supplements_vs_pen`, `calculator_equation`, `doorstep_medical_box`, `daily_cost_comparison_grid`, `newspaper_collage_2026`, `jeans_measuring_tape`, `timer_2min`).
130
+
131
+ ---
132
+
133
+ ## 4. Psychological Triggers They Double-Down On
134
+
135
+ - **Validation before solution:** “It’s not your fault” (biology, not willpower; “None of these”).
136
+ - **Regret aversion:** “A year from now you’ll wish you started today” + “Same weight. Same regret.”
137
+ - **Justified scarcity:** “We cap for quality” so scarcity = care, not gimmick.
138
+ - **Objection-first:** Name “scam” or “supplements didn’t work,” then resolve.
139
+ - **Effort anchoring:** “2 minutes,” “2:00” icon, “less time than scrolling.”
140
+ - **Belonging:** “Everyone is on GLP-1s. Except you.”
141
+ - **Concrete loss:** “$3,000 on supplements that did nothing.”
142
+ - **Empowerment of choice:** “CHOOSE YOUR 2027,” “MAKE TODAY DAY 1.”
143
+
144
+ We have most of these in abstract form; they’re **more explicit and visual**. Recommendations:
145
+
146
+ - Add **validation** and **regret_aversion** as first-class trigger guidance in prompts.
147
+ - In generator prompts, stress **objection-first** and **effort anchoring** for quiz/assessment flows.
148
+ - Add **justified_scarcity** as a distinct angle from generic urgency/scarcity.
149
+
150
+ ---
151
+
152
+ ## 5. Summary: Gaps and Next Steps
153
+
154
+ | Gap | Action |
155
+ |-----|--------|
156
+ | Biology vs. willpower as paradigm | Add **biology_not_willpower** strategy + crossed-out labels visual |
157
+ | “Last first day” / repeated Day 1s | Add **last_first_day** strategy + grid visual |
158
+ | “I thought it was a scam” | Add **skeptic_to_believer** strategy + crossed-out SCAM visual |
159
+ | Nostalgia + clothing freedom | Add **nostalgia_clothing_freedom** strategy + jeans + measuring tape |
160
+ | Scarcity = quality care | Add **quality_capped_scarcity** + progress bar + 10k social proof visual |
161
+ | One-year regret + two paths | Extend **future_pacing** with regret + two-path visual |
162
+ | Scroll guilt | Add one-liner to quiz/assessment prompts |
163
+ | Supplements wasted $ → switch | Add **supplement_fatigue** strategy + clutter vs. pen visual |
164
+ | “Everyone on GLP-1 except you” + 2026 | Add **mainstream_fomo** + newspaper collage visual |
165
+ | Delivered like Amazon | Add **amazon_delivery** strategy + doorstep MEDICAL box |
166
+ | $/day vs. coffee/lunch | Add **daily_cost_compare** strategy + comparison grid visual |
167
+ | Calculator = 30 lbs | Add **calculator_math** strategy + calculator visual |
168
+ | No doctor / no insurance / no BS | Add **no_barriers_transparent_price** strategy |
169
+ | 2-minute + timer icon | Refine quiz strategy with “2-Minute” + timer visual |
170
+
171
+ Implementing these in `data/glp1.py` (new strategy blocks + hooks + visual_styles) and in `generator_prompts.py` (where relevant) will align our system with these competitor angles, concepts, visuals, and psychological triggers.
docs/creative_inventor.md ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Creative Inventor: Self-Generating Ad Angles, Concepts, Visuals & Triggers
2
+
3
+ The **Creative Inventor** is a system that generates new ad angles, concepts, visual directions, and psychological triggers by itself—either as part of the extensive flow or standalone for export/review.
4
+
5
+ ## How It Works
6
+
7
+ 1. **Inputs**: Niche, target audience, offer; optional trend context, competitor insights, or existing strategy reference (to diverge from).
8
+ 2. **LLM**: A single structured call invents `n` “essentials,” each with:
9
+ - **Psychology trigger** – e.g. validation before solution, future regret, justified scarcity.
10
+ - **Angles** – reasons to care; reframes (e.g. biology not willpower, last first day).
11
+ - **Concepts** – execution/storyline (e.g. crossed-out labels, progress bar + social proof).
12
+ - **Visual directions** – concrete visual ideas (e.g. “progress bar 87% full”, “grid of crossed-out DAY 1s”).
13
+ - **Hooks** – scroll-stopping headline phrases.
14
+ - **Visual styles** – short scene descriptions for image prompts.
15
+ 3. **Output**: List of `InventedEssential` used by the extensive flow or returned by the API.
16
+
17
+ ## Where It’s Used
18
+
19
+ ### 1. Inside the Extensive Flow (default)
20
+
21
+ When you run **extensive generation** with `use_creative_inventor=True` (default):
22
+
23
+ - **Step 1** is “Invent new ad angles, concepts, visuals, and psychological triggers” (Creative Inventor).
24
+ - The invented essentials are converted to the same shape as the researcher output and passed to the **creative director**.
25
+ - Rest of the flow is unchanged: retrieve knowledge → creative director → designer → copywriter → image generation.
26
+
27
+ So the extensive flow can **create new angles/concepts/visuals/triggers by itself** instead of relying only on the fixed researcher step.
28
+
29
+ ### 2. Standalone “Invent Only”
30
+
31
+ - **API**: `POST /extensive/invent`
32
+ Body: `niche`, `target_audience`, `offer`, `n`, optional `trend_context`, `export_as_text`.
33
+ Returns: `essentials` (JSON) and optionally `export_text` (human-readable markdown).
34
+ - **Code**: `creative_inventor_service.invent(...)` and `creative_inventor_service.invent_and_export(...)` in `services/creative_inventor.py`.
35
+
36
+ Use this to generate ideas for review, export to docs, or to feed into other tools without generating ads.
37
+
38
+ ### 3. Researcher-Only Flow
39
+
40
+ Set `use_creative_inventor=False` in the extensive generator (or send `use_creative_inventor: false` in the API). Step 1 is then the original **researcher** (psychology triggers, angles, concepts from the existing prompt), and the rest of the pipeline is unchanged.
41
+
42
+ ## API Summary
43
+
44
+ | Endpoint | Purpose |
45
+ |----------|--------|
46
+ | `POST /extensive/generate` | Full extensive run; by default uses Creative Inventor in step 1. Supports `use_creative_inventor`, `trend_context`. |
47
+ | `POST /extensive/invent` | Invent only; returns `essentials` (+ optional `export_text`). No ad generation. |
48
+
49
+ ## Files
50
+
51
+ - **`services/creative_inventor.py`** – `CreativeInventorService`, `InventedEssential`, `invent()`, `invent_and_export()`.
52
+ - **`services/third_flow.py`** – `get_essentials_via_inventor()`, `_invented_to_essentials()`; converts inventor output to `ImageAdEssentials` for the creative director.
53
+ - **`services/generator.py`** – `generate_ad_extensive(..., use_creative_inventor=True, trend_context=...)`; when True, step 1 calls the inventor instead of the researcher.
54
+ - **`api/routers/extensive.py`** – Extensive job run with inventor/researcher switch; `POST /extensive/invent`.
55
+ - **`api/schemas.py`** – `ExtensiveGenerateRequest` (with `use_creative_inventor`, `trend_context`), `InventOnlyRequest`, `InventedEssentialSchema`, `InventOnlyResponse`.
56
+
57
+ ## Optional: Seeding or Constraining the Inventor
58
+
59
+ You can pass:
60
+
61
+ - **`existing_reference`** – Short summary of existing strategies so the inventor can diverge and avoid repeating.
62
+ - **`trend_context`** – e.g. “New Year”, “Valentine’s Day”; used for relevance.
63
+ - **`competitor_insights`** – e.g. notes from competitor ad analysis; used to differentiate.
64
+
65
+ These are supported in `creative_inventor_service.invent(...)` and can be wired through the API or generator if you add the corresponding request fields.
services/creative_inventor.py ADDED
@@ -0,0 +1,160 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Creative Inventor: generates new ad angles, concepts, visuals, and psychological triggers by itself.
3
+ Can run standalone (ideas for review/export) or feed into the extensive flow.
4
+ """
5
+
6
+ from typing import List, Optional
7
+
8
+ from openai import OpenAI
9
+ from pydantic import BaseModel
10
+
11
+ from config import settings
12
+
13
+
14
+ # ---------------------------------------------------------------------------
15
+ # Output models: what the inventor produces
16
+ # ---------------------------------------------------------------------------
17
+
18
+ class InventedEssential(BaseModel):
19
+ """One invented creative essential: trigger + angles + concepts + visual directions + hyper-specific audience."""
20
+ psychology_trigger: str
21
+ angles: List[str]
22
+ concepts: List[str]
23
+ visual_directions: List[str]
24
+ hooks: List[str] = [] # Optional scroll-stopping headlines
25
+ visual_styles: List[str] = [] # Optional scene/prompt-style descriptions
26
+ target_audience: str = "" # Hyper-specific audience for this essential (AI-decided, different per essential)
27
+
28
+
29
+ class InventedCreativeOutput(BaseModel):
30
+ """Structured output from the creative inventor."""
31
+ output: List[InventedEssential]
32
+
33
+
34
+ class CreativeInventorService:
35
+ """
36
+ Invents new ad angles, concepts, visuals, and psychological triggers.
37
+ Use standalone to generate ideas, or feed output into the extensive flow.
38
+ """
39
+
40
+ def __init__(self):
41
+ self.client = OpenAI(api_key=settings.openai_api_key)
42
+ self.model = getattr(settings, "third_flow_model", "gpt-4o")
43
+
44
+ def invent(
45
+ self,
46
+ niche: str,
47
+ offer: str,
48
+ *,
49
+ target_audience_hint: Optional[str] = None,
50
+ existing_reference: Optional[str] = None,
51
+ trend_context: Optional[str] = None,
52
+ competitor_insights: Optional[str] = None,
53
+ n: int = 5,
54
+ ) -> List[InventedEssential]:
55
+ """
56
+ Invent new psychological triggers, angles, concepts, visual directions,
57
+ hooks, visual styles, and a hyper-specific target_audience per essential.
58
+ The AI decides all target audiences; they must be different types (demographic, psychographic, situation).
59
+
60
+ Args:
61
+ niche: e.g. "GLP-1", "Home Insurance"
62
+ offer: what is being promoted
63
+ target_audience_hint: optional broad hint (e.g. "weight loss"); AI still invents distinct hyper-specific audiences per essential
64
+ existing_reference: optional summary of existing strategies (to diverge or avoid repeating)
65
+ trend_context: optional trending topics or occasions
66
+ competitor_insights: optional notes on what competitors are doing
67
+ n: number of invented "essentials" to return (each with its own hyper-specific audience)
68
+
69
+ Returns:
70
+ List of InventedEssential for use in extensive flow or export.
71
+ """
72
+ system_prompt = """You are a creative strategist who invents NEW ad angles, concepts, visuals, psychological triggers, and HYPER-SPECIFIC target audiences for performance and affiliate marketing.
73
+
74
+ Your job is to CREATE novel ideas—not to recycle generic ones. For each "essential" you produce:
75
+ - target_audience: ONE hyper-specific audience for this essential. You MUST invent a different type of audience for each essential. Examples of hyper-specific audiences: "Women 45–55 who tried Ozempic and stalled", "Men 35–45 with 30+ lbs to lose who hate gyms", "Busy moms who yo-yo diet and have given up on willpower", "Women 50+ who've tried everything and feel it's biology not laziness", "Professionals who travel constantly and want no-fuss delivery", "Skeptics who think GLP-1 is a scam and need proof". Mix demographics, psychographics, and situations. No generic "people interested in X".
76
+ - Psychology trigger: the emotional or cognitive lever (e.g. validation before solution, future regret, justified scarcity, objection-first then flip).
77
+ - Angles: the reasons this specific audience should care; reframe the problem or outcome in a fresh way.
78
+ - Concepts: the creative execution or storyline (e.g. crossed-out negative labels, progress bar + social proof, two-path fork).
79
+ - Visual directions: concrete visual ideas (e.g. "progress bar 87% full with 10k counter", "grid of crossed-out DAY 1s").
80
+ - Hooks: 2–4 scroll-stopping headline phrases for that essential and audience.
81
+ - Visual styles: 2–4 short scene/prompt-style descriptions for image generation.
82
+
83
+ Rules:
84
+ - Each essential MUST have a different target_audience. Vary types: age/gender, life situation, mindset, past failures, skepticism, convenience-seekers, etc.
85
+ - Prioritize novelty and specificity. Avoid vague angles or generic audiences.
86
+ - Combine triggers in new ways. Invent visual metaphors that work for the chosen audience.
87
+ - Output exactly the number of "essentials" requested (n). Each essential should feel distinct and speak to a distinctly different audience."""
88
+
89
+ user_parts = [
90
+ f"Niche: {niche}",
91
+ f"Offer: {offer}",
92
+ f"Invent exactly {n} different creative essentials. For EACH essential you MUST set a different hyper-specific target_audience (demographic, psychographic, or situation). Then set psychology_trigger, angles, concepts, visual_directions, hooks, visual_styles for that audience.",
93
+ ]
94
+ if target_audience_hint:
95
+ user_parts.append(f"Broad category hint (invent specific segments within or related to this): {target_audience_hint}")
96
+ if existing_reference:
97
+ user_parts.append(f"Existing strategies to diverge from (do not copy):\n{existing_reference}")
98
+ if trend_context:
99
+ user_parts.append(f"Trend / occasion context (use for relevance):\n{trend_context}")
100
+ if competitor_insights:
101
+ user_parts.append(f"Competitor / market insights (use to differentiate):\n{competitor_insights}")
102
+
103
+ user_content = "\n\n".join(user_parts)
104
+ # Ensure model returns target_audience; fallback for older parsed outputs
105
+ # (InventedEssential now has target_audience with default "")
106
+
107
+ try:
108
+ completion = self.client.beta.chat.completions.parse(
109
+ model=self.model,
110
+ messages=[
111
+ {"role": "system", "content": system_prompt},
112
+ {"role": "user", "content": user_content},
113
+ ],
114
+ response_format=InventedCreativeOutput,
115
+ )
116
+ response = completion.choices[0].message
117
+ if response.parsed and response.parsed.output:
118
+ return response.parsed.output[:n]
119
+ return []
120
+ except Exception as e:
121
+ print(f"CreativeInventor error: {e}")
122
+ return []
123
+
124
+ def invent_and_export(
125
+ self,
126
+ niche: str,
127
+ offer: str,
128
+ essentials: Optional[List["InventedEssential"]] = None,
129
+ target_audience_hint: Optional[str] = None,
130
+ **kwargs,
131
+ ) -> str:
132
+ """
133
+ Invent (if essentials not provided) and return a human-readable text summary for export or review.
134
+ """
135
+ if essentials is None:
136
+ essentials = self.invent(niche=niche, offer=offer, target_audience_hint=target_audience_hint, **kwargs)
137
+ lines = [
138
+ f"# Invented creatives — {niche}" + (f" | {target_audience_hint}" if target_audience_hint else ""),
139
+ "",
140
+ ]
141
+ for i, e in enumerate(essentials, 1):
142
+ aud = getattr(e, "target_audience", "") or ""
143
+ lines.append(f"## Essential {i}: {e.psychology_trigger}" + (f" — {aud}" if aud else ""))
144
+ lines.append("")
145
+ if aud:
146
+ lines.append("**Target audience:** " + aud)
147
+ lines.append("")
148
+ lines.append("**Angles:** " + " | ".join(e.angles))
149
+ lines.append("**Concepts:** " + " | ".join(e.concepts))
150
+ lines.append("**Visual directions:** " + " | ".join(e.visual_directions))
151
+ if e.hooks:
152
+ lines.append("**Hooks:** " + " | ".join(e.hooks))
153
+ if e.visual_styles:
154
+ lines.append("**Visual styles:** " + " | ".join(e.visual_styles))
155
+ lines.append("")
156
+ return "\n".join(lines)
157
+
158
+
159
+ # Singleton for use from third_flow / generator
160
+ creative_inventor_service = CreativeInventorService()
services/generator.py CHANGED
@@ -22,6 +22,7 @@ from data.frameworks import (
22
  get_framework_visual_guidance,
23
  get_frameworks_for_niche,
24
  )
 
25
  from data.hooks import get_power_words, get_random_cta as get_hook_cta, get_random_hook_style
26
  from data.triggers import get_random_trigger, get_trigger_combination, get_triggers_for_niche
27
  from data.visuals import (
@@ -179,13 +180,36 @@ class AdGenerator:
179
 
180
  # --- Niche & strategy ---
181
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
182
  def _get_niche_data(self, niche: str) -> Dict[str, Any]:
183
- """Load data for a specific niche (cached for performance)."""
184
- if niche not in NICHE_DATA:
185
- raise ValueError(f"Unsupported niche: {niche}. Supported: {list(NICHE_DATA.keys())}")
186
- # Use cache to avoid repeated function calls
187
  if niche not in self._niche_data_cache:
188
- self._niche_data_cache[niche] = NICHE_DATA[niche]()
 
 
 
 
189
  return self._niche_data_cache[niche]
190
 
191
  # ========================================================================
@@ -441,7 +465,7 @@ class AdGenerator:
441
  {context}
442
 
443
  Rules:
444
- - Match the niche and tone; avoid generic-only.
445
  - Return ONLY a JSON object with one key "ctas" containing an array of strings. No other text."""
446
 
447
  try:
@@ -560,21 +584,20 @@ CREATIVE DIRECTION: {creative_direction}
560
  CALL-TO-ACTION: {cta}
561
  {trending_section}
562
 
563
- === ANGLE × CONCEPT FRAMEWORK ===
564
- ANGLE: {angle.get('name') if angle else 'N/A'}
565
- - Psychological Trigger: {angle.get('trigger') if angle else 'N/A'}
566
- - Example Hook: "{angle.get('example') if angle else 'N/A'}"
567
- - This angle answers WHY they should care
568
 
569
- CONCEPT: {concept.get('name') if concept else 'N/A'}
570
- - Visual Structure: {concept.get('structure') if concept else 'N/A'}
571
- - Visual Guidance: {concept.get('visual') if concept else 'N/A'}
572
- - This concept defines HOW to show it visually
573
 
574
  {f'=== USER INPUTS ===' if target_audience or offer else ''}
575
  {f'TARGET AUDIENCE: {target_audience}' if target_audience else ''}
576
  {f'OFFER: {offer}' if offer else ''}
577
 
 
 
 
 
578
  === FRAMEWORK VISUAL FORMAT (same framework for copy + image) ===
579
  FRAMEWORK: {framework_data.get('name', framework)}
580
  DESCRIPTION: {framework_data.get('description', '')}
@@ -605,45 +628,37 @@ Incorporate these power words naturally: {', '.join(power_words) if power_words
605
  {niche_guidance}
606
 
607
  {headline_formulas}
 
 
608
  === YOUR MISSION ===
609
- Create a SCROLL-STOPPING Facebook ad for {niche.replace("_", " ").upper()} using the "{angle.get('name') if angle else 'psychological'}" angle and "{concept.get('name') if concept else 'visual'}" concept that looks like organic content, not advertising.
610
 
611
  === OUTPUT REQUIREMENTS ===
612
 
613
  1. HEADLINE (The "Arrest")
614
- - Use the "{angle.get('name') if angle else 'psychological'}" angle to trigger {angle.get('trigger') if angle else 'emotion'}
615
- - MAXIMUM 10 words
616
- - Must create INSTANT pattern interrupt
617
- - You decide whether to include specific numbers/prices based on the angle and what will be most effective. If including prices, make them oddly specific (e.g., "$97.33" not "$100") for believability.
618
- - Include demographic targeting where appropriate
619
- - NO generic phrases - be SPECIFIC and EMOTIONAL
620
 
621
  2. PRIMARY TEXT (The "Agitation")
622
- - 2-3 punchy sentences that AMPLIFY the emotional hook
623
- - Use the "{angle.get('name') if angle else 'psychological'}" angle to deepen emotional connection
624
- - You decide whether to include specific numbers/prices based on the angle and strategy. If including, make them oddly specific for believability.
625
- - Reference the demographic appropriately
626
- - Create urgency
627
- - Make them FEEL the pain or desire
628
 
629
  3. DESCRIPTION (The "Payoff")
630
- - ONE powerful sentence (max 10 words)
631
- - You decide whether to include specific metrics/numbers based on what enhances the message. If including, use oddly specific amounts for believability.
632
- - Create action urgency
633
 
634
  4. IMAGE BRIEF (CRITICAL - must match {framework_data.get('name', framework)} framework style)
635
- - Follow the "{concept.get('name') if concept else 'visual'}" concept: {concept.get('structure') if concept else 'authentic visual'}
636
  - Describe the scene for the {framework_data.get('name', framework)} framework ONLY
637
  - Visual guidance: {get_framework_visual_guidance(framework_data.get('key', ''))}
638
  - The image should look like ORGANIC CONTENT, not an ad
639
- - Include: setting, subjects, props, mood
640
  - Follow framework authenticity tips: {', '.join(framework_data.get('authenticity_tips', [])[:2])}
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
 
648
  === PSYCHOLOGICAL PRINCIPLES ===
649
  - Loss Aversion: Make them feel what they're losing/missing
@@ -654,26 +669,26 @@ Create a SCROLL-STOPPING Facebook ad for {niche.replace("_", " ").upper()} using
654
  - Native Disguise: Content that doesn't look like an ad bypasses filters
655
 
656
  === CRITICAL RULES ===
657
- 1. MATCH THE NICHE: {niche.replace("_", " ").upper()} content only!
658
- 2. Use SPECIFIC numbers from the numbers section above
659
- 3. ALWAYS create curiosity gap with "THIS", "Instead", "After", "Secret"
660
  4. NEVER look like an ad - look like NEWS, PROOF, or UGC
661
  5. Use ACCUSATION framing for maximum impact
662
  6. The image MUST match the {framework_data.get('name', framework)} framework style
663
 
664
  === OUTPUT FORMAT (JSON) ===
665
  {{
666
- "headline": "Your headline using the {angle.get('name') if angle else 'psychological'} angle to trigger {angle.get('trigger') if angle else 'emotion'}",
667
- "primary_text": "Your 2-3 sentence emotional amplification with specific numbers",
668
- "description": "Your one powerful sentence",
669
- "body_story": "A compelling 8-12 sentence STORY that hooks the reader emotionally. Start with a relatable situation or pain point. Build tension gradually. Show the transformation with vivid details. End with hope and a soft CTA. Write in first or second person for intimacy. Make it engaging and detailed enough to fully capture the reader's attention.",
670
- "image_brief": "Detailed description following '{concept.get('name') if concept else 'visual'}' concept and matching {framework_data.get('name', framework)} framework style - organic content feel",
671
  "cta": "{cta}",
672
- "psychological_angle": "{angle.get('name') if angle else 'Primary psychological trigger being used'}",
673
- "why_it_works": "Brief explanation of the psychological mechanism"
674
  }}
675
 
676
- Generate the ad copy now for {niche.replace("_", " ").upper()}. Make it look like ORGANIC CONTENT that triggers IMMEDIATE emotional response."""
677
 
678
  return prompt
679
 
@@ -884,8 +899,8 @@ NO text overlays, decorative elements, borders, banners, or overlays.
884
  people_faces_section = """=== PEOPLE, FACES, CARS: OPTIONAL ===
885
  Only include people, faces, or vehicles if the VISUAL SCENE description specifically mentions them. Most auto insurance ad formats are typography and layout only - no people or cars needed."""
886
  else:
887
- people_faces_section = """=== CRITICAL: PEOPLE AND FACES ===
888
- If this image includes people or faces, they MUST look like real, original people with:
889
  - Photorealistic faces with natural skin texture, visible pores, and realistic skin imperfections
890
  - Natural facial asymmetry (no perfectly symmetrical faces)
891
  - Unique, individual facial features (not generic or model-like)
@@ -900,11 +915,13 @@ If this image includes people or faces, they MUST look like real, original peopl
900
  If you included people or vehicles, they should look realistic. Otherwise focus on layout and typography only."""
901
  else:
902
  authenticity_section = """=== AUTHENTICITY REQUIREMENTS ===
903
- PEOPLE (if present):
904
  - Real, relatable individuals in everyday clothing
905
- - Ages aligned to the niche (home insurance: 30-60 homeowners; GLP-1: 30-50; auto insurance: only when the format calls for people)
906
  - Natural expressions and trustworthy energy
907
 
 
 
908
  FACES (close-up):
909
  - Photorealistic texture with visible pores and natural variation
910
  - Subtle asymmetry and unique features—never plastic or model-perfect
@@ -931,7 +948,7 @@ If the image looks like it belongs on a stock website, it has failed.
931
 
932
  {people_faces_section}
933
 
934
- {"- For this ad graphic layout, headline and price/rate text are part of the design; include them as specified in VISUAL SCENE." if is_auto_insurance_ad_format else "IMPORTANT: Do NOT display numbers, prices, dollar amounts, or savings figures in the image unless they naturally appear as part of the scene (like on a document someone is holding, or a sign in the background). Focus on the visual scene and people, not numerical information. Numbers should be in the ad copy, not the image."}
935
 
936
  === VISUAL SPECIFICATIONS ===
937
  STYLE: {visual_style} - {"clean modern ad graphic, professional layout" if is_auto_insurance_ad_format else "rendered in vintage documentary aesthetic"}
@@ -1001,11 +1018,21 @@ CRITICAL REQUIREMENTS:
1001
 
1002
  # =====================================================================
1003
  # 2. FIX DEMOGRAPHIC ISSUES (niche-specific from niche data)
 
 
1004
  # =====================================================================
1005
  prompt_lower = prompt.lower()
1006
- niche_data_sanitize = self._get_niche_data(niche) if niche else {}
1007
- for pattern, replacement in niche_data_sanitize.get("prompt_sanitization_replacements", []):
1008
- prompt = re.sub(pattern, replacement, prompt, flags=re.IGNORECASE)
 
 
 
 
 
 
 
 
1009
 
1010
  # =====================================================================
1011
  # 3. FIX ILLOGICAL VISUAL COMBINATIONS
@@ -1062,15 +1089,11 @@ CRITICAL REQUIREMENTS:
1062
  'natural environment', prompt, flags=re.IGNORECASE)
1063
 
1064
  # =====================================================================
1065
- # 5. FIX UNREALISTIC BODY/TRANSFORMATION CLAIMS
1066
  # =====================================================================
1067
-
1068
- # Remove extreme/unrealistic transformation language
1069
  unrealistic_patterns = [
1070
- (r'\b(extreme weight loss|dramatic transformation overnight)\b', 'healthy transformation'),
1071
  (r'\b(impossibly thin|skeletal|anorexic)\b', 'healthy fit'),
1072
- (r'\b(perfect body|flawless figure)\b', 'confident healthy body'),
1073
- (r'\b(six pack abs|bodybuilder physique)\b', 'healthy toned body'),
1074
  ]
1075
  for pattern, replacement in unrealistic_patterns:
1076
  prompt = re.sub(pattern, replacement, prompt, flags=re.IGNORECASE)
@@ -1133,7 +1156,7 @@ CRITICAL REQUIREMENTS:
1133
 
1134
  # Ensure the prompt has authenticity markers if not present
1135
  if 'authentic' not in prompt_lower and 'ugc' not in prompt_lower and 'real' not in prompt_lower:
1136
- prompt += "\n\nStyle: Authentic, relatable, real-person aesthetic. Not overly polished or corporate."
1137
 
1138
  # Final trim
1139
  prompt = prompt.strip()
@@ -1832,19 +1855,23 @@ CONCEPT: {concept.get('name', 'Custom Concept')}
1832
  image_model: Optional[str] = None,
1833
  num_strategies: int = 5,
1834
  username: Optional[str] = None, # Username of the user generating the ad
 
 
1835
  ) -> Dict[str, Any]:
1836
  """
1837
- Generate ad using extensive: researcher → creative director → designer → copywriter.
1838
- Works for any niche: home_insurance, glp1, auto_insurance, or custom (e.g. from 'others').
1839
- Motivators are auto-generated per strategy.
1840
 
1841
  Args:
1842
- niche: Target niche (home_insurance, glp1, auto_insurance, or custom display name when 'others')
1843
  target_audience: Optional target audience description
1844
  offer: Optional offer to run
1845
  num_images: Number of images to generate per strategy
1846
  image_model: Image generation model to use
1847
  num_strategies: Number of creative strategies to generate
 
 
1848
 
1849
  Returns:
1850
  Dict with ad copy, images, and metadata
@@ -1860,51 +1887,66 @@ CONCEPT: {concept.get('name', 'Custom Concept')}
1860
  }
1861
  niche_display = niche_map.get(niche, niche.replace("_", " ").title())
1862
 
1863
- # Provide defaults if target_audience or offer are not provided
1864
- if not target_audience:
1865
- target_audience = f"People interested in {niche_display}"
1866
  if not offer:
1867
  offer = f"Get the best {niche_display} solution"
1868
-
1869
- # Step 1: Researcher
1870
- print("🔍 Step 1: Researching psychology triggers, angles, and concepts...")
1871
- researcher_output = await asyncio.to_thread(
1872
- third_flow_service.researcher,
1873
- target_audience=target_audience,
1874
- offer=offer,
1875
- niche=niche_display
1876
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1877
 
1878
  if not researcher_output:
1879
- raise ValueError("Researcher returned no results")
1880
 
1881
- # Step 2: Retrieve knowledge (in parallel) - Optimized: use asyncio.to_thread instead of blocking .result()
1882
  print("📚 Step 2: Retrieving marketing knowledge...")
1883
-
1884
- # Run synchronous operations in thread pool without blocking event loop
1885
  book_knowledge, ads_knowledge = await asyncio.gather(
1886
  asyncio.to_thread(
1887
  third_flow_service.retrieve_search,
1888
- target_audience, offer, niche_display
1889
  ),
1890
  asyncio.to_thread(
1891
  third_flow_service.retrieve_ads,
1892
- target_audience, offer, niche_display
1893
  )
1894
  )
1895
 
1896
- # Step 3: Creative Director
1897
  print(f"🎨 Step 3: Creating {num_strategies} creative strategy/strategies...")
1898
  print(f"�� Parameters: num_strategies={num_strategies}, num_images={num_images}")
 
 
 
1899
  creative_strategies = await asyncio.to_thread(
1900
  third_flow_service.creative_director,
1901
  researcher_output=researcher_output,
1902
  book_knowledge=book_knowledge,
1903
  ads_knowledge=ads_knowledge,
1904
- target_audience=target_audience,
1905
  offer=offer,
1906
  niche=niche_display,
1907
  n=num_strategies,
 
1908
  )
1909
 
1910
  if not creative_strategies:
@@ -1954,14 +1996,20 @@ CONCEPT: {concept.get('name', 'Custom Concept')}
1954
  "alternative angle, different mood",
1955
  ]
1956
 
 
 
 
 
 
 
1957
  async def generate_single_extensive_image(img_idx: int):
1958
  """Helper function to generate a single extensive image with all processing."""
1959
  try:
1960
- # Add low quality camera instruction to the prompt
1961
- prompt_with_camera = f"{prompt}\n\n=== CAMERA QUALITY ===\n- The image should look like it was shot from a low quality camera\n- Include characteristics of low quality camera: slight grain, reduced sharpness, lower resolution appearance, authentic camera imperfections\n- Should have the authentic feel of a real photo taken with a basic or older camera device"
1962
 
1963
  # Refine prompt and add variation for each image (pass niche for demographic fixes)
1964
- base_refined_prompt = self._refine_image_prompt(prompt_with_camera, niche=niche)
1965
 
1966
  # Add variation modifier if generating multiple images
1967
  if num_images > 1:
@@ -2180,11 +2228,7 @@ User's custom angle idea:
2180
 
2181
  {f'User goal/context: {goal}' if goal else ''}
2182
 
2183
- EXAMPLES OF WELL-STRUCTURED ANGLES:
2184
- 1. Name: "Fear / Loss Prevention", Trigger: "Fear", Example: "Don't lose your home to disaster"
2185
- 2. Name: "Save Money", Trigger: "Greed", Example: "Save $600/year"
2186
- 3. Name: "Peace of Mind", Trigger: "Relief", Example: "Sleep better knowing you're protected"
2187
- 4. Name: "Trending Now", Trigger: "FOMO", Example: "Join thousands already using this"
2188
 
2189
  Structure the user's idea into a proper angle format.
2190
 
@@ -2235,11 +2279,7 @@ User's custom concept idea:
2235
 
2236
  {f'User goal/context: {goal}' if goal else ''}
2237
 
2238
- EXAMPLES OF WELL-STRUCTURED CONCEPTS:
2239
- 1. Name: "Before/After Split", Structure: "Side-by-side comparison showing transformation", Visual: "Split screen with contrasting imagery, clear difference visible"
2240
- 2. Name: "Person + Quote", Structure: "Real person with testimonial overlay", Visual: "Authentic photo of relatable person, quote bubble or text overlay"
2241
- 3. Name: "Problem Close-up", Structure: "Zoom in on the pain point", Visual: "Detailed shot showing the problem clearly, emotional resonance"
2242
- 4. Name: "Lifestyle Scene", Structure: "Show the desired outcome/lifestyle", Visual: "Aspirational scene, happy people enjoying results"
2243
 
2244
  Structure the user's idea into a proper concept format.
2245
 
@@ -2326,45 +2366,33 @@ Use it to guide the hook, headline, and primary text. The ad must amplify this m
2326
  return f"""You are an elite direct-response copywriter creating a Facebook ad.
2327
  {motivator_block}
2328
  === ANGLE × CONCEPT FRAMEWORK ===
 
 
2329
 
2330
- ANGLE: {angle.get('name')}
2331
- - Psychological Trigger: {angle.get('trigger')}
2332
- - Example Hook: "{angle.get('example')}"
2333
- - This angle answers WHY they should care
2334
-
2335
- CONCEPT: {concept.get('name')}
2336
- - Visual Structure: {concept.get('structure')}
2337
- - Visual Guidance: {concept.get('visual')}
2338
- - This concept defines HOW to show it visually
2339
 
2340
  {f'=== USER INPUTS ===' if target_audience or offer else ''}
2341
  {f'TARGET AUDIENCE: {target_audience}' if target_audience else ''}
2342
  {f'OFFER: {offer}' if offer else ''}
2343
 
2344
  === CONTEXT ===
2345
- NICHE: {niche.replace("_", " ").title()}
2346
- CTA: {cta}
2347
 
2348
  {numbers_section}
2349
 
2350
- === YOUR MISSION ===
2351
- Create a scroll-stopping ad that:
2352
- 1. Uses the "{angle.get('name')}" angle to trigger {angle.get('trigger', 'emotion')}
2353
- 2. The image should follow the "{concept.get('name')}" visual concept
2354
-
2355
  === OUTPUT (JSON) ===
2356
  {{
2357
- "headline": "10 words max, triggers {angle.get('trigger')}. You decide whether to include specific numbers based on what enhances the message.",
2358
- "primary_text": "2-3 emotional sentences. You decide whether to include specific numbers based on what enhances believability and fits the strategy.",
2359
- "description": "One powerful sentence, 10 words max",
2360
- "body_story": "A compelling 8-12 sentence STORY. Start with relatable pain/situation. Build tension gradually with vivid details. Show transformation with specific examples. End with hope and emotional connection. Write in first/second person. Make it engaging and detailed enough to fully capture the reader's attention.",
2361
- "image_brief": "Detailed scene description following '{concept.get('name')}' concept: {concept.get('structure')}",
2362
  "cta": "{cta}",
2363
  "psychological_angle": "{angle.get('name')}",
2364
- "why_it_works": "Brief explanation of the psychological mechanism"
2365
  }}
2366
 
2367
- Generate the ad now. Be bold, be specific, trigger {angle.get('trigger')}."""
2368
 
2369
  def _build_matrix_image_prompt(
2370
  self,
@@ -2460,8 +2488,8 @@ This image should trigger: {angle.get('trigger')}
2460
 
2461
  {niche_guidance}
2462
 
2463
- === CRITICAL: PEOPLE AND FACES ===
2464
- If this image includes people or faces, they MUST look like real, original people with:
2465
  - Photorealistic faces with natural skin texture, visible pores, and realistic skin imperfections
2466
  - Natural facial asymmetry (no perfectly symmetrical faces)
2467
  - Unique, individual facial features (not generic or model-like)
 
22
  get_framework_visual_guidance,
23
  get_frameworks_for_niche,
24
  )
25
+ from data.ecom_verticals import get_random_vertical, get_verticals_for_prompt
26
  from data.hooks import get_power_words, get_random_cta as get_hook_cta, get_random_hook_style
27
  from data.triggers import get_random_trigger, get_trigger_combination, get_triggers_for_niche
28
  from data.visuals import (
 
180
 
181
  # --- Niche & strategy ---
182
 
183
+ def _get_minimal_niche_data_for_custom(self, niche: str) -> Dict[str, Any]:
184
+ """Return minimal niche data for custom/others niche (no raise). Used when niche is not in NICHE_DATA."""
185
+ label = (niche or "").replace("_", " ").title() or "Custom"
186
+ return {
187
+ "niche": niche,
188
+ "strategies": {},
189
+ "strategy_names": [],
190
+ "creative_directions": ["professional", "clean", "trustworthy"],
191
+ "visual_moods": ["neutral", "approachable"],
192
+ "niche_guidance": f"Focus on the {label} niche. Use authentic, low-production visuals.",
193
+ "image_guidance": f"Images should be appropriate for {label}. Authentic, relatable visuals.",
194
+ "image_niche_guidance_short": f"NICHE: {label}",
195
+ "number_config": {},
196
+ "price_config": {},
197
+ "prompt_sanitization_replacements": [],
198
+ "visual_library": {},
199
+ "image_creative_prompts": [],
200
+ "all_hooks": [],
201
+ "all_visual_styles": [],
202
+ "copy_templates": [],
203
+ }
204
+
205
  def _get_niche_data(self, niche: str) -> Dict[str, Any]:
206
+ """Load data for a specific niche (cached). For custom/others niche, returns minimal data instead of raising."""
 
 
 
207
  if niche not in self._niche_data_cache:
208
+ if niche in NICHE_DATA:
209
+ self._niche_data_cache[niche] = NICHE_DATA[niche]()
210
+ else:
211
+ # Custom/others niche (e.g. "Roofing Programme") — use minimal data so image refinement/save don't crash
212
+ self._niche_data_cache[niche] = self._get_minimal_niche_data_for_custom(niche)
213
  return self._niche_data_cache[niche]
214
 
215
  # ========================================================================
 
465
  {context}
466
 
467
  Rules:
468
+ - Generate diverse, scroll-stopping CTAs; niche is optional context; any tone or style allowed.
469
  - Return ONLY a JSON object with one key "ctas" containing an array of strings. No other text."""
470
 
471
  try:
 
584
  CALL-TO-ACTION: {cta}
585
  {trending_section}
586
 
587
+ === ANGLE × CONCEPT (inspiration—invent or extend) ===
588
+ ANGLE: {angle.get('name') if angle else 'N/A'} | Trigger: {angle.get('trigger') if angle else 'N/A'} | Example: "{angle.get('example') if angle else 'N/A'}"
589
+ CONCEPT: {concept.get('name') if concept else 'N/A'} | Structure: {concept.get('structure') if concept else 'N/A'} | Visual: {concept.get('visual') if concept else 'N/A'}
 
 
590
 
591
+ Use the above or invent your own triggers/angles/concepts.
 
 
 
592
 
593
  {f'=== USER INPUTS ===' if target_audience or offer else ''}
594
  {f'TARGET AUDIENCE: {target_audience}' if target_audience else ''}
595
  {f'OFFER: {offer}' if offer else ''}
596
 
597
+ Target audience can be the given one or any hyperrealistic segment you invent (including outside the niche). You may invent or mix demographics, psychographics, and life stages for maximum diversity and scroll-stopping relevance.
598
+
599
+ For variety, use angles, concepts, and motivators suited to different ecom verticals: fashion, beauty, supplements, fitness, electronics, home goods, pets, food & beverage, jewelry, subscription. This creative can lean into: {get_random_vertical()['name']}.
600
+
601
  === FRAMEWORK VISUAL FORMAT (same framework for copy + image) ===
602
  FRAMEWORK: {framework_data.get('name', framework)}
603
  DESCRIPTION: {framework_data.get('description', '')}
 
628
  {niche_guidance}
629
 
630
  {headline_formulas}
631
+ Use as inspiration only; invent new scroll-stopping or clickbait headlines as needed.
632
+
633
  === YOUR MISSION ===
634
+ Create a scroll-stopping Facebook ad that looks like organic content. Use the angle/concept/trigger above (or invent your own). No restrictions—clickbait, bold statements, and any audience are allowed.
635
 
636
  === OUTPUT REQUIREMENTS ===
637
 
638
  1. HEADLINE (The "Arrest")
639
+ - MAXIMUM 10 words; instant pattern interrupt. Use the angle and trigger above.
640
+ - Include numbers/prices only when they strengthen the hook; use oddly specific amounts (e.g. $97.33). Include demographic targeting where appropriate. Be SPECIFIC and EMOTIONAL.
 
 
 
 
641
 
642
  2. PRIMARY TEXT (The "Agitation")
643
+ - 2-3 punchy sentences that amplify the emotional hook. Reference the demographic; create urgency; make them FEEL the pain or desire. Oddly specific numbers when they fit.
 
 
 
 
 
644
 
645
  3. DESCRIPTION (The "Payoff")
646
+ - ONE powerful sentence (max 10 words). Create action urgency. Oddly specific metrics when they enhance the message.
 
 
647
 
648
  4. IMAGE BRIEF (CRITICAL - must match {framework_data.get('name', framework)} framework style)
649
+ - Follow the concept above: {concept.get('structure') if concept else 'authentic visual'}
650
  - Describe the scene for the {framework_data.get('name', framework)} framework ONLY
651
  - Visual guidance: {get_framework_visual_guidance(framework_data.get('key', ''))}
652
  - The image should look like ORGANIC CONTENT, not an ad
653
+ - Include: setting, props, mood. People are OPTIONAL—creatives can be product-only, layout-only, document-only, or text-only; only add people when the concept clearly calls for them.
654
  - Follow framework authenticity tips: {', '.join(framework_data.get('authenticity_tips', [])[:2])}
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 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.
659
+ - FOR HOME INSURANCE: Document, savings proof, home setting. People optional (e.g. document on table only, or person with document).
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 visible on a label, screen, or document. Use VARIETY: product-only (bottle/pen, screen), quiz interfaces, document/surface, or with people when the concept calls for it. People optional.
661
+ - Visuals and concepts can be invented and need not match niche stereotypes; aim for hyperrealistic, diverse representation (any age, demographic, psychographic).
662
 
663
  === PSYCHOLOGICAL PRINCIPLES ===
664
  - Loss Aversion: Make them feel what they're losing/missing
 
669
  - Native Disguise: Content that doesn't look like an ad bypasses filters
670
 
671
  === CRITICAL RULES ===
672
+ 1. Use the niche as inspiration only—you may invent or blend angles, concepts, triggers, and audiences from any domain for maximum diversity.
673
+ 2. Use SPECIFIC numbers from the numbers section above when they fit; invent or adapt when it strengthens the hook.
674
+ 3. ALWAYS create curiosity gap with "THIS", "Instead", "After", "Secret"; use any clickbait or bold phrasing that works.
675
  4. NEVER look like an ad - look like NEWS, PROOF, or UGC
676
  5. Use ACCUSATION framing for maximum impact
677
  6. The image MUST match the {framework_data.get('name', framework)} framework style
678
 
679
  === OUTPUT FORMAT (JSON) ===
680
  {{
681
+ "headline": "<10 words, pattern interrupt>",
682
+ "primary_text": "<2-3 emotional sentences>",
683
+ "description": "<one sentence, max 10 words>",
684
+ "body_story": "<8-12 sentence story: relatable pain, tension, transformation, hope, soft CTA; first/second person>",
685
+ "image_brief": "<scene following concept and {framework_data.get('name', framework)} style; organic feel>",
686
  "cta": "{cta}",
687
+ "psychological_angle": "<angle name or primary trigger>",
688
+ "why_it_works": "<brief mechanism>"
689
  }}
690
 
691
+ Generate the ad copy now. Organic content, immediate emotional response."""
692
 
693
  return prompt
694
 
 
899
  people_faces_section = """=== PEOPLE, FACES, CARS: OPTIONAL ===
900
  Only include people, faces, or vehicles if the VISUAL SCENE description specifically mentions them. Most auto insurance ad formats are typography and layout only - no people or cars needed."""
901
  else:
902
+ people_faces_section = """=== PEOPLE AND FACES: OPTIONAL ===
903
+ Only include people or faces if the VISUAL SCENE explicitly describes them. Many creatives work without people—product shots, documents, layouts, objects, text-only are all valid. If this image does include people or faces, they MUST look like real, original people with:
904
  - Photorealistic faces with natural skin texture, visible pores, and realistic skin imperfections
905
  - Natural facial asymmetry (no perfectly symmetrical faces)
906
  - Unique, individual facial features (not generic or model-like)
 
915
  If you included people or vehicles, they should look realistic. Otherwise focus on layout and typography only."""
916
  else:
917
  authenticity_section = """=== AUTHENTICITY REQUIREMENTS ===
918
+ Creatives may have no people (product-only, document, layout, objects, text). PEOPLE (only if present):
919
  - Real, relatable individuals in everyday clothing
920
+ - Any hyperrealistic audience—any age, demographic, or psychographic; niche is a suggestion, not a rule
921
  - Natural expressions and trustworthy energy
922
 
923
+ Visuals and concepts can be invented and need not match niche stereotypes; aim for hyperrealistic, diverse representation.
924
+
925
  FACES (close-up):
926
  - Photorealistic texture with visible pores and natural variation
927
  - Subtle asymmetry and unique features—never plastic or model-perfect
 
948
 
949
  {people_faces_section}
950
 
951
+ {"- For this ad graphic layout, headline and price/rate text are part of the design; include them as specified in VISUAL SCENE." if is_auto_insurance_ad_format else "IMPORTANT: Do NOT display numbers, prices, dollar amounts, or savings figures in the image unless they naturally appear as part of the scene (e.g. on a document or sign). Focus on the visual scene (people optional; product/layout-only is fine). Numbers should be in the ad copy, not the image."}
952
 
953
  === VISUAL SPECIFICATIONS ===
954
  STYLE: {visual_style} - {"clean modern ad graphic, professional layout" if is_auto_insurance_ad_format else "rendered in vintage documentary aesthetic"}
 
1018
 
1019
  # =====================================================================
1020
  # 2. FIX DEMOGRAPHIC ISSUES (niche-specific from niche data)
1021
+ # Skip niche demographic overrides when the prompt already describes a specific
1022
+ # audience (unrestricted mode: allow diverse/invented audiences to stand).
1023
  # =====================================================================
1024
  prompt_lower = prompt.lower()
1025
+ has_explicit_audience = any(
1026
+ phrase in prompt_lower
1027
+ for phrase in [
1028
+ "aged ", "years old", "year old", "woman", "man", "demographic",
1029
+ "hyperrealistic", "diverse representation", "any age",
1030
+ ]
1031
+ )
1032
+ if not has_explicit_audience:
1033
+ niche_data_sanitize = self._get_niche_data(niche) if niche else {}
1034
+ for pattern, replacement in niche_data_sanitize.get("prompt_sanitization_replacements", []):
1035
+ prompt = re.sub(pattern, replacement, prompt, flags=re.IGNORECASE)
1036
 
1037
  # =====================================================================
1038
  # 3. FIX ILLOGICAL VISUAL COMBINATIONS
 
1089
  'natural environment', prompt, flags=re.IGNORECASE)
1090
 
1091
  # =====================================================================
1092
+ # 5. SOFTEN EXTREME BODY LANGUAGE ONLY (allow diverse/invented visuals)
1093
  # =====================================================================
1094
+ # Only replace truly extreme/harmful terms; allow dramatic transformation etc.
 
1095
  unrealistic_patterns = [
 
1096
  (r'\b(impossibly thin|skeletal|anorexic)\b', 'healthy fit'),
 
 
1097
  ]
1098
  for pattern, replacement in unrealistic_patterns:
1099
  prompt = re.sub(pattern, replacement, prompt, flags=re.IGNORECASE)
 
1156
 
1157
  # Ensure the prompt has authenticity markers if not present
1158
  if 'authentic' not in prompt_lower and 'ugc' not in prompt_lower and 'real' not in prompt_lower:
1159
+ prompt += "\n\nStyle: Authentic; can be real-person, product-only, or layout-only. Not overly polished or corporate."
1160
 
1161
  # Final trim
1162
  prompt = prompt.strip()
 
1855
  image_model: Optional[str] = None,
1856
  num_strategies: int = 5,
1857
  username: Optional[str] = None, # Username of the user generating the ad
1858
+ use_creative_inventor: bool = True,
1859
+ trend_context: Optional[str] = None,
1860
  ) -> Dict[str, Any]:
1861
  """
1862
+ Generate ad using extensive flow: (inventor or researcher) → creative director → designer → copywriter.
1863
+ When use_creative_inventor=True, the system invents new ad angles, concepts, visuals, and
1864
+ psychological triggers by itself instead of using the fixed researcher step.
1865
 
1866
  Args:
1867
+ niche: Target niche (home_insurance, glp1, auto_insurance, or custom)
1868
  target_audience: Optional target audience description
1869
  offer: Optional offer to run
1870
  num_images: Number of images to generate per strategy
1871
  image_model: Image generation model to use
1872
  num_strategies: Number of creative strategies to generate
1873
+ use_creative_inventor: If True, use Creative Inventor to generate new angles/concepts/visuals/triggers; if False, use researcher
1874
+ trend_context: Optional trend or occasion context (used when use_creative_inventor=True)
1875
 
1876
  Returns:
1877
  Dict with ad copy, images, and metadata
 
1887
  }
1888
  niche_display = niche_map.get(niche, niche.replace("_", " ").title())
1889
 
1890
+ # Provide default offer if not provided; target_audience is optional when using inventor (AI decides audiences)
 
 
1891
  if not offer:
1892
  offer = f"Get the best {niche_display} solution"
1893
+ audience_for_retrieve = target_audience or f"People interested in {niche_display}"
1894
+
1895
+ # Step 1: Invent new angles/concepts/visuals/triggers + hyper-specific audiences (Creative Inventor) or research (Researcher)
1896
+ target_audiences_per_strategy: Optional[List[str]] = None
1897
+ if use_creative_inventor:
1898
+ print("🧠 Step 1: Inventing new ad angles, concepts, visuals, triggers, and hyper-specific audiences...")
1899
+ researcher_output, target_audiences_per_strategy = await asyncio.to_thread(
1900
+ third_flow_service.get_essentials_via_inventor,
1901
+ niche=niche_display,
1902
+ offer=offer,
1903
+ n=num_strategies,
1904
+ target_audience_hint=target_audience if target_audience else None,
1905
+ trend_context=trend_context,
1906
+ )
1907
+ else:
1908
+ print("🔍 Step 1: Researching psychology triggers, angles, and concepts...")
1909
+ if not target_audience:
1910
+ target_audience = audience_for_retrieve
1911
+ researcher_output = await asyncio.to_thread(
1912
+ third_flow_service.researcher,
1913
+ target_audience=target_audience,
1914
+ offer=offer,
1915
+ niche=niche_display
1916
+ )
1917
 
1918
  if not researcher_output:
1919
+ raise ValueError("Step 1 returned no results (inventor or researcher)")
1920
 
1921
+ # Step 2: Retrieve knowledge (in parallel)
1922
  print("📚 Step 2: Retrieving marketing knowledge...")
 
 
1923
  book_knowledge, ads_knowledge = await asyncio.gather(
1924
  asyncio.to_thread(
1925
  third_flow_service.retrieve_search,
1926
+ audience_for_retrieve, offer, niche_display
1927
  ),
1928
  asyncio.to_thread(
1929
  third_flow_service.retrieve_ads,
1930
+ audience_for_retrieve, offer, niche_display
1931
  )
1932
  )
1933
 
1934
+ # Step 3: Creative Director (with per-strategy hyper-specific audiences when from inventor)
1935
  print(f"🎨 Step 3: Creating {num_strategies} creative strategy/strategies...")
1936
  print(f"�� Parameters: num_strategies={num_strategies}, num_images={num_images}")
1937
+ creative_director_target = (
1938
+ "Various hyper-specific audiences (see per-strategy)" if target_audiences_per_strategy else audience_for_retrieve
1939
+ )
1940
  creative_strategies = await asyncio.to_thread(
1941
  third_flow_service.creative_director,
1942
  researcher_output=researcher_output,
1943
  book_knowledge=book_knowledge,
1944
  ads_knowledge=ads_knowledge,
1945
+ target_audience=creative_director_target,
1946
  offer=offer,
1947
  niche=niche_display,
1948
  n=num_strategies,
1949
+ target_audiences=target_audiences_per_strategy,
1950
  )
1951
 
1952
  if not creative_strategies:
 
1996
  "alternative angle, different mood",
1997
  ]
1998
 
1999
+ # Title/headline to show in image (from copywriter; use strategy title ideas if needed)
2000
+ text_for_image = (title or creative_strategies[idx].titleIdeas or "").strip() if idx < len(creative_strategies) else (title or "").strip()
2001
+ text_instruction = ""
2002
+ if text_for_image:
2003
+ text_instruction = f'\n\n=== TEXT THAT MUST APPEAR IN THE IMAGE ===\nInclude this text visibly and readably in the image (e.g. on a sign, document, phone screen, poster, or surface): "{text_for_image}"\n- Spell it correctly; make it clearly readable.\n- Include it once as part of the natural scene, not as a separate overlay.'
2004
+
2005
  async def generate_single_extensive_image(img_idx: int):
2006
  """Helper function to generate a single extensive image with all processing."""
2007
  try:
2008
+ # Add text instruction then low quality camera instruction so the image contains the headline/title
2009
+ prompt_with_text_and_camera = f"{prompt}{text_instruction}\n\n=== CAMERA QUALITY ===\n- The image should look like it was shot from a low quality camera\n- Include characteristics of low quality camera: slight grain, reduced sharpness, lower resolution appearance, authentic camera imperfections\n- Should have the authentic feel of a real photo taken with a basic or older camera device"
2010
 
2011
  # Refine prompt and add variation for each image (pass niche for demographic fixes)
2012
+ base_refined_prompt = self._refine_image_prompt(prompt_with_text_and_camera, niche=niche)
2013
 
2014
  # Add variation modifier if generating multiple images
2015
  if num_images > 1:
 
2228
 
2229
  {f'User goal/context: {goal}' if goal else ''}
2230
 
2231
+ Example format: Name (2-4 words), Trigger (e.g. Fear, Greed, Relief, FOMO), Example hook (5-10 words). e.g. "Save Money" / Greed / "Save $600/year".
 
 
 
 
2232
 
2233
  Structure the user's idea into a proper angle format.
2234
 
 
2279
 
2280
  {f'User goal/context: {goal}' if goal else ''}
2281
 
2282
+ Example format: Name (2-4 words), Structure (one sentence), Visual (one sentence with specific details). e.g. "Before/After Split" / "Side-by-side comparison" / "Split screen, clear contrast".
 
 
 
 
2283
 
2284
  Structure the user's idea into a proper concept format.
2285
 
 
2366
  return f"""You are an elite direct-response copywriter creating a Facebook ad.
2367
  {motivator_block}
2368
  === ANGLE × CONCEPT FRAMEWORK ===
2369
+ ANGLE: {angle.get('name')} (Trigger: {angle.get('trigger')}) — Example: "{angle.get('example')}"
2370
+ CONCEPT: {concept.get('name')} — Structure: {concept.get('structure')} | Visual: {concept.get('visual')}
2371
 
2372
+ For variety, adapt to different ecom verticals (fashion, beauty, supplements, fitness, electronics, home, pets, food). This ad can lean into: {get_random_vertical()['name']}.
 
 
 
 
 
 
 
 
2373
 
2374
  {f'=== USER INPUTS ===' if target_audience or offer else ''}
2375
  {f'TARGET AUDIENCE: {target_audience}' if target_audience else ''}
2376
  {f'OFFER: {offer}' if offer else ''}
2377
 
2378
  === CONTEXT ===
2379
+ NICHE: {niche.replace("_", " ").title()} | CTA: {cta}
 
2380
 
2381
  {numbers_section}
2382
 
 
 
 
 
 
2383
  === OUTPUT (JSON) ===
2384
  {{
2385
+ "headline": "<10 words max; use angle/trigger above; add numbers if they strengthen>",
2386
+ "primary_text": "<2-3 emotional sentences>",
2387
+ "description": "<one sentence, 10 words max>",
2388
+ "body_story": "<8-12 sentence story: pain, tension, transformation, hope; first/second person>",
2389
+ "image_brief": "<scene following concept above>",
2390
  "cta": "{cta}",
2391
  "psychological_angle": "{angle.get('name')}",
2392
+ "why_it_works": "<brief mechanism>"
2393
  }}
2394
 
2395
+ Generate the ad now. Be bold and specific."""
2396
 
2397
  def _build_matrix_image_prompt(
2398
  self,
 
2488
 
2489
  {niche_guidance}
2490
 
2491
+ === PEOPLE AND FACES: OPTIONAL ===
2492
+ Only include people or faces if the image brief/VISUAL SCENE explicitly describes them. Creatives can be product-only, document-only, or layout-only with no people. If this image does include people or faces, they MUST look like real, original people with:
2493
  - Photorealistic faces with natural skin texture, visible pores, and realistic skin imperfections
2494
  - Natural facial asymmetry (no perfectly symmetrical faces)
2495
  - Unique, individual facial features (not generic or model-like)
services/generator_prompts.py CHANGED
@@ -9,113 +9,25 @@ from typing import Dict, Any, Optional
9
  # Headline formulas by niche / number type
10
  # -----------------------------------------------------------------------------
11
 
12
- HEADLINE_FORMULAS_WEIGHT_LOSS = """=== PROVEN WINNING HEADLINE FORMULAS (WEIGHT LOSS) ===
13
-
14
- WITH NUMBERS (use if numbers section provided):
15
- 1. THE TRANSFORMATION: Specific weight loss results
16
- - "Lost 47 lbs In 90 Days"
17
- - "Down 4 Dress Sizes In 8 Weeks"
18
- - "From 247 lbs to 168 lbs"
19
-
20
- WITHOUT NUMBERS (use if no numbers section):
21
- 1. THE ACCUSATION: Direct accusation about weight struggle
22
- - "Still Overweight?"
23
- - "Another Failed Diet?"
24
- - "Tired Of Hiding Your Body?"
25
-
26
- 2. THE CURIOSITY GAP: Open loop about weight loss secret
27
- - "Thousands Are Losing Weight After THIS"
28
- - "Doctors Are Prescribing THIS Instead Of Diets"
29
- - "What Hollywood Has Used For Years"
30
-
31
- 3. THE BEFORE/AFTER: Dramatic transformation proof
32
- - "Same Person. 90 Days Apart."
33
- - "Is This Even The Same Person?"
34
- - "The Transformation That Shocked Everyone"
35
-
36
- 4. THE IDENTITY CALLOUT: Target demographics
37
- - "Women Over 40: This Changes Everything"
38
- - "If You've Tried Every Diet And Failed..."
39
- - "For People Who've Struggled For Years"
40
-
41
- 5. THE MEDICAL AUTHORITY: Doctor/FDA credibility
42
- - "FDA-Approved Weight Loss"
43
- - "Doctor-Prescribed. Clinically Proven."
44
- - "What Doctors Prescribe Their Own Families\""""
45
-
46
- HEADLINE_FORMULAS_AUTO_INSURANCE = """=== PROVEN WINNING HEADLINE FORMULAS (AUTO INSURANCE) ===
47
-
48
- WITH NUMBERS (use if numbers section provided):
49
- 1. THE SPECIFIC PRICE ANCHOR: Oddly specific = believable
50
- - "Car Insurance for as low as $29/month"
51
- - "Drivers Won't Have To Pay More Than $39 A Month"
52
-
53
- 2. THE BEFORE/AFTER PROOF: Savings with evidence
54
- - "WAS: $1,842 → NOW: $647"
55
- - "The Easiest Way To Cut Car Insurance Bills"
56
-
57
- WITHOUT NUMBERS (use if no numbers section):
58
- 1. THE ACCUSATION: Direct accusation about overpaying
59
- - "OVERPAYING?"
60
- - "Still Overpaying For Car Insurance?"
61
- - "Wasting Money On Auto Insurance?"
62
-
63
- 2. THE CURIOSITY GAP: Open loop that demands click
64
- - "Drivers Are Ditching Their Auto Insurance & Doing This Instead"
65
- - "Thousands of drivers are dropping insurance after THIS"
66
- - "Why Are Drivers Switching?"
67
-
68
- 3. THE IDENTITY CALLOUT: Target demographics (drivers, not "seniors" or "homeowners")
69
- - "Drivers Over 50: Check Your Eligibility"
70
- - "Safe Drivers: Check Your Rate"
71
-
72
- 4. THE AUTHORITY TRANSFER: Government/institutional trust
73
- - "State Program Cuts Insurance Costs"
74
- - "Official: Safe Drivers Qualify For Reduced Rates"
75
-
76
- 5. THE EMOTIONAL BENEFIT: Focus on outcomes
77
- - "Protect What Matters Most"
78
- - "Finally, Peace of Mind On The Road"
79
- - "Drive Confident Knowing You're Covered\""""
80
-
81
- HEADLINE_FORMULAS_HOME_INSURANCE = """=== PROVEN WINNING HEADLINE FORMULAS (HOME INSURANCE) ===
82
-
83
- WITH NUMBERS (use if numbers section provided):
84
- 1. THE SPECIFIC PRICE ANCHOR: Oddly specific = believable
85
- - "Home Insurance for as low as $97.33/month"
86
- - "Seniors Won't Have To Pay More Than $49 A Month"
87
-
88
- 2. THE BEFORE/AFTER PROOF: Savings with evidence
89
- - "WAS: $1,701 → NOW: $583"
90
- - "The Easiest Way To Cut Home Insurance Bills"
91
-
92
- WITHOUT NUMBERS (use if no numbers section):
93
- 1. THE ACCUSATION: Direct accusation about overpaying
94
- - "OVERPAYING?"
95
- - "Still Underinsured?"
96
- - "Wasting Money On Insurance?"
97
-
98
- 2. THE CURIOSITY GAP: Open loop that demands click
99
- - "Seniors Are Ditching Home Insurance & Doing This Instead"
100
- - "Thousands of homeowners are dropping insurance after THIS"
101
- - "Why Are Homeowners Switching?"
102
-
103
- 3. THE IDENTITY CALLOUT: Target demographics
104
- - "Homeowners Over 50: Check Your Eligibility"
105
- - "Senior homeowners over the age of 50..."
106
-
107
- 4. THE AUTHORITY TRANSFER: Government/institutional trust
108
- - "State Farm Brings Welfare!"
109
- - "Sponsored by the US Government"
110
-
111
- 5. THE EMOTIONAL BENEFIT: Focus on outcomes
112
- - "Protect What Matters Most"
113
- - "Finally, Peace of Mind"
114
- - "Sleep Better Knowing You're Covered\""""
115
 
116
 
117
  def get_headline_formulas(niche: str, num_type: str) -> str:
118
- """Return the headline formulas block for the given niche and number type."""
 
119
  if num_type == "weight_loss":
120
  return HEADLINE_FORMULAS_WEIGHT_LOSS
121
  if niche == "auto_insurance":
 
9
  # Headline formulas by niche / number type
10
  # -----------------------------------------------------------------------------
11
 
12
+ HEADLINE_FORMULAS_WEIGHT_LOSS = """=== HEADLINE FORMULAS (WEIGHT LOSS) - use as inspiration ===
13
+
14
+ WITH NUMBERS: THE TRANSFORMATION ("Lost 47 lbs In 90 Days") | THE BEFORE/AFTER ("From 247 to 168 lbs")
15
+ WITHOUT NUMBERS: THE ACCUSATION ("Still Overweight?") | CURIOSITY GAP ("Thousands Are Losing Weight After THIS") | IDENTITY CALLOUT ("Women Over 40: This Changes Everything") | MEDICAL AUTHORITY ("FDA-Approved Weight Loss")"""
16
+
17
+ HEADLINE_FORMULAS_AUTO_INSURANCE = """=== HEADLINE FORMULAS (AUTO INSURANCE) - use as inspiration ===
18
+
19
+ WITH NUMBERS: PRICE ANCHOR ("Car Insurance for as low as $29/month") | BEFORE/AFTER ("WAS: $1,842 → NOW: $647")
20
+ WITHOUT NUMBERS: ACCUSATION ("Still Overpaying?") | CURIOSITY GAP ("Drivers Are Ditching Their Auto Insurance & Doing This Instead") | IDENTITY ("Drivers Over 50: Check Your Eligibility") | AUTHORITY ("Official: Safe Drivers Qualify") | EMOTIONAL ("Protect What Matters Most")"""
21
+
22
+ HEADLINE_FORMULAS_HOME_INSURANCE = """=== HEADLINE FORMULAS (HOME INSURANCE) - use as inspiration ===
23
+
24
+ WITH NUMBERS: PRICE ANCHOR ("Home Insurance for as low as $97.33/month") | BEFORE/AFTER ("WAS: $1,701 → NOW: $583")
25
+ WITHOUT NUMBERS: ACCUSATION ("Still Underinsured?") | CURIOSITY GAP ("Seniors Are Ditching Home Insurance & Doing This Instead") | IDENTITY ("Homeowners Over 50: Check Your Eligibility") | AUTHORITY ("State Program Cuts Costs") | EMOTIONAL ("Protect What Matters Most")"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
 
27
 
28
  def get_headline_formulas(niche: str, num_type: str) -> str:
29
+ """Return the headline formulas block for the given niche and number type.
30
+ Use as inspiration only; the generator may invent new scroll-stopping or clickbait headlines without restriction."""
31
  if num_type == "weight_loss":
32
  return HEADLINE_FORMULAS_WEIGHT_LOSS
33
  if niche == "auto_insurance":
services/matrix.py CHANGED
@@ -22,6 +22,11 @@ from data.angles import (
22
  get_angle_by_key,
23
  AngleCategory,
24
  )
 
 
 
 
 
25
  from data.concepts import (
26
  get_all_concepts,
27
  get_random_concepts,
@@ -51,7 +56,8 @@ class AngleConceptMatrix:
51
  niche: Optional[str] = None,
52
  angle_count: int = 6,
53
  concept_count: int = 5,
54
- strategy: str = "balanced"
 
55
  ) -> List[Dict[str, Any]]:
56
  """
57
  Generate initial testing matrix.
@@ -59,27 +65,32 @@ class AngleConceptMatrix:
59
  Default: 6 angles × 5 concepts = 30 combinations
60
 
61
  Args:
62
- niche: Target niche for filtering
63
  angle_count: Number of angles to test
64
  concept_count: Number of concepts per angle
65
  strategy: Selection strategy (balanced, top_performers, diverse)
66
-
67
  Returns:
68
  List of angle × concept combinations
69
  """
70
- # Select angles
71
- if strategy == "top_performers":
 
 
 
 
 
 
 
72
  angles = get_top_angles()[:angle_count]
73
  elif strategy == "diverse":
74
  angles = get_random_angles(angle_count, diverse=True)
75
  elif niche:
76
  angles = get_angles_for_niche(niche)[:angle_count]
77
  if len(angles) < angle_count:
78
- # Supplement with diverse angles
79
  extra = get_random_angles(angle_count - len(angles), diverse=True)
80
  angles.extend(extra)
81
  else:
82
- # Balanced: mix of top performers and diverse
83
  top = get_top_angles()[:angle_count // 2]
84
  diverse = get_random_angles(angle_count - len(top), diverse=True)
85
  angles = top + diverse
@@ -143,28 +154,39 @@ class AngleConceptMatrix:
143
 
144
  def generate_single_combination(
145
  self,
146
- niche: Optional[str] = None
 
147
  ) -> Dict[str, Any]:
148
  """
149
  Generate a single random angle × concept combination.
150
 
151
  Good for generating one-off ads with variety.
 
 
152
  """
153
- # Get random angle (niche-aware if provided)
154
- if niche:
 
 
 
 
 
 
 
 
155
  angles = get_angles_for_niche(niche)
156
  angle = random.choice(angles) if angles else random.choice(self.all_angles)
157
- else:
158
- angle = random.choice(self.all_angles)
159
 
160
- # Get compatible concept based on trigger
161
- trigger = angle.get("trigger", "")
162
- compatible = get_compatible_concepts(trigger)
163
-
164
- if compatible:
165
- concept = random.choice(compatible)
166
- else:
167
  concept = random.choice(self.all_concepts)
 
 
 
 
 
 
 
168
 
169
  return self._create_combination(angle, concept)
170
 
 
22
  get_angle_by_key,
23
  AngleCategory,
24
  )
25
+ try:
26
+ from data.ecom_verticals import get_random_vertical, get_angle_keys_for_vertical
27
+ except ImportError:
28
+ get_random_vertical = None
29
+ get_angle_keys_for_vertical = None
30
  from data.concepts import (
31
  get_all_concepts,
32
  get_random_concepts,
 
56
  niche: Optional[str] = None,
57
  angle_count: int = 6,
58
  concept_count: int = 5,
59
+ strategy: str = "balanced",
60
+ unrestricted: bool = True,
61
  ) -> List[Dict[str, Any]]:
62
  """
63
  Generate initial testing matrix.
 
65
  Default: 6 angles × 5 concepts = 30 combinations
66
 
67
  Args:
68
+ niche: Target niche for filtering (ignored for angles when unrestricted=True)
69
  angle_count: Number of angles to test
70
  concept_count: Number of concepts per angle
71
  strategy: Selection strategy (balanced, top_performers, diverse)
72
+ unrestricted: If True, use full angle pool (all angles) for maximum diversity
73
  Returns:
74
  List of angle × concept combinations
75
  """
76
+ # Select angles: when unrestricted, use full/diverse pool; otherwise niche-filtered
77
+ if unrestricted:
78
+ if strategy == "top_performers":
79
+ angles = get_top_angles()[:angle_count]
80
+ else:
81
+ angles = get_random_angles(angle_count, diverse=True)
82
+ if len(angles) < angle_count:
83
+ angles.extend(get_random_angles(angle_count - len(angles), diverse=True))
84
+ elif strategy == "top_performers":
85
  angles = get_top_angles()[:angle_count]
86
  elif strategy == "diverse":
87
  angles = get_random_angles(angle_count, diverse=True)
88
  elif niche:
89
  angles = get_angles_for_niche(niche)[:angle_count]
90
  if len(angles) < angle_count:
 
91
  extra = get_random_angles(angle_count - len(angles), diverse=True)
92
  angles.extend(extra)
93
  else:
 
94
  top = get_top_angles()[:angle_count // 2]
95
  diverse = get_random_angles(angle_count - len(top), diverse=True)
96
  angles = top + diverse
 
154
 
155
  def generate_single_combination(
156
  self,
157
+ niche: Optional[str] = None,
158
+ unrestricted: bool = True,
159
  ) -> Dict[str, Any]:
160
  """
161
  Generate a single random angle × concept combination.
162
 
163
  Good for generating one-off ads with variety.
164
+ When unrestricted=True (default), use full angle and concept pool for maximum diversity.
165
+ When unrestricted=False and niche provided, filter angles by niche.
166
  """
167
+ # Get random angle: unrestricted = all angles or vertical-biased for variety; otherwise niche-filtered
168
+ if unrestricted or not niche:
169
+ if get_random_vertical and get_angle_keys_for_vertical and random.random() < 0.4:
170
+ v = get_random_vertical()
171
+ keys = get_angle_keys_for_vertical(v.get("key", ""))
172
+ vertical_angles = [get_angle_by_key(k) for k in keys if get_angle_by_key(k)]
173
+ angle = random.choice(vertical_angles) if vertical_angles else random.choice(self.all_angles)
174
+ else:
175
+ angle = random.choice(self.all_angles)
176
+ else:
177
  angles = get_angles_for_niche(niche)
178
  angle = random.choice(angles) if angles else random.choice(self.all_angles)
 
 
179
 
180
+ # Get concept: unrestricted = any concept for max diversity; otherwise trigger-compatible
181
+ if unrestricted:
 
 
 
 
 
182
  concept = random.choice(self.all_concepts)
183
+ else:
184
+ trigger = angle.get("trigger", "")
185
+ compatible = get_compatible_concepts(trigger)
186
+ if compatible:
187
+ concept = random.choice(compatible)
188
+ else:
189
+ concept = random.choice(self.all_concepts)
190
 
191
  return self._create_combination(angle, concept)
192
 
services/motivator.py CHANGED
@@ -1,6 +1,7 @@
1
  """
2
  Motivator service: generate multiple motivators from niche + angle + concept context.
3
  Used in Matrix mode: user picks angle/concept, we generate motivators, user selects one for ad generation.
 
4
  """
5
 
6
  import os
@@ -11,6 +12,11 @@ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
11
 
12
  from services.llm import llm_service
13
 
 
 
 
 
 
14
 
15
  async def generate_motivators(
16
  niche: str,
@@ -54,7 +60,12 @@ Output only motivators: short, punchy statements in the customer's internal voic
54
  Each motivator should feel like a hidden truth—resentment, shame, betrayal, avoided identity, or unspoken truth.
55
  No features, no demographics. Emotional truths only. Keep each to 7–12 words where possible."""
56
 
 
 
 
 
57
  prompt = f"""Niche: {niche.replace("_", " ").title()}
 
58
 
59
  ANGLE (psychological WHY):
60
  - Name: {angle_name}
@@ -67,7 +78,7 @@ CONCEPT (visual HOW):
67
  - Visual: {concept_visual}
68
  {extra_segment}
69
 
70
- Generate exactly {count} distinct emotional motivators that fit this angle and concept.
71
  Each motivator = one short statement, customer's internal voice.
72
  Output as a JSON array of strings only, e.g. ["Motivator 1", "Motivator 2", ...].
73
  No numbering, no explanations."""
 
1
  """
2
  Motivator service: generate multiple motivators from niche + angle + concept context.
3
  Used in Matrix mode: user picks angle/concept, we generate motivators, user selects one for ad generation.
4
+ Supports different ecom verticals for variety (fashion, beauty, supplements, fitness, etc.).
5
  """
6
 
7
  import os
 
12
 
13
  from services.llm import llm_service
14
 
15
+ try:
16
+ from data.ecom_verticals import get_random_vertical
17
+ except ImportError:
18
+ get_random_vertical = None
19
+
20
 
21
  async def generate_motivators(
22
  niche: str,
 
60
  Each motivator should feel like a hidden truth—resentment, shame, betrayal, avoided identity, or unspoken truth.
61
  No features, no demographics. Emotional truths only. Keep each to 7–12 words where possible."""
62
 
63
+ vertical_hint = ""
64
+ if get_random_vertical:
65
+ v = get_random_vertical()
66
+ vertical_hint = f"\nFor variety, motivators can speak to this ecom vertical (or blend verticals): {v['name']}."
67
  prompt = f"""Niche: {niche.replace("_", " ").title()}
68
+ {vertical_hint}
69
 
70
  ANGLE (psychological WHY):
71
  - Name: {angle_name}
 
78
  - Visual: {concept_visual}
79
  {extra_segment}
80
 
81
+ Generate exactly {count} distinct emotional motivators that fit this angle and concept. Use different ecom verticals (fashion, beauty, supplements, fitness, electronics, home, pets, food) for variety when it fits.
82
  Each motivator = one short statement, customer's internal voice.
83
  Output as a JSON array of strings only, e.g. ["Motivator 1", "Motivator 2", ...].
84
  No numbering, no explanations."""
services/third_flow.py CHANGED
@@ -1,9 +1,10 @@
1
- """Extensive ad generation: researcher → creative director → designer → copywriter (OpenAI + file search)."""
 
2
 
3
  import os
4
  import sys
5
  import time
6
- from typing import List
7
  from pydantic import BaseModel
8
 
9
  # Add parent directory to path for imports
@@ -79,14 +80,14 @@ class ThirdFlowService:
79
  "content": [
80
  {
81
  "type": "text",
82
- "text": """You are the researcher for the affiliate marketing company which does research on trending angles, concepts and psychology triggers based on the user input.
83
  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).
84
  A psychology trigger is an emotional or cognitive stimulus that pushes someone toward action—clicking, signing up, or buying—before logic kicks in.
85
  An ad angle is the reason someone should care right now. Same product → different reasons to click → different angles.
86
  An ad concept is the creative execution style or storyline you use to deliver an angle.
87
  In affiliate marketing 'Low-production, realistic often outperform studio creatives' runs most.
88
 
89
- Keeping in mind all this, make sure you provide different angles and concepts we can try based on the psychology triggers for the image ads for the given input based on affiliate marketing.
90
  User will provide you the category on which he needs to run the ads, what is the offer he is providing and what is target audience."""
91
  }
92
  ]
@@ -110,6 +111,7 @@ class ThirdFlowService:
110
  try:
111
  completion = self.client.beta.chat.completions.parse(
112
  model=self.gpt_model,
 
113
  messages=messages,
114
  response_format=ImageAdEssentialsOutput,
115
  )
@@ -125,6 +127,52 @@ class ThirdFlowService:
125
  print(f"Error in researcher: {e}")
126
  return []
127
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
128
  def retrieve_search(
129
  self,
130
  target_audience: str,
@@ -161,7 +209,7 @@ class ThirdFlowService:
161
  # Create assistant with vector store
162
  assistant = self.client.beta.assistants.create(
163
  name="Marketing Knowledge Assistant",
164
- instructions="You are a marketing research assistant. Search through the provided documents and extract relevant creative strategies and knowledge.",
165
  model="gpt-4o",
166
  tools=[{"type": "file_search"}],
167
  tool_resources={
@@ -259,7 +307,7 @@ class ThirdFlowService:
259
  # Create assistant with vector store
260
  assistant = self.client.beta.assistants.create(
261
  name="Ads Knowledge Assistant",
262
- instructions="You are a marketing research assistant. Search through the provided ad examples and extract relevant creative ideas and patterns.",
263
  model="gpt-4o",
264
  tools=[{"type": "file_search"}],
265
  tool_resources={
@@ -330,8 +378,10 @@ class ThirdFlowService:
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"
@@ -346,17 +396,19 @@ class ThirdFlowService:
346
  "content": [
347
  {
348
  "type": "text",
349
- "text": f"""You are the Creative Director for the affiliate marketing company which make creative strategies for the image ads on the basis of the research given and user's input.
350
  The research work includes the psychology triggers, angles and different concepts. Your work is to finalise the {n} strategies based on the research.
351
  There will also be researched content from the different marketing books. Along with these there will information about the old ads information which are winner.
352
  Make the strongest patterns for the image ads, which should include about what types of visual should be their, colors, what should be the text, what should be the tone of the text with it's placement and CTA.
 
 
353
  Along with this provide the title ideas and description/caption which should be added with the image ad. It will complete the full ad copy.
354
  If the image should include only visuals then text field must return None or NA.
355
  What information you should give make sure you give in brief and well defined.
356
  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).
357
  In affiliate marketing 'Low-production, realistic images often outperform studio creatives' runs most.
358
  Role of the Title: Stop the scroll and trigger emotion.
359
- Role of Body: The body is the main paragraph text which should Explain just enough, Reduce anxiety, Stay compliant and Push to the next step.
360
  Role of Description: Reduce friction and justify the click.
361
 
362
  Keeping in mind all this, make sure you provide different creative strategies for the image ads for the given input based on affiliate marketing.
@@ -369,15 +421,10 @@ class ThirdFlowService:
369
  "content": [
370
  {
371
  "type": "text",
372
- "text": f"""Following are the inputs:
373
- Researched Content: {researcher_str}
374
- Researched Content from marketing books: {book_knowledge}
375
- Old Ads Data: {ads_knowledge}
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,6 +447,33 @@ class ThirdFlowService:
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 ""
@@ -420,7 +494,7 @@ class ThirdFlowService:
420
  "content": [
421
  {
422
  "type": "text",
423
- "text": f"""You are the Creative Designer for the affiliate marketing company which makes the prompt from creative strategy given for the ad images in the affiliate marketing.
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.
@@ -432,6 +506,8 @@ class ThirdFlowService:
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,7 +528,7 @@ class ThirdFlowService:
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
  }
@@ -540,7 +616,7 @@ class ThirdFlowService:
540
  "content": [
541
  {
542
  "type": "text",
543
- "text": """You are the Copy Writer Designer for the affiliate marketing company which makes the ad copies from creative strategy given for the ad images in the affiliate marketing.
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
 
@@ -551,9 +627,9 @@ class ThirdFlowService:
551
  2. Short titles win because they are scan-friendly.
552
  3. Use Plain, Human Language. No marketing buzzwords.
553
  4. Imply, Don't Explain. Leave an open loop.
554
- 5. Avoid Hard Claims (Compliance). Especially for insurance & finance.
555
 
556
- Role of Body: The body is the main paragraph text which should Explain just enough, Reduce anxiety, Stay compliant and Push to the next step.
557
  1. Body Must Match the Title Emotion. If the title creates fear, the body must relieve it, not amplify it.
558
  2. Use "Soft Education," Not Sales. The body should feel informational, not promotional.
559
  3. Add Friction Reducers. You must explicitly reduce effort and risk.
 
1
+ """Extensive ad generation: researcher → creative director → designer → copywriter (OpenAI + file search).
2
+ Supports optional Creative Inventor to generate new angles, concepts, visuals, and triggers by itself."""
3
 
4
  import os
5
  import sys
6
  import time
7
+ from typing import List, Optional
8
  from pydantic import BaseModel
9
 
10
  # Add parent directory to path for imports
 
80
  "content": [
81
  {
82
  "type": "text",
83
+ "text": """You are the researcher with 20 years of experience for the affiliate marketing company which does research on trending angles, concepts and psychology triggers based on the user input.
84
  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).
85
  A psychology trigger is an emotional or cognitive stimulus that pushes someone toward action—clicking, signing up, or buying—before logic kicks in.
86
  An ad angle is the reason someone should care right now. Same product → different reasons to click → different angles.
87
  An ad concept is the creative execution style or storyline you use to deliver an angle.
88
  In affiliate marketing 'Low-production, realistic often outperform studio creatives' runs most.
89
 
90
+ Invent psychology triggers, angles, and concepts without limiting to the given niche. Suggest diverse and hyperrealistic audiences including outside the niche. Prioritize novelty and variety. Provide different angles and concepts we can try based on psychology triggers for the image ads; use the user input as a springboard, not a constraint.
91
  User will provide you the category on which he needs to run the ads, what is the offer he is providing and what is target audience."""
92
  }
93
  ]
 
111
  try:
112
  completion = self.client.beta.chat.completions.parse(
113
  model=self.gpt_model,
114
+
115
  messages=messages,
116
  response_format=ImageAdEssentialsOutput,
117
  )
 
127
  print(f"Error in researcher: {e}")
128
  return []
129
 
130
+ def get_essentials_via_inventor(
131
+ self,
132
+ niche: str,
133
+ offer: str,
134
+ n: int = 5,
135
+ *,
136
+ target_audience_hint: Optional[str] = None,
137
+ existing_reference: Optional[str] = None,
138
+ trend_context: Optional[str] = None,
139
+ competitor_insights: Optional[str] = None,
140
+ ) -> tuple[List[ImageAdEssentials], List[str]]:
141
+ """
142
+ Use the Creative Inventor to generate new angles, concepts, visuals,
143
+ psychological triggers, and hyper-specific target audiences.
144
+ Returns (essentials for creative_director, list of target_audience per essential).
145
+ """
146
+ try:
147
+ from services.creative_inventor import creative_inventor_service
148
+ except ImportError:
149
+ from creative_inventor import creative_inventor_service # noqa: F401
150
+ invented = creative_inventor_service.invent(
151
+ niche=niche,
152
+ offer=offer,
153
+ n=n,
154
+ target_audience_hint=target_audience_hint,
155
+ existing_reference=existing_reference,
156
+ trend_context=trend_context,
157
+ competitor_insights=competitor_insights,
158
+ )
159
+ essentials = self._invented_to_essentials(invented)
160
+ target_audiences = [getattr(e, "target_audience", "") or f"Audience {i+1}" for i, e in enumerate(invented)]
161
+ return (essentials, target_audiences)
162
+
163
+ def _invented_to_essentials(self, invented: list) -> List[ImageAdEssentials]:
164
+ """Convert InventedEssential list to ImageAdEssentials for creative_director."""
165
+ out: List[ImageAdEssentials] = []
166
+ for e in invented:
167
+ # Fold visual_directions into concepts so creative_director gets visual hints
168
+ concepts = list(getattr(e, "concepts", [])) + list(getattr(e, "visual_directions", []))
169
+ out.append(ImageAdEssentials(
170
+ phsychologyTriggers=getattr(e, "psychology_trigger", ""),
171
+ angles=list(getattr(e, "angles", [])),
172
+ concepts=concepts,
173
+ ))
174
+ return out
175
+
176
  def retrieve_search(
177
  self,
178
  target_audience: str,
 
209
  # Create assistant with vector store
210
  assistant = self.client.beta.assistants.create(
211
  name="Marketing Knowledge Assistant",
212
+ instructions="You are a marketing research assistant with 20 years of experience. Search through the provided documents and extract relevant creative strategies and knowledge.",
213
  model="gpt-4o",
214
  tools=[{"type": "file_search"}],
215
  tool_resources={
 
307
  # Create assistant with vector store
308
  assistant = self.client.beta.assistants.create(
309
  name="Ads Knowledge Assistant",
310
+ instructions="You are a marketing research assistant with 20 years of experience. Search through the provided ad examples and extract relevant creative ideas and patterns.",
311
  model="gpt-4o",
312
  tools=[{"type": "file_search"}],
313
  tool_resources={
 
378
  offer: str,
379
  niche: str = "",
380
  n: int = 5,
381
+ target_audiences: Optional[List[str]] = None,
382
  ) -> List[CreativeStrategies]:
383
+ """Create creative strategies from research, book knowledge, and ads knowledge.
384
+ When target_audiences is provided (one per strategy), each strategy is tailored to that hyper-specific audience."""
385
  # Convert researcher_output to string for prompt
386
  researcher_str = "\n".join([
387
  f"Psychology Triggers: {item.phsychologyTriggers}\n"
 
396
  "content": [
397
  {
398
  "type": "text",
399
+ "text": f"""You are the Creative Director with 20 years of experience for the affiliate marketing company which make creative strategies for the image ads on the basis of the research given and user's input.
400
  The research work includes the psychology triggers, angles and different concepts. Your work is to finalise the {n} strategies based on the research.
401
  There will also be researched content from the different marketing books. Along with these there will information about the old ads information which are winner.
402
  Make the strongest patterns for the image ads, which should include about what types of visual should be their, colors, what should be the text, what should be the tone of the text with it's placement and CTA.
403
+ Strategies can target any audience and use any visual or concept—research is a springboard, not a constraint.
404
+ When the user provides per-strategy target audiences, you MUST tailor each strategy (angle, concept, visual, title, body) to that specific audience.
405
  Along with this provide the title ideas and description/caption which should be added with the image ad. It will complete the full ad copy.
406
  If the image should include only visuals then text field must return None or NA.
407
  What information you should give make sure you give in brief and well defined.
408
  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).
409
  In affiliate marketing 'Low-production, realistic images often outperform studio creatives' runs most.
410
  Role of the Title: Stop the scroll and trigger emotion.
411
+ Role of Body: The body is the main paragraph text which should Explain just enough, Reduce anxiety, and Push to the next step.
412
  Role of Description: Reduce friction and justify the click.
413
 
414
  Keeping in mind all this, make sure you provide different creative strategies for the image ads for the given input based on affiliate marketing.
 
421
  "content": [
422
  {
423
  "type": "text",
424
+ "text": self._creative_director_user_message(
425
+ researcher_str, book_knowledge, ads_knowledge,
426
+ niche, offer, target_audience, n, target_audiences,
427
+ )
 
 
 
 
 
428
  }
429
  ]
430
  }
 
447
  print(f"Error in creative_director: {e}")
448
  return []
449
 
450
+ def _creative_director_user_message(
451
+ self,
452
+ researcher_str: str,
453
+ book_knowledge: str,
454
+ ads_knowledge: str,
455
+ niche: str,
456
+ offer: str,
457
+ target_audience: str,
458
+ n: int,
459
+ target_audiences: Optional[List[str]] = None,
460
+ ) -> str:
461
+ """Build user message for creative_director; inject per-strategy audiences when provided."""
462
+ audience_block = ""
463
+ if target_audiences and len(target_audiences) >= n:
464
+ per = "\n".join([f"Strategy {i+1} must target: {aud}" for i, aud in enumerate(target_audiences[:n])])
465
+ audience_block = f"\n\nCRITICAL - Each strategy must speak to this hyper-specific audience:\n{per}\n\nTailor angle, concept, visual, title, and body to that audience."
466
+ return f"""Following are the inputs:
467
+ Researched Content: {researcher_str}
468
+ Researched Content from marketing books: {book_knowledge}
469
+ Old Ads Data: {ads_knowledge}
470
+ Niche: {niche}
471
+ Offer to run: {offer}
472
+ Target Audience (overall): {target_audience}
473
+ {audience_block}
474
+
475
+ Provide the different creative strategies based on the given input."""
476
+
477
  def creative_designer(self, creative_strategy: CreativeStrategies, niche: str = "") -> str:
478
  """Generate image prompt from a creative strategy."""
479
  niche_lower = niche.lower().replace(" ", "_").replace("-", "_") if niche else ""
 
494
  "content": [
495
  {
496
  "type": "text",
497
+ "text": f"""You are the Creative Designer with 20 years of experience for the affiliate marketing company which makes the prompt from creative strategy given for the ad images in the affiliate marketing.
498
  Nano Banana image model will be used to generate the images
499
  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).
500
  In affiliate marketing 'Low-production, realistic images often outperform studio creatives' runs most.
 
506
  For image model here's structure for the prompt: [The Hook - emotion from psychology trigger/angle] + [The Subject] + [The Context/Setting] + [The Technical Polish]
507
  {niche_guidance}
508
 
509
+ CRITICAL - TEXT IN IMAGE: The strategy includes "Text" (title/caption to show). Your prompt MUST describe where and how this text appears in the image as visible, readable copy—e.g. on a sign, document, phone screen, poster, note, or surface in the scene. The image must contain that exact text (or the main phrase) so viewers can read it. Do not omit text from the prompt.
510
+
511
  CRITICAL: If the image includes people or faces, ensure they look like real, original people with:
512
  - Photorealistic faces with natural skin texture, visible pores, and realistic skin imperfections
513
  - Natural facial asymmetry (no perfectly symmetrical faces)
 
528
  "type": "text",
529
  "text": f"""Following is the creative strategy:
530
  {strategy_str}
531
+ 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. You MUST include in the prompt a clear description of where the Text from the strategy appears in the scene (e.g. "with the text '[exact phrase]' visible on a sign/phone/document") so the generated image contains readable copy."""
532
  }
533
  ]
534
  }
 
616
  "content": [
617
  {
618
  "type": "text",
619
+ "text": """You are the Copy Writer Designer with 20 years of experience for the affiliate marketing company which makes the ad copies from creative strategy given for the ad images in the affiliate marketing.
620
  The ad copy must include the title, body and description related to the strategies.
621
  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).
622
 
 
627
  2. Short titles win because they are scan-friendly.
628
  3. Use Plain, Human Language. No marketing buzzwords.
629
  4. Imply, Don't Explain. Leave an open loop.
630
+ 5. You may use bold, scroll-stopping, and clickbait-style statements when effective—no compliance or claim restrictions; maximize impact.
631
 
632
+ Role of Body: The body is the main paragraph text which should Explain just enough, Reduce anxiety, and Push to the next step.
633
  1. Body Must Match the Title Emotion. If the title creates fear, the body must relieve it, not amplify it.
634
  2. Use "Soft Education," Not Sales. The body should feel informational, not promotional.
635
  3. Add Friction Reducers. You must explicitly reduce effort and risk.