Commit
·
026f283
1
Parent(s):
4a56a0b
add new things in the extensive flow
Browse files- api/routers/extensive.py +63 -1
- api/schemas.py +32 -0
- data/auto_insurance.py +115 -115
- data/ecom_verticals.py +50 -0
- data/glp1.py +236 -0
- docs/competitor_ad_analysis.md +171 -0
- docs/creative_inventor.md +65 -0
- services/creative_inventor.py +160 -0
- services/generator.py +151 -123
- services/generator_prompts.py +16 -104
- services/matrix.py +41 -19
- services/motivator.py +12 -1
- services/third_flow.py +99 -23
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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 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 |
-
|
| 222 |
|
| 223 |
-
ALL_VISUAL_STYLES = []
|
| 224 |
-
for strategy in STRATEGIES.values():
|
| 225 |
-
|
| 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
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 189 |
return self._niche_data_cache[niche]
|
| 190 |
|
| 191 |
# ========================================================================
|
|
@@ -441,7 +465,7 @@ class AdGenerator:
|
|
| 441 |
{context}
|
| 442 |
|
| 443 |
Rules:
|
| 444 |
-
-
|
| 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
|
| 564 |
-
ANGLE: {angle.get('name') if angle else 'N/A'}
|
| 565 |
-
|
| 566 |
-
- Example Hook: "{angle.get('example') if angle else 'N/A'}"
|
| 567 |
-
- This angle answers WHY they should care
|
| 568 |
|
| 569 |
-
|
| 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
|
| 610 |
|
| 611 |
=== OUTPUT REQUIREMENTS ===
|
| 612 |
|
| 613 |
1. HEADLINE (The "Arrest")
|
| 614 |
-
-
|
| 615 |
-
-
|
| 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
|
| 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
|
| 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,
|
| 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:
|
| 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
|
|
|
|
| 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.
|
| 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": "
|
| 667 |
-
"primary_text": "
|
| 668 |
-
"description": "
|
| 669 |
-
"body_story": "
|
| 670 |
-
"image_brief": "
|
| 671 |
"cta": "{cta}",
|
| 672 |
-
"psychological_angle": "
|
| 673 |
-
"why_it_works": "
|
| 674 |
}}
|
| 675 |
|
| 676 |
-
Generate the ad copy now
|
| 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 = """===
|
| 888 |
-
If this image
|
| 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 |
-
-
|
| 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 (
|
| 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 |
-
|
| 1007 |
-
|
| 1008 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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.
|
| 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
|
| 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 |
-
|
| 1839 |
-
|
| 1840 |
|
| 1841 |
Args:
|
| 1842 |
-
niche: Target niche (home_insurance, glp1, auto_insurance, or custom
|
| 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
|
| 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 |
-
|
| 1870 |
-
|
| 1871 |
-
|
| 1872 |
-
|
| 1873 |
-
|
| 1874 |
-
|
| 1875 |
-
|
| 1876 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1877 |
|
| 1878 |
if not researcher_output:
|
| 1879 |
-
raise ValueError("
|
| 1880 |
|
| 1881 |
-
# Step 2: Retrieve knowledge (in parallel)
|
| 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 |
-
|
| 1889 |
),
|
| 1890 |
asyncio.to_thread(
|
| 1891 |
third_flow_service.retrieve_ads,
|
| 1892 |
-
|
| 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=
|
| 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
|
| 1961 |
-
|
| 1962 |
|
| 1963 |
# Refine prompt and add variation for each image (pass niche for demographic fixes)
|
| 1964 |
-
base_refined_prompt = self._refine_image_prompt(
|
| 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 |
-
|
| 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 |
-
|
| 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 |
-
|
| 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
|
| 2358 |
-
"primary_text": "2-3 emotional sentences
|
| 2359 |
-
"description": "
|
| 2360 |
-
"body_story": "
|
| 2361 |
-
"image_brief": "
|
| 2362 |
"cta": "{cta}",
|
| 2363 |
"psychological_angle": "{angle.get('name')}",
|
| 2364 |
-
"why_it_works": "
|
| 2365 |
}}
|
| 2366 |
|
| 2367 |
-
Generate the ad now. Be bold
|
| 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 |
-
===
|
| 2464 |
-
If this image
|
| 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 = """===
|
| 13 |
-
|
| 14 |
-
WITH NUMBERS (
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
WITHOUT NUMBERS (
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 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
|
| 161 |
-
|
| 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 |
-
|
| 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,
|
| 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":
|
| 373 |
-
|
| 374 |
-
|
| 375 |
-
|
| 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.
|
| 555 |
|
| 556 |
-
Role of Body: The body is the main paragraph text which should Explain just enough, Reduce anxiety,
|
| 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.
|