File size: 5,079 Bytes
5d53706
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
03ecc95
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
162
163
164
165
166
167
168
169
from typing import List, Literal, Optional
from pydantic import BaseModel, Field

from openai import OpenAI
from dotenv import load_dotenv
import os

load_dotenv()

client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))

class VisualHierarchy(BaseModel):
    flow_summary: str = Field(..., description="Brief of top-to-bottom reading flow and funneling.")
    key_elements_order: List[str] = Field(..., description="Ordered list of elements as the eye flows.")

class TypographySpec(BaseModel):
    casing: Literal["all-caps","title-case","sentence-case","mixed"]
    weight: Literal["light","regular","medium","bold","extra-bold"]
    color: str
    notes: str

class CTAInfo(BaseModel):
    text: str
    shape_or_style: str
    bg_color: str
    text_color: str
    contrast_comment: str

class VisualStructureLayout(BaseModel):
    background_color: str
    psychological_association: str
    fit_to_theme_comment: str
    typography: TypographySpec
    minimalism_level: Literal["none","low","medium","high"]
    cta: CTAInfo
    hierarchy: VisualHierarchy

class CopywritingBreakdown(BaseModel):
    headline: str
    device_used: List[Literal["curiosity_gap","authority","fear_avoidance","education","urgency","social_proof","other"]]
    supporting_text: str
    compliance_comment: str
    cta_text: str
    funnel_feel: Literal["content_recommendation","direct_response","hybrid"]

class PsychologicalBehavioralTriggers(BaseModel):
    curiosity: str
    fear_or_pain_point: str
    authority_appeal: str
    ease_of_action: str

class StoryCreativeTells(BaseModel):
    problem: str
    hope: str
    next_step: str
    narrative_summary: str

class StrengthWeaknessItem(BaseModel):
    point: str
    why_it_matters: str

class RisksArbitrageContext(BaseModel):
    bounce_risk: str
    fatigue_risk: str
    compliance_risk: str

class CreativeVariant(BaseModel):
    hypothesis: str
    change: str
    expected_effect: str
    metric_to_watch: Literal["CTR","CPC","CPI","CVR","RPM","BounceRate","TimeOnPage","QualityRanking","Other"]

class FunnelSynergy(BaseModel):
    landing_page_angle: str
    affiliate_or_offer_fit: Optional[str] = None

class TestingPlan(BaseModel):
    primary_metrics: List[Literal["CTR","CPC","CVR","RPM","BounceRate","TimeOnPage","QualityRanking","Other"]]
    sample_size_or_duration_hint: str
    stop_loss_rules: str

class OptimizationNextSteps(BaseModel):
    creative_variants: List[CreativeVariant]
    funnel_synergy: FunnelSynergy
    testing_plan: TestingPlan

class MetaDetect(BaseModel):
    language: str
    detected_topic: str
    confidence_0to1: float = Field(ge=0, le=1)
    red_flags: List[str] = Field(..., description="Potential policy or brand-safety flags, if any.")

class AdAnalysis(BaseModel):
    visual_structure_layout: VisualStructureLayout
    copywriting_breakdown: CopywritingBreakdown
    psychological_behavioral_triggers: PsychologicalBehavioralTriggers
    story_creative_tells: StoryCreativeTells
    strengths: List[StrengthWeaknessItem]
    weaknesses: List[StrengthWeaknessItem]
    risks_in_arbitrage_context: RisksArbitrageContext
    optimization_next_steps: OptimizationNextSteps
    meta: MetaDetect


SYSTEM_PROMPT = """
You are a senior performance-creative analyst who analyze a SINGLE image ad. Do not include explanations, markdown, or extra keys.

Rules:
- Be precise and concise. Avoid generic fluff.
- When unsure, infer cautiously and note uncertainty.
- Explain “why it matters” in business terms (CTR, CPC, CVR, RPM, bounce, fatigue, policy).
- Do not invent medical claims or promises; flag policy risks instead.
- Keep color names human-readable (e.g., “pure orange”, “purple”, “yellow”).
"""

USER_PROMPT_TEMPLATE = """
Task: Analyze the attached image ad and produce a structured breakdown:
1) Visual structure & layout
2) Copywriting breakdown
3) Psychological & behavioral triggers
4) Story the creative tells
5) Strengths
6) Weaknesses
7) Risks in arbitrage context
8) Optimization / next steps (variants, funnel synergy, testing plan)

Constraints:
- Use crisp, decision-ready language.
- If text is in Spanish or another language, keep your summaries in English but preserve original CTA text.
"""

def get_analysis(img_url):
    messages=[
        {
            "role": "system",
            "content": [
                {
                    "type": "text",
                    "text": SYSTEM_PROMPT
                }
            ]
        },
        {
            "role": "user",
            "content": [
                {
                    "type": "text",
                    "text": USER_PROMPT_TEMPLATE
                },
                {
                    "type": "image_url",
                    "image_url": {
                        "url": img_url
                    }
                }
            ]
        }
    ]

    completion = client.chat.completions.parse(
        model="gpt-4o",
        messages=messages,
        response_format=AdAnalysis,
    )

    ad_analysis = completion.choices[0].message.parsed

    return ad_analysis