""" Creative Inventor: generates new ad angles, concepts, visuals, and psychological triggers by itself. Can run standalone (ideas for review/export) or feed into the extensive flow. """ from typing import List, Optional from openai import OpenAI from pydantic import BaseModel from config import settings # --------------------------------------------------------------------------- # Output models: what the inventor produces # --------------------------------------------------------------------------- class InventedEssential(BaseModel): """One invented creative essential: trigger + angles + concepts + visual directions + hyper-specific audience.""" psychology_trigger: str angles: List[str] concepts: List[str] visual_directions: List[str] hooks: List[str] = [] # Optional scroll-stopping headlines visual_styles: List[str] = [] # Optional scene/prompt-style descriptions target_audience: str = "" # Hyper-specific audience for this essential (AI-decided, different per essential) class InventedCreativeOutput(BaseModel): """Structured output from the creative inventor.""" output: List[InventedEssential] class CreativeInventorService: """ Invents new ad angles, concepts, visuals, and psychological triggers. Use standalone to generate ideas, or feed output into the extensive flow. """ def __init__(self): self.client = OpenAI(api_key=settings.openai_api_key) self.model = getattr(settings, "third_flow_model", "gpt-4o") def invent( self, niche: str, offer: str, *, target_audience_hint: Optional[str] = None, existing_reference: Optional[str] = None, trend_context: Optional[str] = None, competitor_insights: Optional[str] = None, n: int = 5, ) -> List[InventedEssential]: """ Invent new psychological triggers, angles, concepts, visual directions, hooks, visual styles, and a hyper-specific target_audience per essential. The AI decides all target audiences; they must be different types (demographic, psychographic, situation). Args: niche: e.g. "GLP-1", "Home Insurance" offer: what is being promoted target_audience_hint: optional broad hint (e.g. "weight loss"); AI still invents distinct hyper-specific audiences per essential existing_reference: optional summary of existing strategies (to diverge or avoid repeating) trend_context: optional trending topics or occasions competitor_insights: optional notes on what competitors are doing n: number of invented "essentials" to return (each with its own hyper-specific audience) Returns: List of InventedEssential for use in extensive flow or export. """ 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. Your job is to CREATE novel ideas—not to recycle generic ones. For each "essential" you produce: - 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". - Psychology trigger: the emotional or cognitive lever (e.g. validation before solution, future regret, justified scarcity, objection-first then flip). - Angles: the reasons this specific audience should care; reframe the problem or outcome in a fresh way. - Concepts: the creative execution or storyline (e.g. crossed-out negative labels, progress bar + social proof, two-path fork). - Visual directions: concrete visual ideas (e.g. "progress bar 87% full with 10k counter", "grid of crossed-out DAY 1s"). - Hooks: 2–4 scroll-stopping headline phrases for that essential and audience. - Visual styles: 2–4 short scene/prompt-style descriptions for image generation. Rules: - Each essential MUST have a different target_audience. Vary types: age/gender, life situation, mindset, past failures, skepticism, convenience-seekers, etc. - Prioritize novelty and specificity. Avoid vague angles or generic audiences. - Combine triggers in new ways. Invent visual metaphors that work for the chosen audience. - Output exactly the number of "essentials" requested (n). Each essential should feel distinct and speak to a distinctly different audience.""" user_parts = [ f"Niche: {niche}", f"Offer: {offer}", 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.", ] if target_audience_hint: user_parts.append(f"Broad category hint (invent specific segments within or related to this): {target_audience_hint}") if existing_reference: user_parts.append(f"Existing strategies to diverge from (do not copy):\n{existing_reference}") if trend_context: user_parts.append(f"Trend / occasion context (use for relevance):\n{trend_context}") if competitor_insights: user_parts.append(f"Competitor / market insights (use to differentiate):\n{competitor_insights}") user_content = "\n\n".join(user_parts) # Ensure model returns target_audience; fallback for older parsed outputs # (InventedEssential now has target_audience with default "") try: completion = self.client.beta.chat.completions.parse( model=self.model, messages=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": user_content}, ], response_format=InventedCreativeOutput, ) response = completion.choices[0].message if response.parsed and response.parsed.output: return response.parsed.output[:n] return [] except Exception as e: print(f"CreativeInventor error: {e}") return [] def invent_and_export( self, niche: str, offer: str, essentials: Optional[List["InventedEssential"]] = None, target_audience_hint: Optional[str] = None, **kwargs, ) -> str: """ Invent (if essentials not provided) and return a human-readable text summary for export or review. """ if essentials is None: essentials = self.invent(niche=niche, offer=offer, target_audience_hint=target_audience_hint, **kwargs) lines = [ f"# Invented creatives — {niche}" + (f" | {target_audience_hint}" if target_audience_hint else ""), "", ] for i, e in enumerate(essentials, 1): aud = getattr(e, "target_audience", "") or "" lines.append(f"## Essential {i}: {e.psychology_trigger}" + (f" — {aud}" if aud else "")) lines.append("") if aud: lines.append("**Target audience:** " + aud) lines.append("") lines.append("**Angles:** " + " | ".join(e.angles)) lines.append("**Concepts:** " + " | ".join(e.concepts)) lines.append("**Visual directions:** " + " | ".join(e.visual_directions)) if e.hooks: lines.append("**Hooks:** " + " | ".join(e.hooks)) if e.visual_styles: lines.append("**Visual styles:** " + " | ".join(e.visual_styles)) lines.append("") return "\n".join(lines) # Singleton for use from third_flow / generator creative_inventor_service = CreativeInventorService()