sushilideaclan01's picture
Restore changes from lost commit (excluding creativity_examples binaries for HF)
7067477
"""
Deep marketing analysis of product data for ad creative generation.
"""
import json
from app.llm import call_llm, call_llm_vision, extract_json
def analyze_product(product_data: dict, target_audience: list[str] | None = None) -> dict:
"""
Deep marketing analysis of the product.
Returns structured dict for generating highly targeted ad creative.
When target_audience is provided, analysis is tailored to those segments.
"""
price = product_data.get("price", "")
audience_block = ""
if target_audience and len(target_audience) > 0:
audience_list = ", ".join(target_audience)
audience_block = f"""
━━━━━━━━━━━━━━━━━━
TARGET AUDIENCES (PRIORITY — MUST DRIVE THIS ANALYSIS)
━━━━━━━━━━━━━━━━━━
The user has selected these specific audience segments. Your ENTIRE analysis MUST be tailored to these audiences.
- positioning: Reframe for these segments (who they are, what they want, how this product fits).
- ideal_customer: Age, lifestyle, platform, and pain_points must align with the selected segments—not generic "urban women" or "25-35".
- emotional_triggers: Choose triggers that resonate with these specific segments.
- ad_angles: Each angle must speak directly to one or more of these audiences; hooks and "why it works" must reference them.
- tagline_options: Lines that appeal to these segments.
- price_analysis: Frame value and perception for these buyers (e.g. COD buyers, ₹1.5k–₹3k, festive, self-gifters).
- shooting_angles, color_worlds, copy_direction: Align with how these audiences discover and consume content.
Selected segments (use these, do not substitute generic demographics):
{audience_list}
Do NOT produce a generic analysis. Every section must reflect the selected segments.
"""
prompt = f"""
You are an elite Indian DTC jewelry performance strategist.
You have scaled multiple 7–8 figure jewelry brands
using Meta static ads.
You think ONLY in:
- Scroll stopping power
- Emotional tension
- Identity signaling
- Conversion psychology
- Visual hierarchy
Your job:
Perform a DEEP conversion-focused analysis of THIS specific product
that will directly power high-performing STATIC ad creatives.
{audience_block}
━━━━━━━━━━━━━━━━━━
CRITICAL PERFORMANCE RULES
━━━━━━━━━━━━━━━━━━
1. No generic marketing language.
2. No empty phrases like “high quality” or “perfect for any occasion”.
3. Every insight must directly influence:
- Visual direction
- Hook creation
- Or targeting decision.
4. Assume this is being sold via Meta ads to global urban buyers (unless target audiences specify otherwise).
5. If product data lacks detail, infer logically based on category norms.
6. Think in micro-audiences, not broad demographics.
7. This analysis must help generate scroll-stopping static creatives.
Return ONLY valid JSON.
No markdown.
No commentary.
No explanations outside JSON.
━━━━━━━━━━━━━━━━━━
PRODUCT DATA
━━━━━━━━━━━━━━━━━━
{json.dumps(product_data, indent=2)}
━━━━━━━━━━━━━━━━━━
OUTPUT REQUIREMENTS
━━━━━━━━━━━━━━━━━━
- Exactly 6 emotional triggers
- Exactly 5 ad angles
- Exactly 4 shooting_angles
- Exactly 3 color_worlds
- Exactly 4 unique_selling_points
- 3 tagline_options
Price rules:
Final price = {product_data.get('price', '')}
Anchor price must not exceed 2.5x final price.
Value framing must feel psychologically believable.
━━━━━━━━━━━━━━━━━━
RETURN THIS EXACT JSON STRUCTURE
━━━━━━━━━━━━━━━━━━
{{
"product_visual_notes": "When product images were provided: 2-3 short paragraphs. (1) How it looks when worn: describe how the piece sits on the wearer (ear/neck/wrist), styling context, how it frames the face or body, movement or drape. (2) Shape, size & proportions: describe shape (e.g. dangler, stud, length), scale and proportions, drop length or size of elements if visible, key design details (chains, stones, motifs). (3) Material, finish & color: metal type, finish (matte/shiny/brushed), color as seen, stones or accents. Omit or empty string if no images.",
"positioning": "Clear one-line positioning statement (who + outcome + differentiation)",
"tagline_options": ["Option 1", "Option 2", "Option 3"],
"ideal_customer": {{
"age_range": "",
"lifestyle": "specific, not generic",
"platform": "primary buying platform",
"pain_points": ["Pain 1", "Pain 2", "Pain 3"]
}},
"emotional_triggers": ["Trigger 1", "Trigger 2", "Trigger 3", "Trigger 4", "Trigger 5", "Trigger 6"],
"price_analysis": {{
"price_point": "{product_data.get('price', '')}",
"perception": "how this price feels emotionally",
"anchor_price": "realistic strikethrough",
"value_framing": "believable comparison framing"
}},
"ad_angles": [
{{ "angle_name": "", "hook": "", "why_it_works": "" }},
{{ "angle_name": "", "hook": "", "why_it_works": "" }},
{{ "angle_name": "", "hook": "", "why_it_works": "" }},
{{ "angle_name": "", "hook": "", "why_it_works": "" }},
{{ "angle_name": "", "hook": "", "why_it_works": "" }}
],
"shooting_angles": ["Angle 1", "Angle 2", "Angle 3", "Angle 4"],
"color_worlds": ["Direction 1", "Direction 2", "Direction 3"],
"copy_direction": {{
"headline_style": "e.g. bold serif uppercase + script style",
"tone": "clear tonal direction"
}},
"unique_selling_points": ["USP 1", "USP 2", "USP 3", "USP 4"]
}}
Return ONLY valid JSON. No markdown. No commentary.
"""
# First 3 product image URLs for vision (comma-split, strip, filter valid)
image_urls = [
u.strip()
for u in (product_data.get("product_images") or "").split(",")
if (u and u.strip().startswith("http"))
][:3]
if image_urls:
vision_note = "\n\nYou are also provided with product images in this message. Describe the product in detail: (1) how it looks when worn—how it sits on the wearer, styling, frame of face/body, movement; (2) shape, size, and proportions—shape type, scale, drop length, key design details; (3) material, finish, and color as seen. Put this in product_visual_notes as 2-3 short paragraphs. Use these observations to inform your visual direction, shooting_angles, color_worlds, and overall creative recommendations."
content: list[dict] = [{"type": "text", "text": prompt + vision_note}]
for url in image_urls:
content.append({"type": "image_url", "image_url": {"url": url}})
raw = call_llm_vision(
messages=[{"role": "user", "content": content}],
model="gpt-4o",
temperature=0.7,
)
else:
raw = call_llm(prompt, temperature=0.7)
return extract_json(raw)