File size: 8,311 Bytes
026f283
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
"""
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()